import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import com.google.gwt.event.shared.HandlerRegistration;
import com.smartgwt.client.data.RecordList;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.TransferImgButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.CanvasItem;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.form.validator.CustomValidator;
import com.smartgwt.client.widgets.form.validator.Validator;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.layout.VStack;

public class PatchedMultipleItem<T> extends CanvasItem {

	private static final String ATTR_VALUE = "v";

	private FormItem valueInputItem;
	private IButton addButton;

	private ListGrid grid;
	private Layout gridButtons;

	private Boolean required;
	
	// TODO: Patch until I can add a ChangedHandler to a CanvasItem as I do with other FormItems
	private List<ChangedHandler> changedHandlers = new ArrayList<ChangedHandler>();


	public PatchedMultipleItem(String name, String title, FormItem valueInputItem) {
		
		super(name, title);
		this.valueInputItem = valueInputItem;
		init();
	}
	
	public PatchedMultipleItem(FormItem valueInputItem) {
		
		super();
		this.valueInputItem = valueInputItem;
		init();
	}

	private void init() {
		
		valueInputItem.setShowTitle(false);
		valueInputItem.setRequired(true);
		
		grid = new ListGrid();  
		grid.setShowAllRecords(true);
		grid.setShowHeader(false);
		ListGridField listGridField = new ListGridField(ATTR_VALUE);
		configureListGridField(listGridField);
		grid.setFields( listGridField );
		grid.setHeight(120);
		
		final DynamicForm form = new DynamicForm();
		form.setFields(valueInputItem);
		
		addButton = new IButton("+");
		addButton.setWidth(20);
		addButton.addClickHandler(new ClickHandler() {
			@SuppressWarnings("unchecked")
			@Override
			public void onClick(ClickEvent event) {
				if( form.validate() ) {
					T value = (T)valueInputItem.getValue();
					addValue(value);
				}
			}
		});
		
		gridButtons = createGridButtons();
		
		Layout upperLayout = new HLayout(5);
		upperLayout.addMember(form);
		upperLayout.addMember(addButton);

		Layout bottomLayout = new HLayout(5);
		bottomLayout.addMember(grid);
		bottomLayout.addMember(gridButtons);

		Layout layout = new VLayout(5);
		layout.addMember(upperLayout);
		layout.addMember(bottomLayout);

		setCanvas(layout);
		
		setShouldSaveValue(true);
		
		Validator requiredValidator = new CustomValidator() {
			@SuppressWarnings("unchecked")
			@Override
			protected boolean condition(Object value) {
				return !required || isNotEmpty((List<T>)value);
			}			
		};
		
		setValidators( requiredValidator );
	}

	private static boolean isNotEmpty(Collection<?> col) {
		return col != null && !col.isEmpty();
	}

	protected void configureListGridField(ListGridField listGridField) {
		// You can add date formatter here, for instance
	}

	// TODO: Patch until I can add a ChangedHandler to a CanvasItem as I do with other FormItems
	@Override
	public HandlerRegistration addChangedHandler(ChangedHandler changedHandler) {
		changedHandlers.add(changedHandler);
		return null;
	}
	
	@Override
	public void setWidth(int width) {
		valueInputItem.setWidth(width);
		grid.setWidth(width);
		super.setWidth(width);
	}
	
	private void addValue(T value) {
		grid.addData(crearRecord(value));
		fireGridUpdatedEvent();
	}

