package com.islandpacific.gui.client;

import java.util.ArrayList;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.DSCallback;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.RecordList;
import com.smartgwt.client.data.SortSpecifier;
import com.smartgwt.client.rpc.RPCResponse;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.DragDataAction;
import com.smartgwt.client.types.FieldType;
import com.smartgwt.client.types.SortDirection;
import com.smartgwt.client.types.TitleOrientation;
import com.smartgwt.client.types.TreeModelType;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Button;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.HTMLFlow;
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.HiddenItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.grid.events.RecordClickEvent;
import com.smartgwt.client.widgets.grid.events.RecordClickHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.HStack;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.layout.VStack;
import com.smartgwt.client.widgets.tree.Tree;
import com.smartgwt.client.widgets.tree.TreeGrid;
import com.smartgwt.client.widgets.tree.TreeNode;
import com.smartgwt.client.widgets.tree.events.FolderDropEvent;
import com.smartgwt.client.widgets.tree.events.FolderDropHandler;

public class DefineStoreGroups extends Canvas {

	private final DataSource ipStoreHierarchyDS = DataSource.get("IPStoreHierarchyDS"); //$NON-NLS-1$
	private final DataSource ipStrtbHierarchyDS = DataSource.get("IPStrtbHierarchyDS"); //$NON-NLS-1$
	private final DataSource ipStrtb = DataSource.get("IPSTRTB"); //$NON-NLS-1$
	IpListGrid storeGroupGrid;
	private MyTreeGrid treeGrid1;
	private MyTreeGrid treeGrid2;
	private DynamicForm form;
	private Record[] fullHierarchy;
	
	private Button saveBtn;
	private IButton newBtn;
	private IButton deleteBtn;
	private IButton resetBtn;		

