import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import com.google.gwt.cell.client.AbstractEditableCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.IsEditor;
import com.google.gwt.editor.client.adapters.EditorSource;
import com.google.gwt.editor.client.adapters.ListEditor;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.Header;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ProvidesKey;

/**
 * <p>
 * An attempt to implement a generic CellTable that plays nice with the Editor
 * framework, and edit the contents of each row. If you do not intend to edit
 * the contents of each row, this is probably not for you.
 * </p>
 * <p>
 * When using this class, <b>do not edit its row data directly!</b>. The row
 * data comes from the Editor framework. If you want to add or remove rows, you
 * have to go throw the editor framework. For that reason, this class provides a
 * getEditableList() method which returns the instance you should manipulate.
 * The instance itself cannot be overwritten, but you may of course clear it
 * entirely and redefine all of its elements. If you insist in editing row data
 * directly, the final behavior cannot be predicted - and probably will not be
 * good.
 * </p>
 * 
 * @author tiago
 * 
 * @param <T>
 */
public class EditableCellTable<T> extends CellTable<T> implements IsEditor<ListEditor<T, ? extends EditableCellTable.SubEditor<T>>> {

	private static final Logger LOGGER = Logger.getLogger(EditableCellTable.class.getName());
	
	/**
	 * The parent class for the editors that will edit a particular row in the
	 * CellTable.
	 * 
	 * @author tiago
	 * 
	 * @param <T>
	 */
	public static class SubEditor<T> extends ListEditorDataProvider.SubEditor<T> {

		private EditableCellTable<T> cellTable;
		
		protected SubEditor(EditableCellTable<T> cellTable, int indexInList) {
			super(cellTable.editor, indexInList);
			this.cellTable = cellTable;
		}

		/**
		 * Defines the widget which will edit a property of this editor. We must
		 * know the index of the column where it will be displayed.
		 * 
		 * @param <P>
		 * @param <E>
		 * @param colIndex
		 * @param editor
		 */
		protected <P, E extends Editor<P> & TakesValue<P>> void defineEditorForColumn(int colIndex, E editor) {
			Column<T,?> col = cellTable.getColumn(colIndex);
			if (!(col instanceof EditableCellTable.EditableColumn)) {
				throw new IllegalArgumentException("The column in the given index is not editable.");
			}
			
			@SuppressWarnings("unchecked")
			EditableColumn<T,P,E,?> editableColumn = (EditableColumn<T,P,E,?>) col;
			editableColumn.addEditor(getIndexInList(), editor);
		}		
	}

	/**
	 * An editable column in this CellTable.
	 * 
	 * @author tiago
	 * 
	 * @param <T>
	 *            The row type (same type of the CellTable, has to be redefined
	 *            because this has to be a static class in order to be extended
	 *            by outside code)
	 * @param <P>
	 *            The property type of the column.
	 * @param <E>
	 *            The editor which will edit this column's property.
	 * @param <C>
	 *            The cell type for this column. Must be an
	 *            {@link AbstractEditableCell} because the whole purpose here is
	 *            to edit stuff.
	 */
	public static class EditableColumn<T, P, E extends TakesValue<P> & Editor<P>, C extends AbstractEditableCell<P, ?>> 
	extends Column<T, P> {

		private ArrayList<E> propertyEditors = new ArrayList<E>();
		private EditableCellTable<T> cellTable;

		/**
		 * Builds an editable column.
		 * 
		 * @param cell
		 */
		public EditableColumn(C cell) {
			super(cell);
			setFieldUpdater(new FieldUpdater<T, P>() {
				@Override
				public void update(int rowIndex, T row, P value) {
					E propertyEditor = propertyEditors.get(rowIndex);
					if (propertyEditor == null) {
						LOGGER.warning("A value with no editor set was updated! " +
								"This update will not be handled by the Editor framework.");
					} else {
						propertyEditor.setValue(value);
					}
				}
			});
		}

		/**
		 * Private in order to hide code complexity from the user.
		 * 
		 * @param cellTable
		 */
		private void setCellTable(EditableCellTable<T> cellTable) {
			this.cellTable = cellTable;
		}

		@Override
		public P getValue(T row) {
			/*
			 * Suboptimal performance: O(n) operation on list size to look up
			 * the index of the given row. Shouldn't be that serious since this
			 * list is supposed to be in memory anyway. Trying to cache a map
			 * and keep it up to date would have been complicated and this whole
			 * code is already complicated enough.
			 */
			int rowIndex = cellTable.getEditableList().indexOf(row);
			E propertyEditor = propertyEditors.get(rowIndex);
			return propertyEditor == null ? null : propertyEditor.getValue();
		}
		
		public void addEditor(int rowIndex, E editor) {
			while (rowIndex >= propertyEditors.size()) {
				propertyEditors.add(null);
			}
			
			propertyEditors.set(rowIndex, editor);
		}
	}
	
