package modeler;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.IntBuffer;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import modeler.shape.BezierRotation;
import modeler.shape.Cube;
import modeler.shape.Cylinder;
import modeler.shape.Shape;
import modeler.shape.Sphere;

/**
 * @author ags
 */
public class MainFrame extends JFrame implements ActionListener, TreeSelectionListener {
	
	private static final long serialVersionUID = 1L;
	
	// The initial size of the application
	protected static final int DEFAULT_WIDTH = 800;
	protected static final int DEFAULT_HEIGHT = 600;
	
	//Size of the screen
	protected static final Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize();
	
	//Menu commands
	private static final String SAVE_AS_MENU_TEXT = "Save As";
	private static final String OPEN_MENU_TEXT = "Open";
	private static final String EXPORT_MENU_TEXT = "Export";
	private static final String EXIT_MENU_TEXT = "Exit";
	private static final String CLEAR_SELECTED_TEXT = "Clear selection";
	private static final String GROUP_MENU_TEXT = "Group selected";
	private static final String REPARENT_MENU_TEXT = "Reparent selected";
	private static final String DELETE_MENU_TEXT = "Delete selected";
	private static final String PICK_MENU_TEXT = "Select";
	private static final String ROTATE_MENU_TEXT = "Rotate selected";
	private static final String TRANSLATE_MENU_TEXT = "Translate selected";
	private static final String SCALE_MENU_TEXT = "Scale selected";
	private static final String ADD_LIGHT_MENU_TEXT = "Add Light";
	private static final String ADD_SPHERE_MENU_TEXT = "Add Sphere";
	private static final String ADD_CUBE_MENU_TEXT = "Add Cube";
	private static final String ADD_CYLINDER_MENU_TEXT = "Add Cylinder";
	private static final String ADD_ROTATION_MENU_TEXT = "Add Spline/R";
	
	//GUI elements
	protected JFileChooser fileChooser;
	protected DefaultTreeModel transformationTree;
	protected JTree transformationTreeView;
	protected JComponent leftWindow;
	protected JPanel currentAttributesPanel;
	protected JPanel attributeHolder;
	protected JPanel allGLViews;
	protected JPanel currentView;
	protected GLView pView;
	protected GLView xOrtho;
	protected GLView yOrtho;
	protected GLView zOrtho;
	private boolean isReparenting = false;
	private Treeable[] nodesToReparent;
	
	//Cameras
	protected PerspectiveCamera pViewCam;
	protected OrthoCamera xOrthoCam;
	protected OrthoCamera yOrthoCam;
	protected OrthoCamera zOrthoCam;
	
	//Manipulator data
	protected Manip currentManip;
	protected IntBuffer pickBuffer = net.java.games.jogl.util.BufferUtils.newIntBuffer(1024);
	
	
	/**
	 * Creates the initial application
	 */
	public MainFrame() {
		
		//Define the basic settings
		super("CS 465 Modeler");
		JPopupMenu.setDefaultLightWeightPopupEnabled(false);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Container cp = getContentPane();
		cp.setLayout(new BorderLayout());
		fileChooser = new JFileChooser();
		
		//Build the GUI
		cp.add(createGUI(), BorderLayout.CENTER);
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
		setLocation((int) (SCREEN_SIZE.getWidth() / 2 - getWidth() / 2), (int) (SCREEN_SIZE.getHeight() / 2 - getHeight() / 2));
		transformationTreeView.expandPath(new TreePath(transformationTree.getRoot()));
		
	}
	
	/**
	 * Creates the GUI
	 * @return
	 */
	protected JComponent createGUI() {
		
		JSplitPane toReturn = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false);
		leftWindow = createLeftComponent();
		toReturn.setLeftComponent(leftWindow);
		toReturn.setRightComponent(createRightComponent());
		toReturn.resetToPreferredSizes();
		toReturn.setOneTouchExpandable(true);
		toReturn.setResizeWeight(0.2);
		