	protected void onInit() {  

		
        super.onInit();  
        
//        SC.say(IPGui.ipc.helloWorld());
        
        // classGroupGrid holds the list of defined class groups
        storeGroupGrid = new IpListGrid("DefineStoreGroups.classStoreGrid");
        storeGroupGrid.setDataSource(ipStrtb);
        storeGroupGrid.setAutoFetchData(true);
        storeGroupGrid.setWidth(300);
        storeGroupGrid.addRecordClickHandler(new RecordClickHandler() {

        	@Override
			public void onRecordClick(RecordClickEvent event) {
    			
    			// Edit the selected record in the form.
				form.editRecord(event.getRecord());
				
				// Load both trees based on the selected group
    			loadTrees();			
    		}
        });
        
        // treeGrid1 holds the classes that are NOT in the selected class group.
        treeGrid1 = new MyTreeGrid();  
        treeGrid1.setDataSource(ipStoreHierarchyDS);
        
        // treeGrid2 holds the classes that ARE in the selected class group
        treeGrid2 = new MyTreeGrid();
        treeGrid2.setDataSource(ipStrtbHierarchyDS);
        
        // A custom drop handler will transfer nodes from tree to tree
        treeGrid1.addFolderDropHandler(new FolderDropHandler() {
			@Override
			public void onFolderDrop(FolderDropEvent event) {
				transferNodes(event.getNodes(), treeGrid2, treeGrid1);
			    event.cancel();
			}
        });
        treeGrid2.addFolderDropHandler(new FolderDropHandler() {
			@Override
			public void onFolderDrop(FolderDropEvent event) {
				transferNodes(event.getNodes(), treeGrid1, treeGrid2);
			    event.cancel();
			}
        });
        
        // moveControls contains the arrow buttons to move things left and right
        VStack moveControls = new VStack(10);  
        moveControls.setWidth(30);  
        moveControls.setHeight(200);  
        moveControls.setLayoutAlign(Alignment.CENTER);  
        TransferImgButton rightArrow = new TransferImgButton(TransferImgButton.RIGHT, new ClickHandler() {  
            public void onClick(ClickEvent event) {
				transferNodes(convertRecordsToTreeNodes(treeGrid2.getSelection()), treeGrid2, treeGrid1);
            }  
        });  
        moveControls.addMember(rightArrow);  
        TransferImgButton leftArrow = new TransferImgButton(TransferImgButton.LEFT, new ClickHandler() {  
            public void onClick(ClickEvent event) {  
				transferNodes(convertRecordsToTreeNodes(treeGrid1.getSelection()), treeGrid1, treeGrid2);
            }  
        });  
        moveControls.addMember(leftArrow);  
  
        // Create a form for entering group code and name
        // Keep the full 10-character key ("U" + GroupCode) in the form as a hidden item.
        form = new DynamicForm();
        form.setDataSource(ipStrtb);
        form.setTitleOrientation(TitleOrientation.TOP);
        form.setNumCols(1);
//        form.setFixedColWidths(false);
//        form.setColWidths(50, 200);
		form.setFields(new TextItem("GroupCode"), new TextItem("SNAM"), //$NON-NLS-1$ //$NON-NLS-2$
				new HiddenItem("SKEY"), new HiddenItem("SARR")); //$NON-NLS-1$
        saveBtn = new Button(Messages.getString("DefineStoreGroups_0")); //$NON-NLS-1$
        saveBtn.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {

				// If the tree is empty, show an error message
				if (!treeGrid2.getData().hasChildren(treeGrid2.getData().getRoot())) {
					SC.say(Messages.getString("DefineStoreGroups_1")); //$NON-NLS-1$
					event.cancel();
					return;
				}
				// First save the basic form fields (creating the record if it is new)
				StringBuffer presarr = new StringBuffer(5000);

				for (int i=0; i<5000;i++)
					presarr.append('0');

				// Apply all classes in the request to the class selection array

				// Get all of the leaves (classes) in the class group tree
				Tree g2Tree = treeGrid2.getData();
				Record rootData = new Record();
				RecordList leaves = new RecordList(g2Tree.getDescendantLeaves(g2Tree.getRoot()));
								
				for (int i = 0; i < leaves.getLength(); i++) {
					Record node = leaves.get(i);
					
					// Set bit for this class
					presarr.setCharAt(Integer.parseInt(node.getAttribute("Code").toString())-1, '1');
				}						

				form.getField("SKEY").setValue("U" + form.getField("GroupCode").getDisplayValue()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				form.getField("SARR").setValue(presarr.toString());
				form.saveData(new DSCallback() {

					@Override
					public void execute(DSResponse response, Object rawData,
							DSRequest request) {
						
						// If the main record save was a failure, don't continue
						if (response.getStatus()==DSResponse.STATUS_FAILURE
								|| response.getStatus()==DSResponse.STATUS_VALIDATION_ERROR)
							return;
						else
							SC.say(Messages.getString("DefineStoreGroups_2")); //$NON-NLS-1$
												
					}
				});
			}
        });

