package com.smartgwt.sample.client;

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

import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.DataSourceField;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.RecordList;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.DrawEvent;
import com.smartgwt.client.widgets.events.DrawHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.FormItemHoverFormatter;
import com.smartgwt.client.widgets.form.FormItemValueFormatter;
import com.smartgwt.client.widgets.form.ValuesManager;
import com.smartgwt.client.widgets.form.fields.ComboBoxItem;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.FormItemIcon;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.form.fields.events.FormItemClickHandler;
import com.smartgwt.client.widgets.form.fields.events.FormItemIconClickEvent;
import com.smartgwt.client.widgets.form.fields.events.TitleDoubleClickEvent;
import com.smartgwt.client.widgets.form.fields.events.TitleDoubleClickHandler;
import com.smartgwt.client.widgets.grid.HeaderSpan;
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.VLayout;
import com.smartgwt.client.widgets.viewer.DetailViewer;
import com.smartgwt.client.widgets.viewer.DetailViewerField;

/**
 * Extends the standard SmartClient ListGrid to add the following additional features.
 * <ul>
 * <li>The standard Column chooser menu item in the column header context menu is 
 * replaced with a hierarchical menu with fields grouped by their menuItem attribute.
 * If no menuItem attribute is specified for a field it is shown at the top level of
 * the menu hierarchy. menuItem can include multiple levels such as
 * menuItem="Ledger>Retail>Sales>This Week"
 * </li>
 * <li>The header context menu includes options to save the ListGrid settings
 * such as column choices, column widths, sort options, etc. Save settings can be
 * restored, updated and deleted from the Grid Settings sub-menu.
 * </li>
 * <li>When the grid is grouped a new Configure Groups menu let's the user switch
 * between showing group summaries in the group header or footer, add a field
 * to the group-by list, etc.
 * </li> 
 * </ul>
 *
 */
public class IpListGrid extends ListGrid {

	public static final String _DISABLED = "_disabled";
	public IpListGrid me;
	private HeaderSpan[] headerSpans = new HeaderSpan[]{};
	
	/**
	 * Constructs a list grid component with a given component name.
	 * @param componentName
	 */
	public IpListGrid(String componentName) {

		super();
		setLeaveScrollbarGap(false);
		setAutoFitFieldsFillViewport(false);	
		setAttribute("useAdvancedFieldPicker", true, false);
		setAttribute("advancedFieldPickerThreshold", 10, false);
		me = this;
	}	

