package com.hero.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import com.hero.HeroDesigner;
import com.hero.objects.GenericObject;
import com.hero.objects.List;
import com.hero.ui.widgets.PopupMessage;
import com.hero.ui.widgets.TipTree;
import com.hero.util.Rounder;

/**
 * Copyright (c) 2000 - 2005, CompNet Design, Inc. All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, is prohibited unless the following conditions are met: 1.
 * Express written consent of CompNet Design, Inc. is obtained by the developer.
 * 2. Redistributions must retain this copyright notice. THIS SOFTWARE IS
 * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @author CompNet Design, Inc.
 * @version $Revision$
 */

public class SelectionList extends JComponent {

	private static final ImageIcon stop = new ImageIcon(Toolkit.getDefaultToolkit()
			.createImage(ClassLoader.getSystemResource("stop.gif")));

	private static final ImageIcon warn = new ImageIcon(Toolkit.getDefaultToolkit()
			.createImage(ClassLoader.getSystemResource("warning.gif")));

	private static final ImageIcon black = new ImageIcon(Toolkit.getDefaultToolkit()
			.createImage(ClassLoader.getSystemResource("black.gif")));

	private static final ImageIcon green = new ImageIcon(Toolkit.getDefaultToolkit()
			.createImage(ClassLoader.getSystemResource("green.gif")));

	private static final ImageIcon yellow = new ImageIcon(Toolkit.getDefaultToolkit()
			.createImage(ClassLoader.getSystemResource("yellow.gif")));

	private static final ImageIcon open = new ImageIcon(Toolkit.getDefaultToolkit()
			.createImage(ClassLoader.getSystemResource("openfolder.gif")));

	private static final ImageIcon closed = new ImageIcon(Toolkit.getDefaultToolkit()
			.createImage(ClassLoader.getSystemResource("closefolder.gif")));
	
	private class Renderer extends DefaultTreeCellRenderer {
		private static final long serialVersionUID = -4025726727839447542L;

		@Override
		public Dimension getPreferredSize() {
			Dimension ret = super.getPreferredSize();
			if (!prefab || (getText() == null)) {
				return ret;
			}
			int sw = 80;
			if ((listScroll != null) && (listScroll.getSize().width - sw > 0)
					&& (ret.width > listScroll.getSize().width - sw)) {
				FontMetrics fm = getFontMetrics(getFont());
				int overallWidth = fm.stringWidth(getText());
				int lineHeight = fm.getHeight();
				int height = lineHeight;
				int numLines = (int) Rounder.roundUp(overallWidth
						/ (listScroll.getSize().width - sw));
				if (numLines < 1) {
					numLines = 1;
				}
				int requiredLines = 2;
				int checkWidth = overallWidth / numLines;
				int lastSpace = 0;
				int lastLine = 0;
				int penultimateSpace = 0;
				for (int i = 0; i < getText().length(); i++) {
					String test = getText().substring(lastLine, i);
					if ((test.length() > 0)
							&& Character.isWhitespace(test
									.charAt(test.length() - 1))) {
						lastSpace = i;
					}
					if (fm.stringWidth(test) >= checkWidth - 30) {
						requiredLines++;
						lastLine = lastSpace;
						if (lastSpace != penultimateSpace) {
							i = lastSpace;
							penultimateSpace = lastSpace;
						} else {
							requiredLines++;
						}
					}
				}
				int checkHeight = lineHeight * requiredLines;
				height = checkHeight;
				ret = new Dimension(listScroll.getSize().width - sw, height);
			}
			return ret;
		}