		return toReturn;
	}
	
	/**
	 * Maps all GUI actions to listeners in this object and builds the menu
	 */
	protected void createActionsAndBuildMenu() {
		
		//Create all the actions
		BasicAction group = new BasicAction(GROUP_MENU_TEXT, this);
		BasicAction reparent = new BasicAction(REPARENT_MENU_TEXT, this);
		BasicAction delete = new BasicAction(DELETE_MENU_TEXT, this);
		BasicAction clear = new BasicAction(CLEAR_SELECTED_TEXT, this);
		
		BasicAction addLight = new BasicAction(ADD_LIGHT_MENU_TEXT, this);
		BasicAction addSphere = new BasicAction(ADD_SPHERE_MENU_TEXT, this);
		BasicAction addCube = new BasicAction(ADD_CUBE_MENU_TEXT, this);
		BasicAction addCylinder = new BasicAction(ADD_CYLINDER_MENU_TEXT, this);
		BasicAction addSplineR = new BasicAction(ADD_ROTATION_MENU_TEXT, this);
		
		BasicAction saveAs = new BasicAction(SAVE_AS_MENU_TEXT, this);
		BasicAction open = new BasicAction(OPEN_MENU_TEXT, this);
		BasicAction export = new BasicAction(EXPORT_MENU_TEXT, this);
		BasicAction exit = new BasicAction(EXIT_MENU_TEXT, this);
		
		BasicAction pickTool = new BasicAction(PICK_MENU_TEXT, this);
		BasicAction rotateTool = new BasicAction(ROTATE_MENU_TEXT, this);
		BasicAction translateTool = new BasicAction(TRANSLATE_MENU_TEXT, this);
		BasicAction scaleTool = new BasicAction(SCALE_MENU_TEXT, this);
		
		//Set shortcut keys
		group.setAcceleratorKey(KeyEvent.VK_G, KeyEvent.CTRL_MASK);
		reparent.setAcceleratorKey(KeyEvent.VK_R, KeyEvent.CTRL_MASK);
		delete.setAcceleratorKey(KeyEvent.VK_DELETE, 0);
		
		pickTool.setAcceleratorKey(KeyEvent.VK_Q, 0);
		translateTool.setAcceleratorKey(KeyEvent.VK_W, 0);
		rotateTool.setAcceleratorKey(KeyEvent.VK_E, 0);
		scaleTool.setAcceleratorKey(KeyEvent.VK_R, 0);
		
		saveAs.setAcceleratorKey(KeyEvent.VK_A, KeyEvent.CTRL_MASK);
		open.setAcceleratorKey(KeyEvent.VK_O, KeyEvent.CTRL_MASK);
		exit.setAcceleratorKey(KeyEvent.VK_Q, KeyEvent.CTRL_MASK);
		
		//Create the menu
		JMenuBar bar = new JMenuBar();
		JMenu menu;
		
		menu = new JMenu("File");
		menu.setMnemonic('F');
		menu.add(new JMenuItem(open));
		menu.add(new JMenuItem(saveAs));
		menu.add(new JMenuItem(export));
		menu.addSeparator();
		menu.add(new JMenuItem(exit));
		bar.add(menu);
		
		menu = new JMenu("Edit");
		menu.setMnemonic('E');
		menu.add(new JMenuItem(group));
		menu.add(new JMenuItem(reparent));
		menu.add(new JMenuItem(delete));
		menu.add(new JSeparator());
		menu.add(new JMenuItem(pickTool));
		menu.add(new JMenuItem(translateTool));
		menu.add(new JMenuItem(rotateTool));
		menu.add(new JMenuItem(scaleTool));
		bar.add(menu);
		
		menu = new JMenu("Scene");
		menu.setMnemonic('S');
		menu.add(new JMenuItem(addLight));
		menu.add(new JMenuItem(addSphere));
		menu.add(new JMenuItem(addCube));
		menu.add(new JMenuItem(addCylinder));
		menu.add(new JMenuItem(addSplineR));
		bar.add(menu);
		
		setJMenuBar(bar);
		
		JPopupMenu p = new JPopupMenu();
		p.add(new JMenuItem(group));
		p.add(new JMenuItem(reparent));
		p.add(new JMenuItem(delete));
		p.add(new JMenuItem(clear));
		p.addSeparator();
		p.add(new JMenuItem(addLight));
		p.add(new JMenuItem(addSphere));
		p.add(new JMenuItem(addCube));
		p.add(new JMenuItem(addCylinder));
		p.add(new JMenuItem(addSplineR));
		
		transformationTreeView.addMouseListener(new PopupListener(p));
		transformationTreeView.addTreeSelectionListener(this);
	}
	
	/**
	 * Create the tree and properties veiws
	 * @return
	 */
	protected JComponent createLeftComponent() {
		
		JSplitPane toReturn = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false);
		JPanel temp;
		
		// Create the tree models
		transformationTree = new DefaultTreeModel(new Transformation("Root"));
		
		// Create the tree views
		transformationTreeView = new JTree(transformationTree);
		DefaultTreeCellRenderer renderer = new TreeRenderer();
		transformationTreeView.setCellRenderer(renderer);
		transformationTreeView.setEditable(true);
		transformationTreeView.setCellEditor(new DefaultTreeCellEditor(transformationTreeView, renderer));
		transformationTreeView.setShowsRootHandles(true);
		transformationTreeView.setRootVisible(true);

		KeyListener[] kls = transformationTreeView.getKeyListeners();
		for (int i=0; i<kls.length; i++) transformationTreeView.removeKeyListener(kls[i]);
		
		// Associate the action handlers (and make the JMenuBar too)
		createActionsAndBuildMenu();
		
		// Add the components to another split pane
		temp = new JPanel(new BorderLayout());
		temp.add(new JLabel(" Scene"), BorderLayout.NORTH);
		temp.add(new JScrollPane(transformationTreeView));
		toReturn.setLeftComponent(temp);
		attributeHolder = new JPanel(new BorderLayout());
		currentAttributesPanel = ItemAttributePanel.EMPTY_PANEL;
		attributeHolder.add(currentAttributesPanel, BorderLayout.NORTH);
		toReturn.setRightComponent(attributeHolder);
		
		// Set some nice options on the split pane
		toReturn.setOneTouchExpandable(true);
		toReturn.setResizeWeight(0.95);
		toReturn.resetToPreferredSizes();
		
		// Return
		return toReturn;
		
	}
	
	/**
	 * Create the viewing windows
	 * @return
	 */
	protected JComponent createRightComponent() {
		
		currentView = new JPanel(new GridLayout(1, 1));
		allGLViews = new JPanel(new GridLayout(2, 2, 10, 10));
		Treeable root = (Treeable) transformationTree.getRoot();
		
		// TOP VIEW
		yOrthoCam = new OrthoCamera(new Point3f(0, 1, 0), new Point3f(0, 0, 0), new Vector3f(0, 0, -1), "Top");
		yOrtho = new GLView(this, transformationTreeView, yOrthoCam);
		allGLViews.add(yOrtho);
		
		// CAMERA VIEW
		pViewCam = new PerspectiveCamera(0.01f, 1000f, 45.0f, "Camera");
		pView = new GLView(this, transformationTreeView, pViewCam);
		transformationTree.insertNodeInto(pView.getCamera(), root, root.getChildCount());
		allGLViews.add(pView);
		
		// FRONT VIEW
		zOrthoCam = new OrthoCamera(new Point3f(0, 0, 1), new Point3f(0, 0, 0), new Vector3f(0, 1, 0), "Front");
		zOrtho = new GLView(this, transformationTreeView, zOrthoCam);
		allGLViews.add(zOrtho);
		
		// SIDE VIEW
		xOrthoCam = new OrthoCamera(new Point3f(1, 0, 0), new Point3f(0, 0, 0), new Vector3f(0, 1, 0), "Side");
		xOrtho = new GLView(this, transformationTreeView, xOrthoCam);
		allGLViews.add(xOrtho);
		
		currentView.add(allGLViews);
		
		// LIGHT
		transformationTree.insertNodeInto(Light.makeNewLight(true), root, root.getChildCount());
		
		return currentView;
	}
	
	/**
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	public void actionPerformed(ActionEvent e) {
		
		String cmd = e.getActionCommand();
		if (cmd == null) {
			return;
		}
		else if (cmd.equals(GROUP_MENU_TEXT)) {
			groupSelected(getSelectedReparentables(), "Group");
		}
		else if (cmd.equals(CLEAR_SELECTED_TEXT)) {
			transformationTreeView.clearSelection();
		}
		else if (cmd.equals(REPARENT_MENU_TEXT)) {
			nodesToReparent = getSelectedReparentables();
			isReparenting = true;
		}
		else if (cmd.equals(DELETE_MENU_TEXT)) {
			deleteSelected(getDeleteableSelections());
			currentManip = null;
			refresh();
		}
		else if (cmd.equals(PICK_MENU_TEXT)) {
			currentManip = null;
			refresh();
		}
		else if (cmd.equals(TRANSLATE_MENU_TEXT)) {
			Treeable ts[] = getLeafmostSelectedTransformations();
			if (ts.length > 0) {
				currentManip = new TranslateManip((Transformation)ts[0]);
				refresh();
			}
		}
		else if (cmd.equals(ROTATE_MENU_TEXT)) {
			Treeable ts[] = getLeafmostSelectedTransformations();
			if (ts.length > 0) {
				currentManip = new RotateManip((Transformation)ts[0]);
				refresh();
			}
		}
		else if (cmd.equals(SCALE_MENU_TEXT)) {
			Treeable ts[] = getLeafmostSelectedTransformations();
			if (ts.length > 0) {
				currentManip = new ScaleManip((Transformation)ts[0]);
				refresh();
			}
		}
		else if (cmd.equals(ADD_LIGHT_MENU_TEXT)) {
			addNewLight();
		}
		else if (cmd.equals(ADD_SPHERE_MENU_TEXT)) {
			addNewShape(Sphere.class);
		}
		else if (cmd.equals(ADD_CUBE_MENU_TEXT)) {
			addNewShape(Cube.class);
		}
		else if (cmd.equals(ADD_CYLINDER_MENU_TEXT)) {
			addNewShape(Cylinder.class);
		}
		else if (cmd.equals(ADD_ROTATION_MENU_TEXT)) {
			addNewShape(BezierRotation.class);
		}
		else if (cmd.equals(OPEN_MENU_TEXT)) {
			openTree();
		}
		else if (cmd.equals(SAVE_AS_MENU_TEXT)) {
			saveTreeAs();
		}
		else if (cmd.equals(EXPORT_MENU_TEXT)) {
			export();
		}
		else if (cmd.equals(EXIT_MENU_TEXT)) {
			System.exit(0);
		}
	}
	
	/**
	 * Returns the tree model
	 * @return
	 */
	public DefaultTreeModel getTree() {
		
		return transformationTree;
		
	}
	
	/**
	 * Loads a tree stored in a file
	 */
	protected void openTree() {
		
		//Select a file
		int choice = fileChooser.showOpenDialog(this);
		if (choice != JFileChooser.APPROVE_OPTION) {
			refresh();
			return;
		}
		String filename = fileChooser.getSelectedFile().getAbsolutePath();
		
		//Load the tree
		try {
			Treeable newRoot;
			ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(filename)));
			newRoot = Treeable.read(in);
			//fixIcons(newRoot);
			synchronized (transformationTree) {
				transformationTree.setRoot(newRoot);
			}
			in.close();
		}
		catch (Exception e) {
			showExceptionDialog(e);
		}
		
		//Update the window
		refresh();
	}
	
	/**
	 * Fix all the icon bindings
	 * @param inRoot
	 */
	protected void fixIcons(Treeable inRoot) {
		
		for (int i = 0; i < inRoot.getChildCount(); i++)
			fixIcons((Treeable) inRoot.getChildAt(i));
	}
	
	/**
	 * Save a tree to a file
	 *
	 */
	protected void saveTreeAs() {
		
		//Pick a file
		int choice = fileChooser.showSaveDialog(this);
		if (choice != JFileChooser.APPROVE_OPTION) {
			refresh();
			return;
		}
		String filename = fileChooser.getSelectedFile().getAbsolutePath();
		
		//Write the tree out
		try {
			synchronized (transformationTree) {
				ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));
				((Treeable) transformationTree.getRoot()).save(out);
				out.close();
			}
		}
		catch (IOException ioe) {
			showExceptionDialog(ioe);
		}
		
		refresh();
	}
	
	/**
	 * Export the tree in a format readable by the ray tracer
	 *
	 */
	protected void export() {
		
		//Pick a file
		int choice = fileChooser.showSaveDialog(this);
		if (choice != JFileChooser.APPROVE_OPTION) {
			refresh();
			return;
		}
		String filename = fileChooser.getSelectedFile().getAbsolutePath();
		
		//Write the tree out
		try {
			synchronized (transformationTree) {
				Treeable.export(filename, (Treeable) transformationTree.getRoot());
			}
		}
		catch (IOException ioe) {
			showExceptionDialog(ioe);
		}
		
		refresh();
	}
	
	/**
	 * Filters out extraneous child nodes from the 
	 * supplied array of Treeables.  
	 */
	protected Vector filterChildren(Treeable[] selected) {
		// Trim extraneous child nodes
		Vector filtered = new Vector();
		for (int i = 0; i < selected.length; i++)
			filtered.add(selected[i]);
		
		//Find the redundant children
		for (Iterator j = filtered.iterator(); j.hasNext();) {
			Treeable tj = (Treeable) j.next();
			for (int i = 0; i < selected.length; i++) {
				Treeable ti = selected[i];
				if (ti == tj) continue;
				if (tj.isNodeAncestor(ti)) j.remove();
			}
		}
		return filtered;
	}
	
	/**
	 * Reparents the items in nodesToReparent underneath
	 * the currently selected node.
	 */
	protected void reparentUnderSelected() {
		Treeable[] ts = getSelectedReparentables();
		
		// Invalid number of new parents selected?
		if (ts.length != 1) return;
		Treeable parent = ts[0];
		
		// Invalid parent type selected?
		if (parent instanceof Shape ) return;
		
		// Invalid children selected?
		for (int i=0; i<nodesToReparent.length; i++) {
			if (parent.isNodeAncestor(nodesToReparent[i])) return;
		}
		
		Vector filtered = filterChildren(nodesToReparent);
		
		//reparent the filtered children each seperately
		for (int i=0; i<filtered.size(); i++) {
			Treeable t = (Treeable)filtered.get(i);
			t.removeFromParent();
			parent.insert(t,0);
		}
	}
	
	/**
	 * Groups a set of selected nodes into a new parent
	 * @param selected
	 * @param name
	 */
	protected void groupSelected(Treeable[] selected, String name) {
		
		Vector filtered = filterChildren(selected);
		
		if (filtered.size() == 0) return;
		
		//Form the new group and add it to the tree
		Treeable groupNode = new Transformation(name);
		Treeable firstSelected = (Treeable) filtered.get(0);
		Treeable groupParent = (Treeable) firstSelected.getParent();
		if (groupParent == null) return;
		int groupIdx = groupParent.getIndex(firstSelected);
		synchronized (transformationTree) {
			transformationTree.insertNodeInto(groupNode, groupParent, groupIdx);
			for (int i = 0; i < filtered.size(); i++) {
				Treeable node = (Treeable) filtered.get(i);
				transformationTree.removeNodeFromParent(node);
				transformationTree.insertNodeInto(node, groupNode, groupNode.getChildCount());
			}
			transformationTree.reload();
		}
	}
	
  /**
   * Deletes all the selected nodes.  Checks to make sure deletes are 
   * valid first
   * @param selected
   */
	protected void deleteSelected(Treeable[] selected) {
		Vector filtered = filterChildren(selected);
		for (int i=0; i<filtered.size(); i++) {
			Treeable t = (Treeable)filtered.get(i);
      if(t == transformationTree.getRoot())
        continue;
      if(t instanceof Light)
        Light.returnLight((Light) t);
			transformationTree.removeNodeFromParent(t);
		}
	}
	
	/**
	 * Add a new light to the tree
	 */
	protected void addNewLight() {
		
		//Create a light
		Light light = Light.makeNewLight(true);
		if (light == null)
			return;
		Treeable root = (Treeable) transformationTree.getRoot();
		
		//Add it to the tree
		synchronized (transformationTree) {
			transformationTree.insertNodeInto(light, root, root.getChildCount());
		}
		
		refresh();
	}
	
	/**
	 * Add a new shape to the tree
	 * @param c
	 */
	protected void addNewShape(Class c) {
		
		TreePath path = transformationTreeView.getSelectionPath();
		
		//Get the node to insert into
		Treeable selected = (Treeable) transformationTree.getRoot();
		if (path != null && path.getLastPathComponent() instanceof Transformation)
			selected = (Treeable) path.getLastPathComponent();
		
		//Add the node
		try {
			Treeable newObject = (Treeable) c.newInstance();
			Treeable newTransform = new Transformation(newObject.toString());
			newTransform.insert(newObject, 0);
			synchronized (transformationTree) {
				transformationTree.insertNodeInto(newTransform, selected, selected.getChildCount());
			}
		}
		catch (Exception e) {
			showExceptionDialog(e);
		}
		
		refresh();
	}
	
	/**
	 * @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent)
	 */
	public void valueChanged(TreeSelectionEvent e) {
		
		if (isReparenting) {
			reparentUnderSelected();
			isReparenting = false;
			transformationTree.reload();
		} else {
			Treeable[] transformations = getLeafmostSelectedTransformations();
			if (transformations.length != 0) {
				if (currentManip != null) {
					currentManip.drawEnabled = true;
					currentManip.setTransformation((Transformation)transformations[0]);
				}
			}
			attributeHolder.remove(currentAttributesPanel);
			if (getSelection().length == 1) {
				Treeable node = getSelection()[0];
				currentAttributesPanel = node.getAttributePanel(this);
			}
			else {
				currentAttributesPanel = ItemAttributePanel.EMPTY_PANEL;
			}
			attributeHolder.add(currentAttributesPanel, BorderLayout.NORTH);
			
			currentAttributesPanel.invalidate();
			((ItemAttributePanel)currentAttributesPanel).refresh();
			leftWindow.revalidate();
			leftWindow.repaint();
		}
		refresh();
		
	}
	
	/**
	 * Returns an array containing the current selection
	 * @return
	 */
	public Treeable[] getSelection() {
		
		TreePath[] paths = transformationTreeView.getSelectionPaths();
		if (paths == null) {
			return new Treeable[] {};
		}
		Treeable[] ts = new Treeable[paths.length];
		for (int i = 0; i < paths.length; i++) {
			ts[i] = (Treeable) paths[i].getLastPathComponent();
		}
		return ts;
	}
	
	/**
	 * Returns an array containing the current selection, limited to
	 * Transformations and Shapes (reparentable objects)
	 */
	public Treeable[] getSelectedReparentables() {
		
		int ts_size = 0;
		TreePath[] paths = transformationTreeView.getSelectionPaths();
		if (paths == null) {
			return new Treeable[] {};
		}
		
		for (int i = 0; i < paths.length; i++) {
			Treeable t = (Treeable) paths[i].getLastPathComponent();
			if (t instanceof Shape || t instanceof Transformation)
				ts_size++;
		}
		
		Treeable ts[] = new Treeable[ts_size];
		int i_ts = 0;
		for (int i = 0; i < paths.length; i++) {
			Treeable t = (Treeable) paths[i].getLastPathComponent();
			if (t instanceof Shape || t instanceof Transformation)
				ts[i_ts++] = t;
		}
		
		return ts;
	}
  
  /**
   * Gets all reparentable selections and then adds all the lights
   * @return
   */
  public Treeable[] getDeleteableSelections() {
    
    Treeable[] output = getSelectedReparentables();
    int ts_size = 0;
    TreePath[] paths = transformationTreeView.getSelectionPaths();
    if (paths == null) {
      return output;
    }
    
    for (int i = 0; i < paths.length; i++) {
      Treeable t = (Treeable) paths[i].getLastPathComponent();
      if (t instanceof Light)
        ts_size++;
    }
    
    Treeable ts[] = new Treeable[output.length + ts_size];
    System.arraycopy(output, 0, ts, 0, output.length);
    int i_ts = output.length;
    for (int i = 0; i < paths.length; i++) {
      Treeable t = (Treeable) paths[i].getLastPathComponent();
      if (t instanceof Light)
        ts[i_ts++] = t;
    }
    
    return ts;
    
    
  }
	
	/**
	 * Returns an array containing the current selection, limited to
	 * Transformations and Shapes, filtering out extraneous parent Transformations
	 * and replacing Shapes with their immediate parent transformation. The result
	 * is a Transformation array where no Transformation appears above any of the
	 * others.
	 */
	public Treeable[] getLeafmostSelectedTransformations() {
		
		Vector transformations = new Vector();
		
		Treeable[] selection = getSelectedReparentables();
		for (int i = 0; i < selection.length; i++) {
			if (selection[i] instanceof Shape) {
				if (selection[i].parent != null && !transformations.contains(selection[i].parent))
					transformations.add(selection[i].parent);
			}
			else if (selection[i] instanceof Transformation && !transformations.contains(selection[i])) {
				transformations.add(selection[i]);
			}
		}
		
		Vector filteredTransformations = new Vector(transformations);
		for (Iterator i = transformations.iterator(); i.hasNext();) {
			Treeable ti = (Treeable) i.next();
			Treeable testParent = ti.parent;
			while (testParent != null) {
				for (Iterator j = filteredTransformations.iterator(); j.hasNext();) {
					Treeable tj = (Treeable) j.next();
					if (ti == tj)
						continue;
					if (tj == testParent) {
						j.remove();
					}
				}
				testParent = testParent.parent;
			}
		}
		
		Treeable[] ts = new Treeable[filteredTransformations.size()];
		for (int i = 0; i < filteredTransformations.size(); i++)
			ts[i] = (Treeable) filteredTransformations.get(i);
		return ts;
	}
	
	/**
	 * Translates all cameras by a given vector
	 * @param motion
	 */
	public void trackAllCams(Vector3f motion) {
		
		pViewCam.translate(motion);
		xOrthoCam.translate(motion);
		yOrthoCam.translate(motion);
		zOrthoCam.translate(motion);
	}
	
	/**
	 * Zooms all the orthographic cameras
	 * @param d
	 */
	public void zoomAllOrtho(float d) {
		
		xOrthoCam.zoom(d);
		yOrthoCam.zoom(d);
		zOrthoCam.zoom(d);
	}
	
	/**
	 * Causes all views to be repainted
	 *
	 */
	public void refresh() {
		
		pView.refresh();
		xOrtho.refresh();
		yOrtho.refresh();
		zOrtho.refresh();
	}
	
	/**
	 * Set one view to cover the whole screen and temporarily repress the remaining views
	 * @param toFocus
	 */
	public void setView(GLView toFocus) {
		
		if (currentView.getComponent(0) == allGLViews) {
			currentView.remove(0);
			currentView.add(toFocus);
		}
		else {
			currentView.remove(0);
			allGLViews.removeAll();
			allGLViews.add(yOrtho);
			allGLViews.add(pView);
			allGLViews.add(zOrtho);
			allGLViews.add(xOrtho);
			currentView.add(allGLViews);
		}
		validate();
	}
	
	/**
	 * Returns true if all the supplied objects are the same class
	 * @param a
	 * @return
	 */
	public boolean allSameType(Object[] a) {
		
		if (a.length == 0)
			return false;
		Class type = a[0].getClass();
		for (int i = 1; i < a.length; i++) {
			if (a[i].getClass() != type)
				return false;
		}
		return true;
	}
	
	/**
	 * Displays an exception in a window
	 * @param e
	 */
	protected void showExceptionDialog(Exception e) {
		
		String str = "The following exception was thrown: " + e.toString() + ".\n\n" + "Would you like to see the stack trace?";
		int choice = JOptionPane.showConfirmDialog(this, str, "Exception Thrown", JOptionPane.YES_NO_OPTION);
		
		if (choice == JOptionPane.YES_OPTION) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Private class that handles the popup menu events in the display tree
	 * @author arbree
	 * Oct 21, 2005
	 * MainFrame.java
	 * Copyright 2005 Program of Computer Graphics, Cornell University
	 */
	private class PopupListener extends MouseAdapter {
		
		protected JPopupMenu menu;
		
		public PopupListener(JPopupMenu newMenu) {
			
			menu = newMenu;
		}
		
		public void mousePressed(MouseEvent e) {
			
			maybeShowPopup(e);
		}
		
		public void mouseReleased(MouseEvent e) {
			
			maybeShowPopup(e);
		}
		
		protected void maybeShowPopup(MouseEvent e) {
			
			if (e.isPopupTrigger()) {
				menu.show(e.getComponent(), e.getX(), e.getY());
			}
		}
	}
	
	/**
	 * Private class that handles the GUI events
	 * @author arbree
	 * Oct 21, 2005
	 * MainFrame.java
	 * Copyright 2005 Program of Computer Graphics, Cornell University
	 */
	private class BasicAction extends AbstractAction {
		
		private static final long serialVersionUID = 1L;
		
		protected ActionListener listener;
		
		public BasicAction(String newName, ActionListener newListener) {
			
			super(newName);
			listener = newListener;
		}
		
		public void setShortDescription(String s) {
			
			putValue(AbstractAction.SHORT_DESCRIPTION, s);
		}
		
		public void setMnemonicKey(int i) {
			
			putValue(AbstractAction.MNEMONIC_KEY, new Integer(i));
		}
		
		public void setAcceleratorKey(int key, int masks) {
			
			putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(key, masks));
		}
		
		public void actionPerformed(ActionEvent e) {
			
			listener.actionPerformed(e);
		}
		
	}
	
	/**
	 * Class that describes how the display tree is rendered
	 * @author arbree
	 * Oct 21, 2005
	 * MainFrame.java
	 * Copyright 2005 Program of Computer Graphics, Cornell University
	 */
	private class TreeRenderer extends DefaultTreeCellRenderer {
		
		private static final long serialVersionUID = 1L;
		
		public TreeRenderer() {
			
		}
		
		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 Treeable)
				setIcon(((Treeable) value).getIcon());
			return this;
		}
	}
	
	/**
	 * Main
	 * @param args
	 */
	public static final void main(String[] args) {
		
		MainFrame m = new MainFrame();
		
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		m.setBounds(0, 0, screenSize.width, screenSize.height);
		
		m.show();
		m.setExtendedState(JFrame.MAXIMIZED_BOTH);
		m.refresh();
	}

	
}
