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

import com.allen_sauer.gwt.log.client.Log;
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.FormItemInitHandler;
import com.smartgwt.client.widgets.form.fields.events.ShowValueEvent;
import com.smartgwt.client.widgets.form.fields.events.ShowValueHandler;
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;

/**
 * Item with multiple values.
 * New values are entered through the {@link FormItem} passed in the constructor.
 * Values are represented ny a {@link ListGrid}.
 * 
 * See: http://forums.smartclient.com/showpost.php?p=78999&postcount=13
 */
public class MultipleItem<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;

	/** Stores a value that was set before grid was created */
	private Object valueSetBeforeGridWasCreated;
	

	public MultipleItem(String name, String title, FormItem valueInputItem) {
		
		super(name, title);
		init(valueInputItem);
	}
	
	public MultipleItem(FormItem valueInputItem) {
		
		super();
		init(valueInputItem);
	}

	private void init(FormItem valueInputItem0) {
		
		Log.debug("MultipleItem init");

		valueInputItem = valueInputItem0;
		valueInputItem.setShowTitle(false);
		valueInputItem.setRequired(true);
		
		setShouldSaveValue(true);
		
		setInitHandler(new FormItemInitHandler() {
			@Override
			public void onInit(FormItem item) {
				
				grid = new ListGrid();
				
				if( valueSetBeforeGridWasCreated != null ) {
					Log.debug("Setting value now that grid is created");
					setValue(valueSetBeforeGridWasCreated);
				}
				
				setWidth(valueInputItem.getWidth());

				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();
							valueInputItem.clearValue();
//							Log.warn("Adding value: " + valueAndType(value));
							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);
				
				addShowValueHandler(new ShowValueHandler() {
					@Override
					public void onShowValue(ShowValueEvent event) {
						RecordList records = event.getDataValueAsRecordList();
						Log.debug("Show value event.");
						if (records != null && grid != null) {
							Log.debug("Setting grid records.");
							grid.setData(records);
						}
					}
				});
				
				Validator requiredValidator = new CustomValidator() {
					@Override
					protected boolean condition(Object value) {
						// TODO: What is value?
						Log.debug("Validator executed with value: " + value);
						return !required || isNotEmpty(getValueAsList());
					}			
				};
				
				requiredValidator.setErrorMessage("Value is required"); // TODO i18n
				
				setValidators( requiredValidator );
			}
		});
	}
	
	/**
	 * Override point where you can configure the {@link ListGridField} of the
	 * {@link ListGrid} that contains/displays the values. Does nothing by default.
	 */
	protected void configureListGridField(ListGridField listGridField) {
		// Nothing by default
	}

	@Override
	public void setWidth(int width) {
		valueInputItem.setWidth(width);
		grid.setWidth(width);
		super.setWidth(width);
	}
	
	private void addValue(T value) {
//		Log.warn("addValue() executed");
		grid.addData(newValueRecord(value));
		fireGridUpdatedEvent();
	}

	@Override
	public Object getValue() {
//		Log.warn("getValue() executed");
		if( grid != null ) {
			return recordsToList(grid.getRecords());
		} else {
			return valueSetBeforeGridWasCreated;
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<T> getValueAsList() {
		return (List<T>) getValue();
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setValue(Object value) {
//		Log.debug("setValue(Object) executed");
		if( grid != null ) {
			grid.setData( listToRecords((List<T>)value) );
			fireGridUpdatedEvent();
		} else {
			Log.debug("Saving value for when grid is created");
			valueSetBeforeGridWasCreated = value;
		}
	}
	
	/**
	 * This method must be executed every time the {@link ListGrid} is modified.
	 */
	private void fireGridUpdatedEvent() {
		
		Log.debug("Grid updated!");
		
		// TODO: grid.getDataAsRecordList() always returns the same object
		// so storeValue() thinks there is no change!
		// See: http://forums.smartclient.com/showthread.php?t=18558
//		RecordList data = grid.getDataAsRecordList();
		
		RecordList data = new RecordList(grid.getRecords());
		
        storeValue(data);
	}

	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] = newValueRecord(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} with the value inside the {@link #ATTR_VALUE} attribute.
	 */
	@SuppressWarnings("unchecked")
	private ValueRecord<T> newValueRecord(T value) {
		if( value instanceof Date ) {
			return (ValueRecord<T>) 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;
	}
}