		@Override
		public Component getTreeCellRendererComponent(JTree tree, Object value,
				boolean sel, boolean expanded, boolean leaf, int row,
				boolean hasFocus) {
			super.getTreeCellRendererComponent(tree, value, sel, expanded,
					leaf, row, hasFocus);
			if (value instanceof DefaultMutableTreeNode) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
				if (node.getUserObject() instanceof GenericObject) {
					GenericObject go = (GenericObject) node.getUserObject();
					if (go.isStopSign()) {
						setIcon(stop);
					} else if (go.isWarnSign()) {
						setIcon(warn);
					} else if (!(go instanceof List)) {
						setIcon(sel ? go.showBuildDialog() ? yellow : green
								: black);
					} else if ((go.getName().trim().length() == 0)
							&& (((List) go).getObjects().size() == 0)) {
						setIcon(sel ? green : black);
						setText("(separator)");
					} else if (expanded) {
						setIcon(open);
					} else {
						setIcon(closed);
					}
				}
				if (!sel) {
					setBackground(Color.white);
					setOpaque(true);
				} else {
					setOpaque(false);
				}
			}
			setVerticalAlignment(SwingConstants.TOP);
			setVerticalTextPosition(SwingConstants.TOP);
			return this;
		}
	}

	private static final long serialVersionUID = -278664391861564657L;

	protected TipTree listTree;

	protected TreeSelectionListener treeListener;

	protected ActionListener defineListener;

	private JScrollPane listScroll;

	protected JButton selectBtn;

	protected JButton defineBtn;

	protected boolean prefab;

	private ArrayList<? extends GenericObject> listData;

	public SelectionList(ArrayList<? extends GenericObject> initialData) {
		super();
		prefab = false;
		setLayout(new GridBagLayout());
		listData = initialData;
		initWidgets();
		initListeners();
		layoutComponent();
	}

	/**
	 * @param initialData
	 *            The data to display in the tree
	 * @param isPrefab
	 *            If the data represents a prefab, then the display will be
	 *            resized dynamically to handle the additional text on prefab
	 *            displays
	 */
	public SelectionList(ArrayList<GenericObject> initialData, boolean isPrefab) {
		this(initialData);
		prefab = isPrefab;
	}

	/**
	 * Adds and Action Listener to the Select button.
	 * 
	 * @param listener
	 */
	public void addActionListener(ActionListener listener) {
		selectBtn.addActionListener(listener);
	}

	/**
	 * Adds a MouseListener to the underlying JTree.
	 */
	@Override
	public void addMouseListener(MouseListener listener) {
		listTree.addMouseListener(listener);
	}

	/**
	 * Adds a TreeSelectionListener to the underlying JTree.
	 * 
	 * @param listener
	 */
	public void addTreeSelectionListener(TreeSelectionListener listener) {
		listTree.addTreeSelectionListener(listener);
	}

	/**
	 * Returns the currently selected object (null if none).
	 * 
	 * @return
	 */
	public GenericObject getSelection() {
		if (listTree.getSelectionPath() != null) {
			TreePath path = listTree.getSelectionPath();
			DefaultMutableTreeNode node = (DefaultMutableTreeNode) path
					.getLastPathComponent();
			Object obj = node.getUserObject();
			if (obj != null) {
				GenericObject o = (GenericObject) obj;
				try {
					obj = o.clone();
				} catch (Exception exp) {
				}
				return (GenericObject) obj;
			} else {
				return null;
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns the underlying JTree.
	 * 
	 * @return
	 */
	public JScrollPane getTree() {
		return listScroll;
	}

	private void initListeners() {
		listScroll.addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				((DefaultTreeModel) listTree.getModel()).reload();
			}
		});
		listTree.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if (getSelection() != null) {
					if (e.getKeyChar() == KeyEvent.VK_ENTER) {
						selectBtn.doClick();
					}
				}
			}
		});
		treeListener = new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				if (getSelection() != null) {
					listTree.scrollPathToVisible(listTree.getSelectionPath());
					if (!(getSelection() instanceof com.hero.objects.List)) {
						GenericObject obj = getSelection();
						if ((obj.getDefinition() != null)
								&& (obj.getDefinition().trim().length() > 0)) {
							defineBtn.setEnabled(true);
						} else {
							defineBtn.setEnabled(false);
						}
						selectBtn.setEnabled(true);
					} else {
						GenericObject obj = getSelection();
						if ((obj.getDefinition() != null)
								&& (obj.getDefinition().trim().length() > 0)) {
							defineBtn.setEnabled(true);
						} else {
							defineBtn.setEnabled(false);
						}
						selectBtn.setEnabled(false);
					}
				} else {
					defineBtn.setEnabled(false);
					selectBtn.setEnabled(false);
				}
			}
		};
		listTree.addTreeSelectionListener(treeListener);
		defineListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				GenericObject obj = getSelection();
				if (obj == null) {
					return;
				}
				String definition = obj.getNotes();
				if ((definition == null) || (definition.trim().length() == 0)) {
					definition = obj.getDefinition();
				}
				if (definition == null) {
					return;
				}
				if (definition.trim().length() == 0) {
					return;
				}
				PopupMessage popup = PopupMessage.getInstance(HeroDesigner
						.getAppFrame(), defineBtn, definition, false);
				popup.setVisible(true);
			}
		};
		defineBtn.addActionListener(defineListener);
	}

	private void initWidgets() {
		if (listData == null) {
			listData = new ArrayList<GenericObject>();
		}
		DefaultMutableTreeNode root = new DefaultMutableTreeNode();
		listTree = new TipTree(root);

		listTree.setToolTipText("Blah");

		listTree.setRowHeight(-1);
		listTree.setCellRenderer(new Renderer());
		listTree.setRootVisible(false);
		listTree.setShowsRootHandles(true);
		listTree.getSelectionModel().setSelectionMode(
				TreeSelectionModel.SINGLE_TREE_SELECTION);
		listTree.setBackground(Color.white);
		listTree.setOpaque(true);
		setAvailableList(listData);
		listScroll = new JScrollPane(listTree);
		listScroll.setPreferredSize(new Dimension(200, 200));
		listTree.setLargeModel(true);
		selectBtn = new JButton("Select");
		selectBtn.setEnabled(false);
		defineBtn = new JButton("Define");
		defineBtn.setEnabled(false);
	}

	private void layoutComponent() {
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.gridwidth = 1;
		gbc.gridheight = 1;
		gbc.fill = GridBagConstraints.BOTH;
		gbc.weightx = 1;
		gbc.weighty = 1;
		add(listScroll, gbc);
		gbc.gridy++;
		gbc.weighty = 0;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.anchor = GridBagConstraints.CENTER;
		JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
		buttons.add(selectBtn);
		buttons.add(defineBtn);
		add(buttons, gbc);
	}

	private void populateNode(ArrayList<? extends GenericObject> data,
			DefaultMutableTreeNode root) {
		for (GenericObject obj : data) {
			if (obj instanceof com.hero.objects.List) {
				DefaultMutableTreeNode branch = new DefaultMutableTreeNode(obj,
						true);
				com.hero.objects.List list = (com.hero.objects.List) obj;
				list.updateChildPositions();
				ArrayList<GenericObject> objects = list.getObjects();
				populateNode(objects, branch);
				root.add(branch);
			} else {
				DefaultMutableTreeNode leaf = new DefaultMutableTreeNode(obj,
						false);
				root.add(leaf);
			}
		}
	}

	/**
	 * Returns the row in the underlying JTree at the specified Point.
	 * 
	 * @param p
	 * @return
	 */
	public int rowAtPoint(Point p) {
		return listTree.getRowForLocation((int) p.getX(), (int) p.getY());
	}

	public void setAvailableList(ArrayList<? extends GenericObject> newData) {
		if (newData == null) {
			listData = new ArrayList<GenericObject>();
		} else {
			listData = newData;
		}
		DefaultMutableTreeNode root = (DefaultMutableTreeNode) listTree
				.getModel().getRoot();
		root.removeAllChildren();
		populateNode(listData, root);
		DefaultTreeModel model = new DefaultTreeModel(root);
		listTree.setModel(model);
		listTree.treeDidChange();
	}

	/**
	 * Sets the ActionListener for the Define button.
	 * 
	 * @param listener
	 */
	public void setDefineListener(ActionListener listener) {
		defineBtn.removeActionListener(defineListener);
		defineBtn.addActionListener(listener);
		defineListener = listener;
	}

	/**
	 * Sets the selected row from the underlying JTree.
	 * 
	 * @param row
	 */
	public void setSelectedRow(int row) {
		listTree.setSelectionRow(row);
	}

	/**
	 * Sets the TreeSelectionListener for the JTree (only one allowed)
	 * 
	 * @param listener
	 */
	public void setTreeListener(TreeSelectionListener listener) {
		listTree.removeTreeSelectionListener(treeListener);
		listTree.addTreeSelectionListener(listener);
		treeListener = listener;
		return;
	}
}