	/* ========== A few potentially useful subclasses of EditableColumn ===============*/

//	/**
//	 * An EditableColumn which uses a EditTextCell for rendering and a
//	 * SimpleEditor<String> for editing. Only applies to String
//	 * column.
//	 * 
//	 * @author tiago
//	 * 
//	 * @param <T>
//	 */
//	public static class SimpleEditTextColumn<T> extends EditableColumn<T, String, SimpleEditor<String>, EditTextCell> {
//		public SimpleEditTextColumn() {
//			super(new EditTextCell());
//		}
//	}
//
//	/**
//	 * An EditableColumn which uses CheckboxCell for rendering and a
//	 * SimpleEditor<Boolean> for editing. Only applies to Boolean columns.
//	 * 
//	 * @author tiago
//	 * 
//	 * @param <T>
//	 */
//	public static class SimpleCheckboxEditableColumn<T> extends EditableColumn<T, Boolean, SimpleEditor<Boolean>, CheckboxCell> {
//		public SimpleCheckboxEditableColumn() {
//			super(new CheckboxCell());
//		}
//	}
//
//	/**
//	 * An EditableColumn which uses SelectionCell for rendering and a
//	 * SimpleEditor<String> for editing. Only applies to Boolean columns.
//	 * 
//	 * @author tiago
//	 * 
//	 * @param <T>
//	 */
//	public static class SimpleSelectionEditableColumn<T> extends EditableColumn<T, String, SimpleEditor<String>, SelectionCell> {
//		public SimpleSelectionEditableColumn(List<String> options) {
//			super(new SelectionCell(options));
//		}
//	}
//
//	/**
//	 * An EditableColumn which uses EnumSelectionCell for rendering and a
//	 * SimpleEditor<E> for editing. Only applies to enum columns.
//	 * 
//	 * @author tiago
//	 * 
//	 * @param <T>
//	 * @param <E>
//	 */
//	public static class SimpleEnumSelectionEditableColumn<T, E extends Enum<E>> extends EditableColumn<T, E, SimpleEditor<E>, EnumSelectionCell<E>> {
//		public SimpleEnumSelectionEditableColumn(Class<E> enumClass, ConstantsWithLookup translations) {
//			super(new EnumSelectionCell<E>(enumClass, translations));
//		}
//	}
	
	
	/* ========== End of potentially useful subclasses of EditableColumn ===============*/
	
	
	private ListEditorDataProvider<T, ? extends SubEditor<T>> editor;
	
	/*=========== Adapted constructors from superclass =================*/
	/*
	 * These constructors assure that the user provides an editor source.
	 */
	
	public EditableCellTable(EditorSource<? extends SubEditor<T>> editorSource) {
		initEditor(editorSource);
	}

	public EditableCellTable(EditorSource<? extends SubEditor<T>> editorSource, int pageSize, ProvidesKey<T> keyProvider) {
		super(pageSize, keyProvider);
		initEditor(editorSource);
	}

	public EditableCellTable(EditorSource<? extends SubEditor<T>> editorSource, int pageSize, CellTable.Resources resources, 
			ProvidesKey<T> keyProvider, Widget loadingIndicator) {
		super(pageSize, resources, keyProvider, loadingIndicator);
		initEditor(editorSource);
	}

	public EditableCellTable(EditorSource<? extends SubEditor<T>> editorSource, int pageSize, CellTable.Resources resources, 
			ProvidesKey<T> keyProvider) {
		super(pageSize, resources, keyProvider);
		initEditor(editorSource);
	}

	public EditableCellTable(EditorSource<? extends SubEditor<T>> editorSource, int pageSize, CellTable.Resources resources) {
		super(pageSize, resources);
		initEditor(editorSource);
	}

	public EditableCellTable(EditorSource<? extends SubEditor<T>> editorSource, int pageSize) {
		super(pageSize);
		initEditor(editorSource);
	}

	public EditableCellTable(EditorSource<? extends SubEditor<T>> editorSource, ProvidesKey<T> keyProvider) {
		super(keyProvider);
		initEditor(editorSource);
	}

	private <E extends SubEditor<T>> void initEditor(EditorSource<E> editorSource) {
		editor = ListEditorDataProvider.of(editorSource);
	}

	/*=========== End of adapted constructors from superclass =================*/
	

	@Override
	public ListEditor<T, ? extends SubEditor<T>> asEditor() {
		return editor.asEditor();
	}

	/**
	 * Method which returns an editable copy of this class' row data. If you
	 * want to add or remove elements of this list, always go through this
	 * method. The list instance cannot be changed, but you may change all of
	 * its content.
	 * 
	 * @return
	 */
	public List<T> getEditableList() {
		return editor.getList();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void insertColumn(int beforeIndex, Column<T, ?> col, Header<?> header, Header<?> footer) {
		/*
		 * We override this method to make sure the column is always linked, in
		 * case of EditableColumns
		 */
		if (col instanceof EditableColumn) {
			((EditableColumn<T,?,?,?>)col).setCellTable(this);
		}
		super.insertColumn(beforeIndex, col, header, footer);
	}
	
	/*============= Utility methods to make calling code shorter   =====================*/

//	/**
//	 * Adds a SimpleEditTextColumn<T> with the given header.
//	 * 
//	 * @param header
//	 */
//	public void addEditTextColumn(String header) {
//		addColumn(new SimpleEditTextColumn<T>(), header);
//	}
//
//	/**
//	 * Adds a {@link SimpleCheckboxEditableColumn} with the given header.
//	 * 
//	 * @param header
//	 */
//	public void addCheckboxColumn(String header) {
//		addColumn(new SimpleCheckboxEditableColumn<T>(), header);
//	}
//
//	/**
//	 * Adds a {@link SimpleEnumSelectionEditableColumn} with the given
//	 * attributes.
//	 * 
//	 * @param <E>
//	 *            The Enum class.
//	 * @param header
//	 *            The column header.
//	 * @param enumClass
//	 *            The instance of the enum class.
//	 * @param translations
//	 *            The internationalization class that will be used to translate
//	 *            the enum values.
//	 */
//	public <E extends Enum<E>> void addEnumColumn(String header, Class<E> enumClass, ConstantsWithLookup translations) {
//		addColumn(new SimpleEnumSelectionEditableColumn<T,E>(enumClass, translations));
//	}
	
	/*============= Utility methods to make calling code shorter   =====================*/
	
}
