package pipeline;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.File;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.vecmath.Tuple2f;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3f;

import net.java.games.jogl.GLJPanel;
import pipeline.fragment.FragmentProcessor;
import pipeline.gui.GLView;
import pipeline.gui.PipeView;
import pipeline.misc.Camera;
import pipeline.misc.Texture;
import pipeline.scene.Scene;
import pipeline.scene.SceneBalls;
import pipeline.scene.SceneCube;
import pipeline.scene.SceneMaze;
import pipeline.scene.SceneMesh;
import pipeline.scene.SceneTriangle;
import pipeline.triangle.TriangleProcessor;

/**
 * The class that starts the whole process and ties it together.
 * 
 * @author ags
 */
public class MainFrame extends JFrame implements MouseListener, MouseMotionListener, ActionListener {

  private static final long serialVersionUID = 1L;

  /** The path to append to all image and data files */
  public final static String PATH = "data/";

  /** The width of the canvases */
  public final static int XSIZE = 400;

  /** The height of the canvases */
  public final static int YSIZE = XSIZE;

  /** The currently selected camera */
  protected Camera camera;

  /** A camera designed to orbit the scene at a fixed radius */
  protected Camera cameraOrbit;

  /** A camera designed to fly around the scene */
  protected Camera cameraFly;

  /** The scene currently being rendered */
  protected Scene scene;

  /** The GUI widget rendering the results of our pipeline process */
  protected PipeView pv;

  /** The GUI widget rendering the results of OpenGL's process */
  protected GLView gv;

  /** A timer firing off timed events */
  protected Timer timer;

  /** Selector for the triangle processor */
  protected JComboBox vertexCombo;

  /** Selector for the fragment processor */
  protected JComboBox fragmentCombo;

  /** Selector for the current scene */
  protected JComboBox sceneCombo;

  /** Selector for the current texture */
  protected JComboBox textureCombo;

  /** Selector for the current camera */
  protected JComboBox cameraCombo;

  /** Default constructor */
  public MainFrame() {

    super("Pipeline");

    // Handle closing the frame
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    // Fix the window size cus we are cheap for now
    setResizable(false);

    // Set up the views
    pv = new PipeView(XSIZE);
    GLJPanel canvas = pv.getCanvas();
    canvas.addMouseListener(this);
    canvas.addMouseMotionListener(this);

    gv = new GLView(XSIZE);
    canvas = gv.getCanvas();
    canvas.addMouseListener(this);
    canvas.addMouseMotionListener(this);

    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(pv, BorderLayout.WEST);
    getContentPane().add(gv, BorderLayout.EAST);

    // Do some initialization
    timer = new Timer((int) (1.0 / 15 * 1000), this);

    cameraOrbit = new Camera(new Vector3f(3.0f, 4.0f, 5.0f), new Vector3f(0.0f, 0.0f, 0.0f), new Vector3f(0.0f, 1.0f, 0.0f), 1.0f, 10.0f, 0.6f);
    cameraFly = new Camera(new Vector3f(-1.5f, 0.0f, -5.0f), new Vector3f(-1.5f, 0.0f, 0.0f), new Vector3f(0.0f, 1.0f, 0.0f), 0.25f, 20.0f, 0.3f);
    pv.setCamera(cameraOrbit);
    gv.setCamera(cameraOrbit);
    camera = cameraOrbit;

    JPanel labelPanel = new JPanel();
    labelPanel.setLayout(new BorderLayout());
    labelPanel.add(new JLabel("Software"), BorderLayout.WEST);
    labelPanel.add(new JLabel("OpenGL"), BorderLayout.EAST);
    getContentPane().add(labelPanel, BorderLayout.NORTH);

    sceneCombo = new JComboBox(new Scene[] { new SceneTriangle(), new SceneBalls(), new SceneCube(), new SceneMesh(new File(PATH + "ship1.msh")), new SceneMesh(new File(PATH + "ship2.msh")),
        new SceneMaze() });

    textureCombo = new JComboBox(new Texture[] { new Texture(new File(PATH + "chessboard.png")), new Texture(new File(PATH + "chessboard-R.png")), new Texture(new File(PATH + "ship1.png")),
        new Texture(new File(PATH + "ship2.png")), new Texture(new File(PATH + "brick.png")) });

    cameraCombo = new JComboBox(new String[] { "Orbit Camera", "Flythrough" });
    vertexCombo = new JComboBox(TriangleProcessor.classes);
    fragmentCombo = new JComboBox(FragmentProcessor.classes);

    vertexCombo.addActionListener(this);
    fragmentCombo.addActionListener(this);
    sceneCombo.addActionListener(this);
    textureCombo.addActionListener(this);
    cameraCombo.addActionListener(this);

    sceneCombo.setSelectedIndex(0);
    textureCombo.setSelectedIndex(0);

    JPanel controlPanel = new JPanel();
    controlPanel.add(vertexCombo);
    controlPanel.add(fragmentCombo);
    controlPanel.add(sceneCombo);
    controlPanel.add(textureCombo);
    controlPanel.add(cameraCombo);

    getContentPane().add(controlPanel, BorderLayout.SOUTH);
    scene = (Scene) sceneCombo.getSelectedItem();
    scene.setTexture((Texture) textureCombo.getSelectedItem());
    pv.setScene(scene);
    gv.setScene(scene);

    pack();
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    setLocation((int) (screenSize.getWidth() / 2 - getWidth() / 2), (int) (screenSize.getHeight() / 2 - getHeight() / 2));
  }

