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.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.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 GROUP_MENU_TEXT = "Group selected";
  private static final String REPARENT_MENU_TEXT = "Reparent 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;

  //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 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);

    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 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.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);
    transformationTreeView.setCellRenderer(new TreeRenderer());
    transformationTreeView.setShowsRootHandles(true);
    transformationTreeView.setRootVisible(false);

    // 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(REPARENT_MENU_TEXT)) {
      // TODO: do something
      System.out.println("reparent?");
    }
    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)) {
      // TODO: do something
      System.out.println("rotate?");
      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();
  }
  
  /**
   * Removes a node from the tree
   */
  protected void removeCurrentNode() {

    TreePath path = transformationTreeView.getSelectionPath();
    if (path == null)
      return;

    Treeable selected = (Treeable) path.getLastPathComponent();
    if (selected == transformationTree.getRoot())
      return;
    if (selected instanceof Camera)
      return;

    synchronized (transformationTree) {
      transformationTree.removeNodeFromParent(selected);
      if (selected instanceof Light) {
        Light.returnLight((Light) selected);
      }
    }

    refresh();
  }

  /**
   * Groups a set of selected nodes into a new parent
   * @param selected
   * @param name
   */
  protected void groupSelected(Treeable[] selected, String name) {

    System.out.println("GroupSelected " + selected.length);

    // First, trim redundant child nodes from the selection
    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();
      boolean removed = false;
      for (int i = 0; i < selected.length; i++) {
        Treeable ti = selected[i];
        if (ti == tj)
          continue;
        Treeable testParent = tj.parent;
        while (testParent != null) {
          if (testParent == ti) {
            j.remove();
            removed = true;
            break;
          }
          testParent = testParent.parent;
        }
        if (removed)
          break;
      }
    }

    //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();
    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);
        groupNode.insert(node, groupNode.getChildCount());
      }
    }
  }

  /**
   * 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) {

    Treeable[] transformations = getLeafmostSelectedTransformations();
    if (transformations.length != 0 && currentManip != null) {
      currentManip.setTransformation((Transformation)transformations[0]);
    }

    // TODO: maybe use allSameType(Object[]) to handle multiple selection attribute-sheet display?
    attributeHolder.remove(currentAttributesPanel);
    if (getSelection().length == 1) {
      Treeable node = getSelection()[0];
      currentAttributesPanel = node.getAttributePanel(this);
    }
    else {
      currentAttributesPanel = ItemAttributePanel.EMPTY_PANEL;
      System.out.println(getSelection().length);
    }
    attributeHolder.add(currentAttributesPanel, BorderLayout.NORTH);
    
    currentAttributesPanel.invalidate();
    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;
  }

  /**
   * 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();
  }

}