	/**
	 * Display all editable fields in the selected records in a 
	 * form and let the user edit them as a group.  Select whether
	 * or not to auto-save edits using skipSaveAll parameter. 
	 */
	public void editSelectedRecords(final Boolean skipSaveAll) {
		// Count the records that have at least one editable field.
		// Build a list of selected rows that cannot be edited so we can de-select them.
		RecordList rowsToDeselect = new RecordList();
		int rowCountToUpdate = 0;
		for (int s=0; s<getSelectedRecords().length; s++) {
			ListGridRecord selectedRecord = getSelectedRecords()[s];
			int rowNum = getRecordIndex(selectedRecord);
			boolean canEditRow = false;
			for (int colNum=0; colNum<getFields().length; colNum++) {
				/*
				 * TODO Catch exceptions until this is resolved. 
				 * http://forums.smartclient.com/showthread.php?t=28445
				 */
				try {
					if (canEditCell(rowNum, colNum)) {
						canEditRow = true;
						rowCountToUpdate += 1;
						break;
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			// Save the row to de-select if it can't be edited.
			if (!canEditRow)
				rowsToDeselect.add(selectedRecord);
		}

		// De-select any rows that cannot be edited.
		for (Record rec : rowsToDeselect.toArray()) {
			deselectRecord(rec);
		}

		// If nothing selected, say try again.
		if (getSelectedRecords().length==0) {
			if (rowsToDeselect.isEmpty())
				SC.say("Select one or more rows first.");
			else
				SC.say("All selected rows are not eligible for edit and have been de-selected.");
			return;
		}
		else if (!rowsToDeselect.isEmpty()) {
			SC.say("Some rows are not eligible for edit and have been de-selected.");
			return;
		}

		// A pop-up window
		final Window window = new Window();
		window.setHeaderIcon("[SKIN]/actions/edit.png");
		window.setKeepInParentRect(true);
		window.setIsModal(true);
		window.setShowCloseButton(true);
		window.setShowMinimizeButton(false);
		window.setShowMaximizeButton(true);
		window.setAutoCenter(true);
		window.setAutoSize(true);
		window.setCanDragResize(true);
		window.setShowHeaderIcon(true);
		window.setTitle("Edit Selected Records");

		// A form to go into the window
		final DynamicForm multiEditForm = new DynamicForm();
		multiEditForm.setWrapItemTitles(false);
		multiEditForm.setSelectOnFocus(true);
		multiEditForm.setAutoFocus(true);
		final ValuesManager multiEditFormVM = new ValuesManager();
		multiEditFormVM.setDataSource(getDataSource());
		multiEditForm.setDataSource(getDataSource());
		multiEditForm.setValuesManager(multiEditFormVM);

		// Include all editable grid fields in the form
		ArrayList<FormItem> items = new ArrayList<FormItem>();

		// We need to check each field in each of the selected rows.
		Record firstRec = getEditedRecord(getRecordIndex(getSelectedRecords()[0]));
		for (int colNum=0; colNum<getFields().length; colNum++) {
			ListGridField field = getField(colNum);
			String fieldName = field.getName();
			boolean canEdit = false;
			boolean multiValued = false;			
			for (int selIndex=0; selIndex<getSelectedRecords().length; selIndex++) {

				// If any row allows editing for this field we'll include it in the form.
				if (canEdit==false && canEditCell(getRecordIndex(getSelectedRecords()[selIndex]), colNum))
					canEdit = true;

				// If the field has multiple values among the selected rows, we'll create a
				// StaticTextItem version of the field and allow switching between
				// The two versions so you can leave the existing values unchanged.
				if (multiValued==false && selIndex>0) {
					String thisValue = getEditedRecord(getRecordIndex(getSelectedRecords()[selIndex])).getAttribute(fieldName);
					String firstValue = firstRec.getAttribute(fieldName);
					if (thisValue==null && firstValue==null)
						continue;
					if ((thisValue==null && firstValue!=null)
							|| (thisValue!=null && firstValue==null)
							|| (!thisValue.equals(firstValue)))
						multiValued = true;
				}

				// Once we determine both conditions we can stop looking.
				if (canEdit && multiValued)		
					break;
			}
			/*
			 *  Add the field to the form. We use the same field name
			 *  and title as the grid, in case the title has been overridden
			 *  from the data source title. We also carry over any value 
			 *  map in case that has been explicitly set instead of using
			 *  the one from the ds.xml.
			 */
			if (canEdit) {
				FormItem item = cloneListGridField(field);	
				items.add(item);

				// Add a static twin if the field has multiple values
				if (multiValued) {
					items.add(getStaticTwin(item, getDataSource()));
				}
			}
		}

		// If there are no editable rows, tell the user and exit.
		if (rowCountToUpdate==0) {
			SC.warn("None of the rows you selected can be changed.");
			return;
		}

		// Tell the user how many rows they'll update.
		Label blurb = new Label();
		blurb.setWrap(true);
		blurb.setWidth(300);
		blurb.setAutoHeight();
		blurb.setContents("Enter new values and click Save to change the "
				+ rowCountToUpdate + " rows that can be changed.");

		FormItem[] formItems = new FormItem[items.size()];
		formItems = items.toArray(formItems);
		adjustFormLayout(multiEditForm, formItems);
		multiEditForm.setFields(formItems);
		multiEditFormVM.editRecord(getEditedRecord(getRecordIndex(getSelectedRecords()[0])));

		IButton applyBtn = new IButton("Save");
		applyBtn.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {

				// Don't continue if errors.
				if (!multiEditFormVM.validate())
					return;

				// For each grid row in the selection
				boolean allRowsValid = true;
				for (ListGridRecord rec : getSelectedRecords()) {
					int recordIndex = getRecordIndex(rec);

					// For each visible form item that is not a StaticTextItem
					for (FormItem formItem : multiEditForm.getFields()) {
						if (formItem.getVisible() && !formItem.getName().endsWith(_DISABLED)) {

							// Set the edit value for the grid record field to the form item value
							// but only if the field is editable in this record.
							if (canEditCell(recordIndex, getFieldNum(formItem.getName()))) {
								Object value = formItem.getValue();
								if (value==null)
									setEditValue(recordIndex, formItem.getName(), (String)null);
								else if ("date".equals(formItem.getType()))
									setEditValue(recordIndex, formItem.getName(), (Date)value);
								else 
									setEditValue(recordIndex, formItem.getName(), value.toString());
							}
						}
					}

					// See if this updated row passes validations
					if (!validateRow(recordIndex))
						allRowsValid = false;
				}
				// If all rows are valid save edits and close window.
				if (allRowsValid) {
					if (!skipSaveAll) 
						saveAllEdits();	//PRIMARK-831 - prevent grid from being refreshed and losing position.
				} else {
					SC.warn("The new values you entered are not valid for all affected rows. Edit and correct rows individually.");
				}
				window.destroy();
			}

		});
		IButton cancelBtn = new IButton("Cancel");
		cancelBtn.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				window.destroy();
			}
		});
		IButton resetBtn = new IButton("Reset");
		resetBtn.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				multiEditForm.reset();
			}
		});
		HLayout btnLayout = new HLayout(10);
		btnLayout.setMembers(applyBtn, cancelBtn, resetBtn);
		final VLayout winLayout = new VLayout(10);
		winLayout.setMargin(10);
		winLayout.setMembers(blurb, multiEditForm, btnLayout);
		window.addItem(winLayout);	
		window.show();
	}

	/**
	 * Adjust the form columns based on number of form items.
	 * @param form The form
	 * @param formItems The proposed array of form items,
	 */
	public void adjustFormLayout(DynamicForm form,
			FormItem[] formItems) {
		if (formItems.length>14) {
			form.setNumCols(4);
//			form.setColWidths(125,"*",125,"*");
		} else {
			form.setNumCols(2);
//			form.setColWidths(125,"*");
		}
	}
	/**
	 * Show a DetailViewer with all visible grid fields in a pop up window. 
	 */
	public void viewSelectedRecords() {

		// A pop-up window
		final Window window = new Window();
		window.setHeaderIcon("customimages/view-icon.png");
		window.setKeepInParentRect(true);
		window.setIsModal(true);
		window.setShowCloseButton(true);
		window.setShowMinimizeButton(false);
		window.setShowMaximizeButton(true);
		window.setAutoCenter(true);
		window.setAutoSize(true);
		window.setCanDragResize(true);
		window.setShowHeaderIcon(true);
		window.setTitle("View Selected Records");

		// A form to go into the window
		final DetailViewer multiViewer = new DetailViewer();
		multiViewer.setWidth100();
		multiViewer.setRecordsPerBlock(getSelectedRecords().length>6 ? 6 : getSelectedRecords().length);
		multiViewer.setWrapLabel(false);
//		multiViewer.setWrapValues(false);
		multiViewer.setDataSource(getDataSource());
		multiViewer.setFields(getDetailViewFieldsFromGridFields());
		multiViewer.setData(getSelectedRecords());

		IButton cancelBtn = new IButton("Close Window");
		cancelBtn.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				window.destroy();
			}
		});

		HLayout btnLayout = new HLayout(10);
		btnLayout.setMembers(cancelBtn);
		final VLayout winLayout = new VLayout(10);
		winLayout.setWidth100();
		winLayout.setHeight100();
		winLayout.setMargin(10);
		winLayout.setMembers(multiViewer, btnLayout);
		window.addItem(winLayout);	
		window.addDrawHandler(new DrawHandler() {

			@Override
			public void onDraw(DrawEvent event) {
				int viewportHeight = com.google.gwt.user.client.Window.getClientHeight();
				int viewportWidth = com.google.gwt.user.client.Window.getClientWidth();
				Window theWindow = (Window)event.getSource();
				if (theWindow.getVisibleHeight()>viewportHeight
						|| theWindow.getVisibleWidth()>viewportWidth) {
					theWindow.setAutoSize(false);
					int newHeight = theWindow.getVisibleHeight()>viewportHeight ? viewportHeight : theWindow.getVisibleHeight();
					int newWidth = theWindow.getVisibleWidth()>viewportWidth ? viewportWidth : theWindow.getVisibleWidth();
					theWindow.resizeTo(newWidth, newHeight);
					winLayout.markForRedraw();
				}
			}
		});
		window.show();
	}

	private DetailViewerField[] getDetailViewFieldsFromGridFields() {
		List<DetailViewerField> fields = new ArrayList<DetailViewerField>();
		for (ListGridField field : getFields()) {
			// Skip framework created grid fields (select checkbox, etc.)
			if (field.getName().startsWith("_")
					|| field.getName().startsWith("$"))
				continue;
			DetailViewerField detailViewerField = new DetailViewerField(field.getName());
			fields.add(detailViewerField);
		}
		return fields.toArray(new DetailViewerField[fields.size()]);
	}

	/**
	 * Create a FormItem clone of a ListGridField. All attributes
	 * are copied except for width and alignment.
	 * @param field The ListGridField
	 * @return The cloned FormItem
	 */
	public FormItem cloneListGridField(ListGridField field) {
		
		/*
		 * Create a form item based on the ListGridField
		 * editor type. SelectItems and ComboboxItems
		 */
		String editorType = field.getAttribute("editorType");
		FormItem item;
		if ("SelectItem".equalsIgnoreCase(editorType))
			item = new SelectItem(field.getName());
		else if ("ComboBoxItem".equalsIgnoreCase(editorType))
			item = new ComboBoxItem(field.getName());
		else
			item = new FormItem(field.getName());
		
		/*
		 * Hacked this in to work around a problem I was having with CanvasItems in a grid.  See HandoverDateItem and 
		 * FiscalDateItem, specifically.  These each have inner TextItems that need their widths set to "*", so that they're sized
		 * properly in the grid.  This then causes the TextItem to be rendered incorrectly here.  Just set the CanvasItem's width
		 * to 150 explicitly (150 is the default anyway) to work around it.   
		 */
		if ("CanvasItem".equalsIgnoreCase(editorType)) {
			item.setWidth(150);
		}
		
		/*
		 * Copy over all other attributes except for:
		 *   width & alignment- those should use the defaults when put into a form.
		 *   showIf - if the column is in the grid, it should appear on the form.
		 *   $-prefixed attributes - unsure what these are, so let's be safe and not copy them.
		 */
		for (String attribute : field.getAttributes()) {
			if (!"width".equals(attribute)
					&& !"align".equals(attribute)
					&& !"showIf".equals(attribute)
					&& attribute.charAt(0) != '$')
					item.setAttribute(attribute, field.getAttributeAsObject(attribute));
		}
		// Prepend any grid column header spans onto the field title
		if (field.getTitle()!=null) {
			String title = "";
			foreachspan: for (HeaderSpan span : getHeaderSpans()) {
				for (String name : span.getFields()) {
					if (name.equals(field.getName())) {
						title = title.concat("[").concat(span.getTitle())
								.concat("] ");
						break foreachspan;
					}
				}
			}
			item.setTitle(title.concat(field.getTitle()));
		}
		return item;
	}

	@Override
	public void setHeaderSpans(HeaderSpan... headerSpans) throws IllegalStateException {
		this.headerSpans = headerSpans;
		super.setHeaderSpans(headerSpans);
	}
	public HeaderSpan[] getHeaderSpans() {
		return headerSpans;
	}
	
	/**
	 * Create a StaticTextItem clone of a FormItem. The twin
	 * has a double-click handler that will hide it and show
	 * show the original item instead. An icon it added to the 
	 * original item to allow switching back to the twin.
	 * @param item The item to clone.
	 * @return The StaticTextItem twin.
	 */
	public static FormItem getStaticTwin(final FormItem item, DataSource ds) {

		// The twin's title will come from the real item's title if it has one.
		// The field will have a title if it was explicitly set when the field was 
		// created (like some of the freight and duty field titles that are overridden).
		// If not, it will come from the underlying datasource field (if it has a title).
		StaticTextItem disabledItem = new StaticTextItem(item.getName()+_DISABLED);
		disabledItem.setWrap(false);
		DataSourceField dsField = ds.getField(item.getName());
		if ((item.getTitle()!=null))
			disabledItem.setTitle(item.getTitle());
		else if (dsField!=null)
			disabledItem.setTitle(dsField.getTitle());

		// The disabled twin gets it value from the regular item
		disabledItem.setValue(item.getValue());
		disabledItem.setValueFormatter(new FormItemValueFormatter () {
			@Override
			public String formatValue(Object value, Record record,
					DynamicForm form, FormItem item) {
				FormItem regularItem = form.getItem(item.getName().substring(0, item.getName().indexOf(_DISABLED)));
				return regularItem.getDisplayValue();
			}	
		});

		// An icon click on the real item hides it and shows the disabled twin.
		// The value is reset to it's original value.
		final FormItemIcon returnIcon = new FormItemIcon();
		returnIcon.setPrompt("Click to leave this field unchanged.");
		returnIcon.setSrc("customimages/return.gif");
		returnIcon.addFormItemClickHandler(new FormItemClickHandler() {

			@Override
			public void onFormItemClick(FormItemIconClickEvent event) {
				if(event.getForm().getOldValues().get(event.getItem().getName())!=null)
					event.getItem().setValue(event.getForm().getOldValues().get(event.getItem().getName()).toString());
				FormItem twin = event.getForm().getItem(event.getItem().getName()+_DISABLED);
				event.getItem().hide();
				twin.show();
				// If there are dependentFields revert those too.
				String dependentFields = event.getItem().getAttribute("dependentFields");
				if (dependentFields!=null) {
					for (String dependentField : dependentFields.split(",")) {
						dependentField = dependentField.trim();
						FormItem dependentReal = event.getForm().getField(dependentField);
						FormItem dependentTwin = event.getForm().getField(dependentField+_DISABLED);
						dependentReal.setValue(event.getForm().getOldValues().get(dependentField).toString());
						if (dependentTwin!=null) {
							dependentTwin.show();
							dependentReal.hide();
						}
					}
				}
			}
 
		});
		returnIcon.setPrompt("Click to leave this field unchanged.");
		item.setIcons(returnIcon);
		item.setShowIcons(false);
		item.setVisible(false);
		item.setAttribute("wrap", false);

		// Set appropriate help text for real item..
		item.setItemHoverFormatter(new FormItemHoverFormatter(){
			@Override
			public String getHoverHTML(FormItem item, DynamicForm form) {
				String hoverText =  "There is more than one " + item.getTitle() + ".";
				return hoverText;
			}
		});

		// Double-clicking on the disabled item hides it and shows the real item.
		disabledItem.addDoubleClickHandler(new com.smartgwt.client.widgets.form.fields.events.DoubleClickHandler() {
			@Override
			public void onDoubleClick(com.smartgwt.client.widgets.form.fields.events.DoubleClickEvent event) {
				FormItem theFormItem = event.getItem();
				showRealItem(theFormItem);
			}
		});
		
		// Double-clicking on the disabled item label does the same thing.
		disabledItem.addTitleDoubleClickHandler(new TitleDoubleClickHandler() {
			
			@Override
			public void onTitleDoubleClick(TitleDoubleClickEvent event) {
				FormItem theFormItem = event.getItem();
				showRealItem(theFormItem);
			}
		});	

		// Set appropriate help text for disabled twin item..
		disabledItem.setItemHoverFormatter(new FormItemHoverFormatter(){
			@Override
			public String getHoverHTML(FormItem item, DynamicForm form) {
				String realName = item.getName().substring(0, item.getName().indexOf(_DISABLED));
				String hoverText =  "There is more than one " + item.getTitle() + " for this item.";
				FormItem realItem = form.getItem(realName);
				String type = realItem.getType();
				if (type!=null && !type.equalsIgnoreCase("StaticTextItem"))
					hoverText +=  " Double-click to change.";
				return hoverText;
			}
		});

		// Return the disabled item.
		return disabledItem;

	}
	/**
	 * Show the real item given it's static twin.
	 * @param disabledTwin The static text item shown when a field in 
	 * the bulk edit form is "disabled" due to multiple values.
	 */
	public static void showRealItem(FormItem disabledTwin) {
		String realItemName = disabledTwin.getName().substring(0, disabledTwin.getName().indexOf(_DISABLED));
		DynamicForm form = disabledTwin.getForm();
		FormItem realItem = form.getItem(realItemName);
		form.focusInItem(realItem);
		disabledTwin.hide();
		realItem.setShowIcons(true);
		if (!realItem.isVisible())
			form.clearFieldErrors(realItemName, true);
		realItem.show();
		
		// If there are dependentFields show those too.
		String dependentFields = realItem.getAttribute("dependentFields");
		if (dependentFields!=null) {
			for (String dependentFieldName : dependentFields.split(",")) {
				dependentFieldName = dependentFieldName.trim();
				FormItem dependentItemDisabled = form.getField(dependentFieldName+_DISABLED);
				if (dependentItemDisabled!=null) {
					FormItem dependentItemReal = form.getItem(dependentFieldName);
					form.focusInItem(dependentItemReal);
					dependentItemDisabled.hide();
					dependentItemReal.setShowIcons(true);
					if (!dependentItemReal.isVisible())
						form.clearFieldErrors(dependentFieldName, true);
					dependentItemReal.show();	
				}
			}
		}
	}


	/**
	 * Expose the grid ListGrid.canEditCell as a public method.
	 * @param rowNum The row number to check
	 * @param colNum The column number to check
	 * @return True if the cell can be edited, otherwise false.
	 */
	public boolean canEditGridCell(int rowNum, int colNum) {
		return canEditCell(rowNum, colNum);
	}

}