  protected boolean listening = true;

  /**
   * @see ActionListener#actionPerformed(ActionEvent)
   */
  public void actionPerformed(ActionEvent e) {

    if (listening == false)
      return;

    Object src = e.getSource();

    if (button1Pressed && (src == timer) && (camera == cameraFly)) {
      listening = false;
      mouseDelta.set(currentMousePoint);
      mouseDelta.sub(lastMousePoint);
      mouseDelta.scale(0.08f);
      camera.panDolly(mouseDelta, button3Pressed);
      // System.out.println("panDollying: " + mouseDelta);
      pv.refresh();
      gv.refresh();
      listening = true;
      return;
    }
    if (src == timer)
      return;

    if (src == textureCombo || src == sceneCombo) {
      scene = (Scene) sceneCombo.getSelectedItem();
      scene.setTexture((Texture) textureCombo.getSelectedItem());
      pv.setScene(scene);
      gv.setScene(scene);
    }

    if (src == cameraCombo) {
      Object srcItem = ((JComboBox) src).getSelectedItem();
      if (srcItem.equals("Orbit Camera")) {
        pv.setCamera(cameraOrbit);
        gv.setCamera(cameraOrbit);
        camera = cameraOrbit;
        timer.stop();
      }
      else {
        pv.setCamera(cameraFly);
        gv.setCamera(cameraFly);
        camera = cameraFly;
        timer.start();
      }
    }

    Class fpClass = (Class) fragmentCombo.getSelectedItem();
    Class tpClass = (Class) vertexCombo.getSelectedItem();

    pv.configure(fpClass, tpClass);
    gv.configure(fpClass, tpClass);
    pv.refresh();
    gv.refresh();
  }

  // MouseListener interface

  protected Vector2f lastMousePoint = new Vector2f();

  protected Vector2f currentMousePoint = new Vector2f();

  protected boolean button1Pressed = false;

  protected boolean button3Pressed = false;

  protected Vector2f mouseDelta = new Vector2f();

  /**
   * @see MouseListener#mousePressed(MouseEvent)
   */
  public void mousePressed(MouseEvent e) {

    lastMousePoint.set(e.getX(), e.getY());
    windowToViewport(lastMousePoint);
    button1Pressed = flagSet(e, MouseEvent.BUTTON1_DOWN_MASK);
    button3Pressed = flagSet(e, MouseEvent.BUTTON3_DOWN_MASK) || flagSet(e, MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.SHIFT_DOWN_MASK);

  }

  /**
   * @see MouseListener#mouseReleased(MouseEvent)
   */
  public void mouseReleased(MouseEvent e) {

    button1Pressed = flagSet(e, MouseEvent.BUTTON1_DOWN_MASK);
    button3Pressed = flagSet(e, MouseEvent.BUTTON3_DOWN_MASK) || flagSet(e, MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.SHIFT_DOWN_MASK);
  }

  private static void windowToViewport(Tuple2f p) {

    p.set((2.0f * p.x - XSIZE) / YSIZE, (2.0f * (YSIZE - p.y - 1) - YSIZE) / YSIZE);
  }

  // MouseMotionListener interface

  static boolean flagSet(MouseEvent e, int flag) {

    return (e.getModifiersEx() & flag) == flag;
  }

  /**
   * @see MouseMotionListener#mouseDragged(MouseEvent)
   */
  public void mouseDragged(MouseEvent e) {

    if (camera == cameraOrbit) {
      mouseDelta.set(e.getX(), e.getY());
      windowToViewport(mouseDelta);

      mouseDelta.sub(lastMousePoint);

      if (flagSet(e, MouseEvent.BUTTON1_DOWN_MASK) && !flagSet(e, MouseEvent.SHIFT_DOWN_MASK))
        camera.orbit(mouseDelta);
      else if (flagSet(e, MouseEvent.BUTTON3_DOWN_MASK) || flagSet(e, MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.SHIFT_DOWN_MASK))
        camera.dolly(mouseDelta.y);

      lastMousePoint.set(e.getX(), e.getY());
      windowToViewport(lastMousePoint);
      pv.refresh();
      gv.refresh();
    }
    else {
      currentMousePoint.set(e.getX(), e.getY());
      windowToViewport(currentMousePoint);
    }
  }

  // unused bits of MouseListener interface
  /**
   * @see MouseListener#mouseClicked(MouseEvent)
   */
  public void mouseClicked(MouseEvent e) {

  }

  /**
   * @see MouseListener#mouseEntered(MouseEvent)
   */
  public void mouseEntered(MouseEvent e) {

  }

  /**
   * @see MouseListener#mouseExited(MouseEvent)
   */
  public void mouseExited(MouseEvent e) {

  }

  // unused bits of MouseMotionListener interface
  /**
   * @see MouseMotionListener#mouseMoved(MouseEvent)
   */
  public void mouseMoved(MouseEvent e) {

  }

  /**
   * The main method. Just creates a MainFrame and shows it.
   * 
   * @param args
   */
  public static void main(String[] args) {

    try {
      (new MainFrame()).show();
    }
    catch (UnsatisfiedLinkError ule) {
      System.out.println("Unsatisfied Link Error.");
      System.out.print("java.library.path=");
      System.out.println(System.getProperty("java.library.path"));
      System.out.println(ule);
      ule.printStackTrace();
    }
  }

}