	@Override
	public Object getValue() {
		return recordsToList(grid.getRecords());
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setValue(Object value) {
		grid.setData( listToRecords((List<T>)value) );
		fireGridUpdatedEvent();
	}
	
	private void fireGridUpdatedEvent() {
		
		List<T> value = recordsToList(grid.getRecords());
		
		// TODO: Throws an exception
		// http://forums.smartclient.com/showthread.php?p=74568&posted=1#post74568
//		storeValue(value);
		
		// TODO: In the meantime I store the value this way
		super.setValue(value);

		// TODO: Patch until I can add a ChangedHandler to a CanvasItem as I do with other FormItems
		for( ChangedHandler changedHandler : changedHandlers ) {
			ChangedEvent event = new ChangedEvent(null) {
				@Override
				public FormItem getItem() {
					return PatchedMultipleItem.this;
				}
				@Override
				public Object getValue() {
					return PatchedMultipleItem.this.getValue();
				}
			};
			changedHandler.onChanged(event);
		}
	}

	private ListGridRecord[] listToRecords(List<T> value) {
		
		int size = value != null ? value.size() : 0;
		
		ListGridRecord[] records = new ListGridRecord[size];
		for (int i=0; i<size; i++) {
			records[i] = crearRecord(value.get(i));
		}
		return records;
	}

	@SuppressWarnings("unchecked")
	private List<T> recordsToList(ListGridRecord[] records) {
		
		List<T> result = new ArrayList<T>();
		
		for (ListGridRecord record : records) {
			ValueRecord<T> valueRecord = (ValueRecord<T>) record;
			result.add( valueRecord.getValue() );
		}
		
		return result;
	}
	
	@Override
	public void setRequired(Boolean required) {
		this.required = required;
		super.setRequired(required);
	}
	
	/**
	 * Returns a {@link ValueRecord} containing the value.
	 * 
	 * Dates must be treated differently.
	 */
	private ListGridRecord crearRecord(T value) {
		if( value instanceof Date ) {
			return new DateValueRecord((Date) value);
		} else {
			return new ValueRecord<T>(value);
		}
	}

	private static class ValueRecord<V> extends ListGridRecord {

		public ValueRecord(V value) {
			setAttribute(ATTR_VALUE, value);  
		}
		
		@SuppressWarnings("unchecked")
		public V getValue() {
			return (V) getAttributeAsObject(ATTR_VALUE);
		}
	}
	
	private static class DateValueRecord extends ValueRecord<Date> {
		
		public DateValueRecord(Date value) {
			super(value);
		}

		@Override
		public Date getValue() {
			return getAttributeAsDate(ATTR_VALUE);
		}
	}

	private Layout createGridButtons() {

		VStack modifyStack = new VStack(3);
		modifyStack.setWidth(20);
		modifyStack.setAlign(VerticalAlignment.CENTER);

		TransferImgButton up = new TransferImgButton(TransferImgButton.UP);
		up.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				ListGridRecord selectedRecord = grid.getSelectedRecord();
				if (selectedRecord != null) {
					int idx = grid.getRecordIndex(selectedRecord);
					if (idx > 0) {
						RecordList rs = grid.getRecordList();
						rs.removeAt(idx);
						rs.addAt(selectedRecord, idx - 1);
						fireGridUpdatedEvent();
					}
				}
			}
		});

		TransferImgButton upFirst = new TransferImgButton(TransferImgButton.UP_FIRST);
		upFirst.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				ListGridRecord selectedRecord = grid.getSelectedRecord();
				if (selectedRecord != null) {
					int idx = grid.getRecordIndex(selectedRecord);
					if (idx > 0) {
						RecordList rs = grid.getRecordList();
						rs.removeAt(idx);
						rs.addAt(selectedRecord, 0);
						fireGridUpdatedEvent();
					}
				}
			}
		});

		TransferImgButton down = new TransferImgButton(TransferImgButton.DOWN);
		down.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				ListGridRecord selectedRecord = grid.getSelectedRecord();
				if (selectedRecord != null) {
					RecordList rs = grid.getRecordList();
					int numRecords = rs.getLength();
					int idx = grid.getRecordIndex(selectedRecord);
					if (idx < numRecords - 1) {
						rs.removeAt(idx);
						rs.addAt(selectedRecord, idx + 1);
						fireGridUpdatedEvent();
					}
				}
			}
		});

		TransferImgButton downLast = new TransferImgButton(TransferImgButton.DOWN_LAST);
		downLast.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				ListGridRecord selectedRecord = grid.getSelectedRecord();
				if (selectedRecord != null) {
					RecordList rs = grid.getRecordList();
					int numRecords = rs.getLength();
					int idx = grid.getRecordIndex(selectedRecord);
					if (idx < numRecords - 1) {
						rs.removeAt(idx);
						rs.addAt(selectedRecord, rs.getLength());
						fireGridUpdatedEvent();
					}
				}
			}
		});

		TransferImgButton delete = new TransferImgButton(TransferImgButton.DELETE);
		delete.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				ListGridRecord[] selectedRecords = grid.getSelectedRecords();
				if (selectedRecords != null) {
					for (ListGridRecord selectedRecord : selectedRecords) {
						grid.removeData(selectedRecord);
						fireGridUpdatedEvent();
					}
				}
			}
		});

		modifyStack.addMember(upFirst);
		modifyStack.addMember(up);
		modifyStack.addMember(down);
		modifyStack.addMember(downLast);
		modifyStack.addMember(delete);
		return modifyStack;
	}
}