		newBtn = new IButton(Messages.getString("DefineStoreGroups_3")); //$NON-NLS-1$
		newBtn.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				form.editNewRecord();
				loadTrees();
				form.rememberValues();
			}
		});
		deleteBtn = new IButton(Messages.getString("DefineStoreGroups_4")); //$NON-NLS-1$
		deleteBtn.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				// TODO What is the right way to handle a delete?
				if (form.isNewRecord())
					SC.warn(Messages.getString("DefineStoreGroups_5")); //$NON-NLS-1$
				else {
					SC.ask(Messages.getString("DefineStoreGroups_6"), new BooleanCallback(){ //$NON-NLS-1$
						public void execute(Boolean ok) {
							if (ok) {
								storeGroupGrid.removeSelectedData(new DSCallback() {
									public void execute(DSResponse response,
											Object rawDaSta, DSRequest request) {
										if (response.getStatus()==RPCResponse.STATUS_SUCCESS)
											form.editNewRecord();
										loadTrees();
									}
								}, null);
							}
						}
					});
				}
			}
		});
		resetBtn = new IButton(Messages.getString("DefineStoreGroups_11")); //$NON-NLS-1$
		resetBtn.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				form.reset();
				loadTrees();
			}
		});
		
		HLayout buttonLayout = new HLayout(10);
		buttonLayout.setMembers(saveBtn, newBtn, deleteBtn, resetBtn);
		buttonLayout.setAutoHeight();
		
        HTMLFlow fullHierarchyText = 
        	new HTMLFlow(Messages.getString("DefineStoreGroups_7")); //$NON-NLS-1$
        HTMLFlow groupHierarchyText = 
        	new HTMLFlow(Messages.getString("DefineStoreGroups_8")); //$NON-NLS-1$
        fullHierarchyText.setWidth(300);
        groupHierarchyText.setWidth(300);
        
        VLayout grid1Layout = new VLayout(10);
        grid1Layout.setMembers(fullHierarchyText, treeGrid1);
        VLayout grid2Layout = new VLayout(10);
        grid2Layout.setMembers(form, groupHierarchyText, treeGrid2);
        HStack treesStack = new HStack(10);
        treesStack.addMember(grid2Layout);  
        treesStack.addMember(moveControls);  
        treesStack.addMember(grid1Layout); 
        VLayout treesAndFormLayout = new VLayout(10);
        treesAndFormLayout.setMembers(buttonLayout, treesStack);
        
        // If the user is not authorized, limit capabilities to read only.
		String dataSourceId = ipStrtb.getID();
		Boolean isAuthorized = IpAuthenticationContext.isActivityAuthorized(dataSourceId);
		if (!isAuthorized) {
			form.setDisabled(true);
			storeGroupGrid.setCanEdit(false);					
			saveBtn.disable();
			newBtn.disable();
			deleteBtn.disable();
			resetBtn.disable();
			SC.say(IpAuthenticationConstants.NOT_AUTHORIZED_MESSAGE);
		}
				
        HStack mainStack = new HStack(10);  
        mainStack.setHeight100();  
        mainStack.setWidth100();
        mainStack.addMember(storeGroupGrid);
        mainStack.addMember(treesAndFormLayout);
        
        // load the trees for a new class group.
        loadTrees();
        form.editNewRecord();

        // Fill out our canvas
        this.addChild(mainStack);

    }  

	private void loadStoreGroupHierarchy() {
		
		final Tree dsTree = new Tree();
		dsTree.setModelType(TreeModelType.PARENT);
		dsTree.setReportCollisions(false);
		dsTree.setParentIdField("ParentID"); //$NON-NLS-1$
		dsTree.setIdField("ID"); //$NON-NLS-1$
		dsTree.setTitleProperty("Name"); //$NON-NLS-1$
		
		// If there is no class group selected, return with the tree empty.
		if (form.getField("GroupCode").getDisplayValue().isEmpty()) { //$NON-NLS-1$
			treeGrid2.setData(dsTree);
			treeGrid2.setSort(new SortSpecifier[]{new SortSpecifier("Code",SortDirection.ASCENDING)}); //$NON-NLS-1$
			return;
		}
			
		// Load the second tree with the selected class group
		Criteria crit = new Criteria();
		crit.addCriteria("SKEY", "U" + form.getField("GroupCode").getDisplayValue()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		DSRequest dsr = new DSRequest();
		dsr.setPrompt(Messages.getString("DefineStoreGroups_9")); //$NON-NLS-1$
        ipStrtbHierarchyDS.fetchData(crit, new DSCallback () {

			@Override
			public void execute(DSResponse response, Object rawData,
					DSRequest request) 
			{
				// Convert the returned records into tree nodes
				TreeNode[] nodes = convertRecordsToTreeNodes(response.getData());
				dsTree.setData(nodes);
				treeGrid2.setData(dsTree);
				treeGrid2.setSort(new SortSpecifier[]{new SortSpecifier("Code",SortDirection.ASCENDING)}); //$NON-NLS-1$
				
				// Get all of the classes (leaves) in the class group.
				TreeNode[] leaves = dsTree.getDescendantLeaves();
				
				// Remove the class group classes from the main hierarchy tree
				// along with any empty parents that result
				removeWithEmptyParents(leaves, treeGrid1);
			}
        }, dsr);
	}
	
	// Convenient method to set the sortByProperty using JSNI since it isn't yet exposed in SmartGWT
//	public native void sortTreeByProperty(JavaScriptObject tree, String fieldName, Boolean ascending) /*-{
//		tree.sortByProperty(fieldName, ascending);
//	}-*/;
	
	/*
	 * Remove leaves from a tree by ID and remove any parent nodes that are empty as a result
	 */
	public void removeWithEmptyParents(TreeNode[] leaves, TreeGrid grid) {
		
		for (TreeNode leaf : leaves) {
			Tree g1Data = grid.getData();
			TreeNode targetLeaf = g1Data.findById(leaf.getAttribute("ID")); //$NON-NLS-1$
			TreeNode targetParent = g1Data.getParent(targetLeaf);
			g1Data.remove(targetLeaf);
			while (targetParent != null && g1Data.getDescendants(targetParent).length == 0) {
				TreeNode newParent = g1Data.getParent(targetParent);
				g1Data.remove(targetParent);
				targetParent = newParent;
				// Could probably be more efficient -- EG: make single list of parents (no duplicates)
				// then go through that after all leaves removed?
			}
		}
	}
	
	/*
	 *  Transfer nodes from one tree to another as the are drag/dropped
	 */
	@SuppressWarnings("unchecked")
	public void transferNodes (TreeNode[] nodes, MyTreeGrid sourceTreeGrid, MyTreeGrid targetTreeGrid) {
		Tree sourceTree = sourceTreeGrid.getData();
		Tree targetTree = targetTreeGrid.getData();
		
		// For each dropped node
		for (TreeNode node : nodes) {
			
			// If it is a folder, get the leaves underneath it, otherwise it is the only leaf to move
			TreeNode[] leaves = sourceTree.isFolder(node) 
				? sourceTree.getDescendantLeaves(node) : new TreeNode[] { node };
								
			// We'll clone the leaves to move and load them into a new array					
			ArrayList dropLeaves = new ArrayList();
			ArrayList<TreeNode> openNodes = new ArrayList();
			
			for (TreeNode leaf : leaves) {
				
				// Get parents of the leaf being transferred
				TreeNode[] parents = sourceTree.getParents(leaf);
				
				// Nodes will be returned with immediate parent first
				// Iterate through from top most parent and add to the target tree if necessary
				// Last entry in array will be root - skip that
				for (int i = parents.length-2; i >= 0; i--) {
					Boolean isOpen = sourceTree.isOpen(parents[i]);
					TreeNode targetNode = targetTree.findById(parents[i].getAttribute("ID")); //$NON-NLS-1$
					if (targetNode == null) {
						
						TreeNode superParent = (i == parents.length-2) ? targetTree.getRoot() 
												: targetTree.findById(parents[i+1].getAttribute("ID")); //$NON-NLS-1$
						targetNode = cloneTreeNode(parents[i], sourceTreeGrid.getDataSource());
						targetTree.add(targetNode, superParent);

					}
					
					// If the the source node was open, open it in the target	
					if (isOpen) openNodes.add(targetNode);
				}
				
				// Save each leaf to add to the target tree.
				dropLeaves.add(cloneTreeNode(leaf, sourceTreeGrid.getDataSource()));
			}
			
			// Link all of the new leaves into the target tree
			targetTree.linkNodes((TreeNode[]) dropLeaves.toArray(new TreeNode[dropLeaves.size()]));
			
			// Open any folders that were open in the source tree
			for (TreeNode openNode : openNodes) {
				targetTree.openFolder(openNode);
			}
			// Sort the target tree
			targetTreeGrid.setSort(new SortSpecifier[]{new SortSpecifier("Code",SortDirection.ASCENDING)}); //$NON-NLS-1$
			targetTreeGrid.sort();
			
			// Now remove the dropped leaves from the source tree (along with any empty parents that result)
			removeWithEmptyParents(leaves, sourceTreeGrid);

		}
	}
	
	/* 
	 * Clone a tree node
	 */
	public TreeNode cloneTreeNode (TreeNode source, DataSource ds) {
		TreeNode target = new TreeNode();
		String[] fieldNames = ds.getFieldNames();
		for (String fieldName : fieldNames) {
			
			// Cloned field needs to be the same type
			// We only have integer and string fields so this is pretty easy
			// TODO SmartGWT should have a method to do this
			FieldType fieldType = ds.getField(fieldName).getType();
			if (fieldType.equals(FieldType.INTEGER))
				target.setAttribute(fieldName, 
						source.getAttributeAsInt(fieldName)==null 
							? 0 : source.getAttributeAsInt(fieldName));
			else
				target.setAttribute(fieldName, source.getAttribute(fieldName));
		}
		return target;
	}

	private void loadFullHierarchy(Record[] data) {
		Tree dsTree = new Tree();
		dsTree.setModelType(TreeModelType.PARENT);
		dsTree.setReportCollisions(false);
		
		dsTree.setParentIdField("ParentID"); //$NON-NLS-1$
		dsTree.setIdField("ID"); //$NON-NLS-1$
		dsTree.setTitleProperty("Name"); //$NON-NLS-1$
		
		// Convert the records in the response to client side tree nodes
		TreeNode[] nodes = convertRecordsToTreeNodes(data);
		
		// Set data for the client side tree
		dsTree.setData(nodes);
		treeGrid1.setData(dsTree);
		
		// Make the client side tree the data for the tree UI component
		treeGrid1.setSort(new SortSpecifier[]{new SortSpecifier("Code",SortDirection.ASCENDING)}); //$NON-NLS-1$
	}

	private TreeNode[] convertRecordsToTreeNodes(Record[] data) {
		TreeNode[] droppedNodes = new TreeNode[data.length];
		for (int i = 0; i < data.length; i++) {
			droppedNodes[i] = new TreeNode(data[i].getJsObj());
		}
		return droppedNodes;
	}

	/**
	 * Load both trees based on the selected class group.
	 */
	private void loadTrees() {
		
		// Use cached copy of full hierarchy from first fetch
		if (fullHierarchy!=null) {
			loadFullHierarchy(fullHierarchy);
			loadStoreGroupHierarchy();
		}
		else {	
			// Load the full merchandise hierarchy into a client side Tree
			// Then load the class group hierarchy and remove classes in it from the full hierarchy
			DSRequest dsr = new DSRequest();
			dsr.setPrompt(Messages.getString("DefineClassGroups_10")); //$NON-NLS-1$
			ipStoreHierarchyDS.fetchData(null, new DSCallback () {
	
				@Override
				public void execute(DSResponse response, Object rawData,
						DSRequest request) 
				{
					fullHierarchy = response.getData();
					loadFullHierarchy(response.getData());
					loadStoreGroupHierarchy();
			    }
			}, dsr);
		}
	}

	/*
	 * Both tree grids are virtually identical so use a helper class to construct them
	 */
    private static class MyTreeGrid extends TreeGrid {  
        public MyTreeGrid() {  
            setWidth(300);  
            setHeight100();  
            setShowHeader(false);
            setLeaveScrollbarGap(false);  
            setShowDropIcons(false);
            setDragDataAction(DragDataAction.NONE);
            setCanAcceptDroppedRecords(true);
            setCanReparentNodes(false);
            setCanDragRecordsOut(true);
            setPreventDuplicates(false);
            setAutoFetchData(false);
        }  
    }  

}