/*
 * @(#)CheckBoxTreeDemo.java 9/10/2005
 *
 * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
 */

import com.jidesoft.icons.IconsFactory;
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.swing.*;
import com.jidesoft.tree.TreeUtils;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.*;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;
import java.util.zip.GZIPInputStream;

/**
 * Demoed Component: {@link com.jidesoft.swing.CheckBoxTree} <br> Required jar files: jide-common.jar, jide-grids.jar
 * <br> Required L&F: any L&F
 */
public class CheckBoxTreeDemo extends AbstractDemo 
	implements TreeSelectionListener {
	
	Map<String,Vector<String>> listItemsMap;
	protected Map<String,Vector<String>> selectedItemsMap;
	
	
    private CheckBoxTree _tree;

    private static final String SINGLE_SELECTION = "Single Selection";
    private static final String CONTIGUOUS_SELECTION = "Contiguous Selection";
    private static final String DISCONTIGUOUS_SELECTION = "Discontiguous Selection";

    public CheckBoxTreeDemo() {
    }

    public String getName() {
        return "CheckBoxTree Demo";
    }

    public String getProduct() {
        return PRODUCT_NAME_COMMON;
    }

    @Override
    public String getDescription() {
        return "This is a demo of CheckBoxTree. \n" +
                "\n" +
                "Demoed classes:\n" +
                "com.jidesoft.swing.CheckBoxTree";
    }

    @Override
    public Component getOptionsPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new JideBoxLayout(panel, BoxLayout.Y_AXIS, 3));

        final JButton selectAll = new JButton(new AbstractAction("Select All") {
            public void actionPerformed(ActionEvent e) {
                if (_tree.getCheckBoxTreeSelectionModel().isDigIn()) {
                    _tree.getCheckBoxTreeSelectionModel().setSelectionPath(new TreePath(_tree.getModel().getRoot()));
                }
            }
        });
        JButton clearAll = new JButton(new AbstractAction("Clear All") {
            public void actionPerformed(ActionEvent e) {
                _tree.getCheckBoxTreeSelectionModel().clearSelection();
            }
        });

        final JCheckBox digIn = new JCheckBox("Dig In");
        digIn.addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                _tree.getCheckBoxTreeSelectionModel().setDigIn(digIn.isSelected());
                selectAll.setEnabled(digIn.isSelected());
            }
        });
        digIn.setSelected(_tree.getCheckBoxTreeSelectionModel().isDigIn());

        final JCheckBox checkBoxEnabled = new JCheckBox("CheckBox Enabled");
        checkBoxEnabled.addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                _tree.setCheckBoxEnabled(checkBoxEnabled.isSelected());
            }
        });
        checkBoxEnabled.setSelected(_tree.isCheckBoxEnabled());

        final JCheckBox treeEnabled = new JCheckBox("Tree Enabled ");
        treeEnabled.addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                _tree.setEnabled(treeEnabled.isSelected());
            }
        });
        treeEnabled.setSelected(_tree.isEnabled());

        String[] selectionModes = new String[]{
                CheckBoxTreeDemo.SINGLE_SELECTION,
                CheckBoxTreeDemo.CONTIGUOUS_SELECTION,
                CheckBoxTreeDemo.DISCONTIGUOUS_SELECTION
        };

        JComboBox comboBox = new JComboBox(selectionModes);
        comboBox.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED && e.getItem() instanceof String) {
                    if ((e.getItem()).equals(CheckBoxTreeDemo.SINGLE_SELECTION)) {
                        _tree.getCheckBoxTreeSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
                    }
                    else if ((e.getItem()).equals(CheckBoxTreeDemo.CONTIGUOUS_SELECTION)) {
                        _tree.getCheckBoxTreeSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
                    }
                    else {
                        _tree.getCheckBoxTreeSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
                    }
                }
            }
        });
        int mode = _tree.getCheckBoxTreeSelectionModel().getSelectionMode();
        switch (mode) {
            case TreeSelectionModel.SINGLE_TREE_SELECTION:
                comboBox.setSelectedIndex(0);
                break;
            case TreeSelectionModel.CONTIGUOUS_TREE_SELECTION:
                comboBox.setSelectedIndex(1);
                break;
            case TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION:
                comboBox.setSelectedIndex(2);
                break;
        }

        final JCheckBox singleEventMode = new JCheckBox("Single Event Mode");
        singleEventMode.addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                _tree.getCheckBoxTreeSelectionModel().setSingleEventMode(singleEventMode.isSelected());
            }
        });
        singleEventMode.setSelected(_tree.getCheckBoxTreeSelectionModel().isSingleEventMode());

        panel.add(new JLabel("Set Selection Mode:"));
        panel.add(comboBox);
        panel.add(Box.createVerticalStrut(3));
        panel.add(digIn);
        panel.add(singleEventMode);
        panel.add(checkBoxEnabled);
        panel.add(treeEnabled);
        panel.add(Box.createVerticalStrut(3));
        JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 6, 6));
        buttonPanel.add(selectAll);
        buttonPanel.add(clearAll);
        panel.add(buttonPanel);
        panel.add(Box.createGlue());
        return panel;
    }

    public Component getDemoPanel() {
        JPanel panel = new JPanel(new BorderLayout(6, 6));

        if (_tree != null) {
        	_tree.getCheckBoxTreeSelectionModel().removeTreeSelectionListener(this);
        }
        
        final TreeModel treeModel = createTreeModel();

        JPanel treePanel = new JPanel(new BorderLayout(2, 2));
        treePanel.setBorder(BorderFactory.createCompoundBorder(new JideTitledBorder(new PartialEtchedBorder(PartialEtchedBorder.LOWERED, PartialSide.NORTH), "Albums", JideTitledBorder.LEADING, JideTitledBorder.ABOVE_TOP),
                BorderFactory.createEmptyBorder(6, 0, 0, 0)));
        
        _tree = new CheckBoxTree(treeModel) {
            @Override
            public Dimension getPreferredScrollableViewportSize() {
                return new Dimension(400, 400);
            }
        };
        _tree.setRootVisible(false);
        _tree.setShowsRootHandles(true);
        
        
        //MY CALL TO set the selection list...
        //currently the same as the list itself.
        //SOMETHING in here is causing the multiple events.
        //I wasn't getting the multiple events until I added this
        //(which is also in my class that uses the checkboxlist.
        updateCheckBoxListSelectedItems(getCheckBoxTreeItemsFromModel());
        
        _tree.getCheckBoxTreeSelectionModel().addTreeSelectionListener(this);
        
        DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) _tree.getActualCellRenderer();
        renderer.setLeafIcon(IconsFactory.getImageIcon(QuickFilterTreeDemo.class, "/icons/song.png"));
        renderer.setClosedIcon(IconsFactory.getImageIcon(QuickFilterTreeDemo.class, "/icons/album.png"));
        renderer.setOpenIcon(IconsFactory.getImageIcon(QuickFilterTreeDemo.class, "/icons/album.png"));

        SearchableUtils.installSearchable(_tree);

         TreeUtils.expandAll(_tree, true);

        JPanel listsPanel = new JPanel(new GridLayout(1, 2));

        final JList selectedList = new JList();
        final JList eventsList = new JList();
        final DefaultListModel eventsModel = new DefaultListModel();
       
        /**
         * Even if I keep this here...I get multiple calls per
         * checkbox click.
         *
        _tree.getCheckBoxTreeSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                TreePath[] paths = e.getPaths();
                for (TreePath path : paths) {
                    eventsModel.addElement((e.isAddedPath(path) ? "Added - " : "Removed - ") + path);
                }
                eventsModel.addElement("---------------");
                eventsList.ensureIndexIsVisible(eventsModel.size() - 1);

                TreePath[] treePaths = _tree.getCheckBoxTreeSelectionModel().getSelectionPaths();
                DefaultListModel selectedModel = new DefaultListModel();
                if (treePaths != null) {
                    for (TreePath path : treePaths) {
                        selectedModel.addElement(path);
                    }
                }
                selectedList.setModel(selectedModel);
            }
        });
        */
        //this is how I declare the listener in my class that
        //uses the checkboxtree.
        _tree.getCheckBoxTreeSelectionModel().addTreeSelectionListener(this);
        
        eventsList.setModel(eventsModel);

        selectedList.setVisibleRowCount(8);
        eventsList.setVisibleRowCount(8);
        JPanel selectedPanel = new JPanel(new BorderLayout());
        selectedPanel.setBorder(BorderFactory.createCompoundBorder(new JideTitledBorder(new PartialEtchedBorder(PartialEtchedBorder.LOWERED, PartialSide.NORTH), "Selected Songs", JideTitledBorder.LEADING, JideTitledBorder.ABOVE_TOP),
                BorderFactory.createEmptyBorder(6, 0, 0, 0)));
        selectedPanel.add(new JScrollPane(selectedList));

        JPanel eventsPanel = new JPanel(new BorderLayout());
        eventsPanel.setBorder(BorderFactory.createCompoundBorder(new JideTitledBorder(new PartialEtchedBorder(PartialEtchedBorder.LOWERED, PartialSide.NORTH), "Event Fired", JideTitledBorder.LEADING, JideTitledBorder.ABOVE_TOP),
                BorderFactory.createEmptyBorder(6, 0, 0, 0)));
        eventsPanel.add(new JScrollPane(eventsList));

        listsPanel.add(selectedPanel);
        listsPanel.add(eventsPanel);

        treePanel.add(listsPanel, BorderLayout.AFTER_LAST_LINE);
        treePanel.add(new JScrollPane(_tree));
        panel.add(treePanel);

        return panel;
    }

    @Override
    public String getDemoFolder() {
        return "B16.CheckBoxTree";
    }

    static public void main(String[] s) {
        LookAndFeelFactory.installDefaultLookAndFeelAndExtension();
        showAsFrame(new CheckBoxTreeDemo());
    }
    
    
    
    
    
    
    
    
    
    /**
     * My methods....from my class......
     * 
     * It looks like the updateCheckBoxListSelectedItems() is
     * the root of the issue, but I don't know why
     * 
     * 
     * 
     * 
     */
	
	/**
	 * This method finds a child node from a node.  It only 
	 * searches 1 level deep.
	 * 
	 * @param Node
	 * @param String
	 * @return Node
	 */
	public DefaultMutableTreeNode findChildNode(DefaultMutableTreeNode parentNode, String childNameStr) {
		DefaultMutableTreeNode childNode = null;
		
		TreeModel myTreeModel = _tree.getModel();
		
		int rootChildCount = myTreeModel.getChildCount(parentNode);
		for (int i = 0; i < rootChildCount; i++) {
			DefaultMutableTreeNode child = (DefaultMutableTreeNode)myTreeModel.getChild(parentNode, i);
			
			if (child.getUserObject().toString().compareToIgnoreCase(childNameStr) == 0) {
				childNode = child;
				break;
			}						
		}
		
		return childNode;
	}
	
	
	protected Map<String,Vector<String>> getCheckBoxTreeItemsFromModel() {		
		Map<String,Vector<String>> itemMap = new LinkedHashMap<String,Vector<String>>();
		
		selectedItemsMap = new LinkedHashMap<String, Vector<String>>();
		
		{
			String level1Str = "Level1a";
			Vector<String> level2 = new Vector<String>();
			level2.add("Bar");
			level2.add("Blah");
			selectedItemsMap = insertIntoItemMap(level1Str, "Bar", selectedItemsMap);
			selectedItemsMap = insertIntoItemMap(level1Str, "Blah", selectedItemsMap);
			itemMap.put("Level1a", level2);
		}
		
		{
			String level1Str = "Level1b";
			Vector<String> level2 = new Vector<String>();
			level2.add("Bar2");
			level2.add("Blah2");
			
			selectedItemsMap = insertIntoItemMap(level1Str, "Bar2", selectedItemsMap);
			selectedItemsMap = insertIntoItemMap(level1Str, "Blah2", selectedItemsMap);
			
			itemMap.put("Level1b", level2);
		}
		
		{
			String level1Str = "Level1c";
			Vector<String> level2 = new Vector<String>();
			level2.add("Bar3");
			level2.add("Blah3");
			
			selectedItemsMap = insertIntoItemMap(level1Str, "Bar3", selectedItemsMap);
			selectedItemsMap = insertIntoItemMap(level1Str, "Blah3", selectedItemsMap);
			
			itemMap.put("Level1c", level2);
		}
		
		return itemMap;
	}
	
	
	private TreeModel createTreeModel() {				
		
		listItemsMap = getCheckBoxTreeItemsFromModel();
		DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root");

		if (listItemsMap.size() > 0) {
			Iterator<String> i = listItemsMap.keySet().iterator();
			
			while (i.hasNext()) {
				String key = i.next();
				
				DefaultMutableTreeNode level1Node = new DefaultMutableTreeNode(key);
				
				//now do the 2nd level
				Vector<String> level2List = listItemsMap.get(key);
				
				for (Enumeration<String> e = level2List.elements(); e.hasMoreElements();) {
					String level2String = (String)e.nextElement();
					DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(level2String); 
					level1Node.add(subNode);
				}
				
				rootNode.add(level1Node);
			}			
		}
		
		return new DefaultTreeModel(rootNode);
	}
	
	
	
	protected boolean isCheckBoxListSelectionChanged(TreeSelectionEvent e) {
		//TODO FIXME!
		//get the list of selections from the tree
		//and see if it matches the list of paths
		//when the tree was originally built.
		
		int numSelected = _tree.getCheckBoxTreeSelectionModel().getSelectionCount();
		int selectedItemsMapSize = getMapSizeCount(selectedItemsMap);
		TreePath[] treeSelectionPaths = _tree.getCheckBoxTreeSelectionModel().getSelectionPaths();
		
		//if the number selected
		if (numSelected != selectedItemsMapSize) {
			return true;
		} else {
			//we may still have changes even if the number of selections
			//is the same.
			
			//now get all of the selected paths
			//TreePath[] treeSelectionPaths = checkBoxTree.getCheckBoxTreeSelectionModel().getSelectionPaths();
			
			//now walk the selections.
			if (treeSelectionPaths != null) {
				for (int x=0; x< treeSelectionPaths.length; x++) {
					TreePath treeSelectionPath = treeSelectionPaths[x];

					String myPath = treeSelectionPath.toString();
					String parentName = treeSelectionPath.getParentPath().getLastPathComponent().toString();
					String categoryName = treeSelectionPath.getLastPathComponent().toString();

					if (!isFoundInSelectionMap(parentName, categoryName)) {
						return true;
					}
				}
			} else {
				//there are no selections in the checkbox tree.
				//make sure the original list was empty as well.
				if (selectedItemsMapSize == 0) {
					return false;
				} else {
					return true;
				}
				
			}

		}
				
		return false;
	}
	
	/**
	 * This method looks to see if a selection path
	 * exists in the selectedItemsMap 
	 * 
	 * @param String
	 * @param String
	 * @return boolean
	 */
	protected boolean isFoundInSelectionMap(String parentName, String childName) {
		boolean found = false;
		if (selectedItemsMap != null) {
			Vector<String> childkeys = selectedItemsMap.get(parentName);
			
			if (childkeys != null) {
				//we found the parentName in the selected map.
				if (childName != null && !childName.isEmpty()) {
					for (Enumeration<String> e = childkeys.elements(); e.hasMoreElements();) {
						String level2String = (String)e.nextElement();
						if (childName.equalsIgnoreCase(level2String)) {
							found = true;
							break;
						}
					}
				} else {
					//we found a parentName
					//the childName was empty or null
					//so we found the parent.
					return true;
				}
			}
		}
		
		return found;
	}
	
	/**
	 * This gets the count of all items in the map.
	 * 
	 * @param itemMap
	 * @return
	 */
	protected int getMapSizeCount(Map<String, Vector<String>> itemMap) {
		if (itemMap != null) {
			int count = 0;
			Iterator<String> i = itemMap.keySet().iterator();
			while(i.hasNext()) {
				String key = i.next();
				Vector<String> value = itemMap.get(key);
				count += value.size();
			}			
			return count;
		} else {
			return 0;
		}				
	}
	
	/**
	 * insert a new entry into the itemMap, which will 
	 * be the model for the checkboxtree.
	 * 
	 * @param level1Name
	 * @param level2Name
	 */
	protected Map<String,Vector<String>> insertIntoItemMap(String level1Name, String level2Name, Map<String,Vector<String>> itemMap) {
		if (!itemMap.containsKey(level1Name)) {
			//we have to create a new entry
			Vector<String> level2 = new Vector<String>();
			level2.add(level2Name);
			
			itemMap.put(level1Name, level2);
		} else {
			//we already have an entry...update it.
			Vector<String> level2 = itemMap.get(level1Name);
			level2.add(level2Name);
		}
						
		return itemMap;
	}
	
	
	/**
	 * Something in HERE is causing the double events 
	 * 
	 * 
	 * 
	 * TODO  BROKEN HERE
	 * 
	 * 
	 * @param selectedItems
	 */
	protected void updateCheckBoxListSelectedItems(Map<String,Vector<String>> selectedItems) {
		if (selectedItems != null && selectedItems.size() > 0) {
			TreePath[] checkedCategoryPaths = new TreePath[getMapSizeCount(selectedItems)];
			
			Iterator<String> i = selectedItems.keySet().iterator();
			int x = 0;
			while(i.hasNext()) {
				String selectedKey = i.next();
				Vector<String> selectedVector = selectedItems.get(selectedKey);
				
				//find the level1Node in the tree.
				DefaultMutableTreeNode level1Node = this.findChildNode((DefaultMutableTreeNode)_tree.getModel().getRoot(), 
						selectedKey);
				
				for (Enumeration<String> e = selectedVector.elements(); e.hasMoreElements();) {
					String level2String = (String)e.nextElement();
					
					DefaultMutableTreeNode childNode = this.findChildNode(level1Node, level2String);
					if (childNode != null) {
						checkedCategoryPaths[x] = new TreePath(((DefaultMutableTreeNode) childNode).getPath());
						x++;
					}
				}
				
				_tree.collapsePath( new TreePath( ((DefaultMutableTreeNode)level1Node).getPath() ));				
			}
			
			CheckBoxTreeSelectionModel selectionModel = _tree.getCheckBoxTreeSelectionModel();
			selectionModel.setSelectionPaths(checkedCategoryPaths);
		}		
	}
	
	
	public void valueChanged(TreeSelectionEvent e) {
		boolean dirty = false;
		
		dirty = isCheckBoxListSelectionChanged(e);
		
	}
}
