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.IOException;
import java.lang.reflect.Constructor;

import javax.swing.AbstractAction;
import javax.swing.Icon;
import javax.swing.ImageIcon;
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.JSplitPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.tree.DefaultMutableTreeNode;
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 {

  private static final long serialVersionUID = 1L;

  protected static final int DEFAULT_WIDTH = 800;
  protected static final int DEFAULT_HEIGHT = 600;
  protected static final Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize();

  private static final String SAVE_AS_MENU_TEXT = "Save As";
  private static final String OPEN_MENU_TEXT = "Open";
  private static final String EXIT_MENU_TEXT = "Exit";
  private static final String GROUP_MENU_TEXT = "Group selected";
  private static final String CUT_NODE_MENU_TEXT = "Cut";
  private static final String COPY_NODE_MENU_TEXT = "Copy";
  private static final String PASTE_NODE_MENU_TEXT = "Paste";
  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";

  protected JFileChooser fileChooser;
  protected DefaultTreeModel transformationTree;
  protected JTree transformationTreeView;
  protected JPanel allGLViews;
  protected JPanel currentView;

  protected GLView pView;
  protected GLView xOrtho;
  protected GLView yOrtho;
  protected GLView zOrtho;

  protected PerspectiveCamera pViewCam;
  protected OrthoCamera xOrthoCam;
  protected OrthoCamera yOrthoCam;
  protected OrthoCamera zOrthoCam;

  protected boolean linkedViews = true;

  public MainFrame() {

    super("CS 465 Modeler");
    JPopupMenu.setDefaultLightWeightPopupEnabled(false);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Container cp = getContentPane();
    cp.setLayout(new BorderLayout());

    cp.add(createCenterPanel(), BorderLayout.CENTER);
    fileChooser = new JFileChooser();

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

  protected JComponent createCenterPanel() {

    JSplitPane toReturn = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false);

    toReturn.setLeftComponent(createLeftComponent());
    toReturn.setRightComponent(createRightComponent());

    toReturn.setOneTouchExpandable(true);
    toReturn.setResizeWeight(0.2);
    return toReturn;
  }

  protected void createActions() {

    BasicAction group = new BasicAction(GROUP_MENU_TEXT, this);

    BasicAction cutNode = new BasicAction(CUT_NODE_MENU_TEXT, this);
    BasicAction copyNode = new BasicAction(COPY_NODE_MENU_TEXT, this);
    BasicAction pasteNode = new BasicAction(PASTE_NODE_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 exit = new BasicAction(EXIT_MENU_TEXT, this);

    group.setAcceleratorKey(KeyEvent.VK_G, KeyEvent.CTRL_MASK);

    cutNode.setAcceleratorKey(KeyEvent.VK_X, KeyEvent.CTRL_MASK);
    copyNode.setAcceleratorKey(KeyEvent.VK_C, KeyEvent.CTRL_MASK);
    pasteNode.setAcceleratorKey(KeyEvent.VK_V, KeyEvent.CTRL_MASK);

    saveAs.setAcceleratorKey(KeyEvent.VK_A, KeyEvent.CTRL_MASK);
    open.setAcceleratorKey(KeyEvent.VK_O, KeyEvent.CTRL_MASK);
    exit.setAcceleratorKey(KeyEvent.VK_Q, KeyEvent.CTRL_MASK);

    JMenuBar bar = new JMenuBar();
    JMenu menu;

    menu = new JMenu("File");
    menu.setMnemonic('F');
    menu.add(new JMenuItem(open));
    menu.add(new JMenuItem(saveAs));
    menu.addSeparator();
    menu.add(new JMenuItem(exit));
    bar.add(menu);

    menu = new JMenu("Edit");
    menu.setMnemonic('E');
    menu.add(new JMenuItem(group));
    menu.addSeparator();
    menu.add(new JMenuItem(cutNode));
    menu.add(new JMenuItem(copyNode));
    menu.add(new JMenuItem(pasteNode));
    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(cutNode));
    p.add(new JMenuItem(copyNode));
    p.add(new JMenuItem(pasteNode));
    p.addSeparator();
    menu = new JMenu("Add Shape");
    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));
    p.add(menu);

    transformationTreeView.addMouseListener(new PopupListener(p));

    // Special steps to override default operations on CTRL- key events
    // TODO group key?
    transformationTreeView.registerKeyboardAction(this, COPY_NODE_MENU_TEXT, KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
    transformationTreeView.registerKeyboardAction(this, CUT_NODE_MENU_TEXT, KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
    transformationTreeView.registerKeyboardAction(this, PASTE_NODE_MENU_TEXT, KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
    transformationTreeView.registerKeyboardAction(this, SAVE_AS_MENU_TEXT, KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
  }

  protected JComponent createLeftComponent() {

    JSplitPane toReturn = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false);
    JPanel temp;

    // Create the tree models
    transformationTree = new DefaultTreeModel(new DefaultMutableTreeNode(null /*
                                                                               * new
                                                                               * Transformation("Nothing")
                                                                               */));

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

    // 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);
    temp = new JPanel(new BorderLayout());
    temp.add(new JLabel(" Item Attributes"), BorderLayout.NORTH);
    temp.add(new JScrollPane()); // TODO placeholder properysheet
    toReturn.setRightComponent(temp);

    // Set some nice options on the split pane
    toReturn.setOneTouchExpandable(true);
    toReturn.setResizeWeight(0.8);

    // Return
    return toReturn;
  }

  protected JComponent createRightComponent() {

    currentView = new JPanel(new GridLayout(1, 1));
    allGLViews = new JPanel(new GridLayout(2, 2, 10, 10));
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) 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(new DefaultMutableTreeNode(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(new DefaultMutableTreeNode(Light.makeNewLight(true)), root, root.getChildCount());

    return currentView;
  }

  public void actionPerformed(ActionEvent e) {

    String cmd = e.getActionCommand();
    if (cmd == null) {
      return;
    }
    else if (cmd.equals(GROUP_MENU_TEXT)) {
      // TODO do something
      groupSelected(transformationTreeView.getSelectionPaths(), "Group");
    }
    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(EXIT_MENU_TEXT)) {
      System.exit(0);
    }
  }

  protected void openTree() {

    // TODO
    refresh();
  }

  protected void saveTreeAs() {

    // TODO
  }

  protected void removeCurrentNode() {

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

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

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

    refresh();
  }

  protected void groupSelected(TreePath[] selected, String name) {

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

    DefaultMutableTreeNode groupNode = new DefaultMutableTreeNode(new Transformation(name));
    DefaultMutableTreeNode groupParent = (DefaultMutableTreeNode) ((DefaultMutableTreeNode) selected[0].getLastPathComponent()).getParent();
    int groupIdx = groupParent.getIndex((DefaultMutableTreeNode) (selected[0].getLastPathComponent()));
    synchronized (transformationTree) {
      for (int i = 0; i < selected.length; i++) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) selected[i].getLastPathComponent();
        if (node.getUserObject() instanceof Light || node.getUserObject() instanceof Camera)
          return;
        transformationTree.removeNodeFromParent(node);
        groupNode.add(node);
      }

      transformationTree.insertNodeInto(groupNode, groupParent, groupIdx);
    }
  }

  protected void addNewLight() {

    Light light = Light.makeNewLight(true);
    if (light == null)
      return;
    DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(light);
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) transformationTree.getRoot();

    synchronized (transformationTree) {
      transformationTree.insertNodeInto(newNode, root, root.getChildCount());
    }

    refresh();
  }

  protected void addNewShape(Class c) {

    TreePath path = transformationTreeView.getSelectionPath();
    DefaultMutableTreeNode selected;

    if (path == null) {
      selected = (DefaultMutableTreeNode) transformationTree.getRoot();
    }
    else {
      selected = (DefaultMutableTreeNode) path.getLastPathComponent();
      if (!(selected.getUserObject() instanceof Transformation))
        selected = (DefaultMutableTreeNode) transformationTree.getRoot();
    }

    try {
      Treeable o = (Treeable) c.newInstance();
      DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(o);
      DefaultMutableTreeNode newTransform = new DefaultMutableTreeNode(new Transformation(o.toString()));
      synchronized (transformationTree) {
        transformationTree.insertNodeInto(newTransform, selected, selected.getChildCount());
        transformationTree.insertNodeInto(newNode, newTransform, 0);
      }
    }
    catch (Exception e) {
      showExceptionDialog(e);
    }

    refresh();
  }

  public void trackAllCams(Vector3f motion) {

    pViewCam.translate(motion);
    xOrthoCam.translate(motion);
    yOrthoCam.translate(motion);
    zOrthoCam.translate(motion);
  }

  public void zoomAllOrtho(float d) {

    xOrthoCam.zoom(d);
    yOrthoCam.zoom(d);
    zOrthoCam.zoom(d);
  }

  public void refresh() {

    pView.refresh();
    xOrtho.refresh();
    yOrtho.refresh();
    zOrtho.refresh();
  }

  public void setView(GLView toFocus) {

    if (currentView.getComponent(0) == allGLViews) {
      currentView.remove(0);
      currentView.add(toFocus);
    }
    else {
      currentView.remove(0);
      allGLViews.removeAll();
      allGLViews.add(xOrtho);
      allGLViews.add(yOrtho);
      allGLViews.add(zOrtho);
      allGLViews.add(pView);
      currentView.add(allGLViews);
    }
    validate();
  }

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

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

  }

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

  }

  public class TreeRenderer extends DefaultTreeCellRenderer {

    private static final long serialVersionUID = 1L;

    Icon lightIcon = new ImageIcon("../icons/cam.gif");

    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);
      Object obj = ((DefaultMutableTreeNode) value).getUserObject();
      if (obj instanceof Treeable)
        setIcon(((Treeable) obj).getIcon());
      return this;
    }
  }

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

}
