package com.islandpacific.gui.client;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.DataSourceField;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.types.Autofit;
import com.smartgwt.client.types.FieldType;
import com.smartgwt.client.types.RowEndEditAction;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.CellSavedEvent;
import com.smartgwt.client.widgets.grid.events.CellSavedHandler;

/**
 * @author jfisher
 * Build a two-dimensional grid from a list of records.
 */
public class IpListGrid2D extends Canvas {
	
	private ListGrid grid;
	private DataSource gridDS;
	private	DataSourceField myColSortField;
	private	DataSourceField myRowField;
	private	DataSourceField myColField;
	private	DataSourceField myCellField;
	
	/**
	 * Construct a two-dimensional grid from an array of Records
	 * @param records - Records containing, at a minimum, row/column and cell fields. It can contain others.
	 * @param rowField - Field to use as unique key to a row
	 * @param colField - Field to use as unique key to a column
	 * @param cellField - Field to display in cells
	 * @param colSortField - Field to use for sorting columns left to right. If null, colField will be used.
	 */
	@SuppressWarnings("unchecked")
	IpListGrid2D(Record[] records, 
			DataSourceField rowField, 
			DataSourceField colField,
			DataSourceField cellField,
			DataSourceField colSortField) {
		
		// Column sort field defaults to column heading
		// Save all constructor arguments in private member variables
		if (colSortField==null) myColSortField = colField;
		else myColSortField = colSortField;
		myRowField = rowField;
		myColField = colField;
		myCellField = cellField;
		
		// Build distinct list of column and row attribute values
		// The map will contain a representative Record for that 
		// column or row in case we want to refer to other attributes
		// that should be the same for all records in that column or row.
		// It is the responsibility of the caller to ensure that "records" does not
		// include duplicate values for any rowField/colField values combination.
		HashMap<String, Record> colMap = new HashMap<String, Record>();
		HashMap<String, Record> rowMap = new HashMap<String, Record>();
		for (Record record : records) {
			colMap.put(record.getAttribute(myColField.getName()), record);
			rowMap.put(record.getAttribute(myRowField.getName()), record);
		}
		
		// Load columns into a list
		ArrayList<Record> colList = new ArrayList<Record>();
		colList.addAll(colMap.values());
		
		// sort the list by the colSortAttr interpreting it as a colSortType
		Collections.sort(colList, new Comparator<Record>() {
			@Override
			public int compare(Record r1, Record r2) {
				if (myColSortField.getType().equals(FieldType.INTEGER))
					return r1.getAttributeAsInt(myColSortField.getName()) 
						- r2.getAttributeAsInt(myColSortField.getName());
				else if (myColSortField.getType().equals(FieldType.DATE))
					return r1.getAttributeAsDate(myColSortField.getName())
						.compareTo(r2.getAttributeAsDate(myColSortField.getName()));
				else 
					return r1.getAttribute(myColSortField.getName())
						.compareTo(r2.getAttribute(myColSortField.getName()));
			}
		});		
		
		// Create a map where each entry is a row and the value is another map of colKey/cellValue 
		// We use a HashMap so we end up with a distinct list of values for the rowField.
		HashMap<String, HashMap<String, Object>> rowCellMap 
			= new HashMap<String, HashMap<String, Object>>();
		for (Record record : records) {
			HashMap<String, Object> cellMap = (HashMap<String, Object>) 
				rowCellMap.get(record.getAttribute(myRowField.getName()));
			if (cellMap==null) cellMap = new HashMap<String, Object>();
			cellMap.put(record.getAttribute(myColField.getName()), 
					record.getAttribute(myCellField.getName()));
			rowCellMap.put(record.getAttribute(myRowField.getName()), cellMap);
		}
		
		// Create a client-only data source with a field for the rowKey and a rowTotal.
		gridDS = new DataSource();
		gridDS.setClientOnly(true);
		DataSourceField rowKey = new DataSourceField(myRowField.getName(),
				myRowField.getType(), myRowField.getTitle());
		rowKey.setPrimaryKey(true);
		rowKey.setCanEdit(false);
		gridDS.addField(rowKey); 
		DataSourceField rowTotal = new DataSourceField(myRowField.getName()+"_total",
				myCellField.getType(), myRowField.getTitle() + " Total");
		rowTotal.setCanEdit(false);
		gridDS.addField(rowTotal); 
		
		// Add two fields for each column (in sorted order). The first of each column
		// field pair is the field to be edited (the cellField). The second is a flag that
		// lets us know whether the corresponding cellField should be editable (ie. there 
		// is a record in the original Record list corresponding to that cell.
		for (int i=0; i<colList.size(); i++) {
			Record rec = colList.get(i);
			DataSourceField field = new DataSourceField();
			field.setName(rec.getAttribute(myColField.getName()));
			field.setType(myCellField.getType());
			gridDS.addField(field);
			DataSourceField fieldEdit = new DataSourceField();
			fieldEdit.setName(field.getName() + "_edit");
			fieldEdit.setType(FieldType.BOOLEAN);
			fieldEdit.setHidden(true);
			fieldEdit.setCanEdit(false);
			gridDS.addField(fieldEdit);
		}
		// Fill the DataSource with a Record for each row
		Iterator rowIt = rowMap.entrySet().iterator();
		Record[] dataSet = new Record[rowMap.size()];
		int r = 0;
		while (rowIt.hasNext()) {
			Map.Entry<String, Record> rowPairs = (Map.Entry<String, Record>)rowIt.next();
			ListGridRecord gridRec = new ListGridRecord();
			Record rowRec = rowPairs.getValue();
			Integer theRowTotal = new Integer(0);
			gridRec.setAttribute(myRowField.getName(), rowRec.getAttribute(myRowField.getName()));
			HashMap<String, Object> cellMap = rowCellMap.get(rowRec.getAttribute(myRowField.getName()));
			for (Record colRec : colList) {
				String colId = colRec.getAttributeAsString(myColField.getName());
				gridRec.setAttribute(colId, cellMap.get(colId));
				if (cellMap.get(colId) != null) {
					gridRec.setAttribute(colId + "_edit", true);
					theRowTotal += Integer.parseInt(cellMap.get(colId).toString());
				}
				else gridRec.setAttribute(colId + "_edit", false);
			}
			gridRec.setAttribute(myRowField.getName()+"_total", theRowTotal);
			dataSet[r++] = gridRec;
		}
		gridDS.setTestData(dataSet);
		
		// Create the grid from the new data source
		grid = new ListGrid() {
			/* 
			 * Return true if the cell can be edited, false if not.
			 */
			@Override
			protected boolean canEditCell(int rowNum, int colNum) {
				// Get the grid record for this row and the field name for the column
				// There will be a field with the same name plus a suffix
				// of "_edit" that will containg true or false
				// to tell whether editing should be allowed
				ListGridRecord record = grid.getRecord(rowNum);
				String fieldName = grid.getFields()[colNum].getName();
//				SC.logWarn("canEdit rowNum="+rowNum+" colNum="+colNum+" returning "+record.getAttributeAsBoolean(fieldName + "_edit"));
				return record.getAttributeAsBoolean(fieldName + "_edit");		
			}
		};
		grid.addCellSavedHandler(new CellSavedHandler() {

			@Override
			public void onCellSaved(CellSavedEvent event) {
				String rowTotalFieldName = myRowField.getName()+"_total";
				Integer rowTotal = event.getRecord().getAttributeAsInt(rowTotalFieldName);
				rowTotal = rowTotal + Integer.parseInt(event.getNewValue().toString()) 
					- Integer.parseInt(event.getOldValue().toString());
				event.getRecord().setAttribute(rowTotalFieldName, rowTotal);
			}
			
		});
		grid.setCanEdit(true);
		grid.setEditOnFocus(true);
//		grid.setEnterKeyEditAction(EnterKeyEditAction.NEXTCELL);
		grid.setRowEndEditAction(RowEndEditAction.NEXT);
		grid.setSaveByCell(true);
		grid.setAutoFitData(Autofit.BOTH);
		grid.setDataSource(gridDS);
		grid.setShowGridSummary(true);
		grid.setCanDragResize(true);
		grid.setWidth100();
//		grid.setHeight100();
		grid.setShowHeaderContextMenu(false);
		grid.setSortField(rowField.getName());
		grid.setAutoFetchData(true);
		addChild(grid);
	}
}
