package pipeline.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.JPanel;

import net.java.games.jogl.GL;
import net.java.games.jogl.GLCapabilities;
import net.java.games.jogl.GLDrawable;
import net.java.games.jogl.GLDrawableFactory;
import net.java.games.jogl.GLEventListener;
import net.java.games.jogl.GLJPanel;
import pipeline.fragment.TexturedFP;
import pipeline.fragment.TexturedPhongFP;
import pipeline.fragment.TrivialColorFP;
import pipeline.misc.Camera;
import pipeline.scene.Scene;
import pipeline.triangle.ConstColorTP;
import pipeline.triangle.FlatShadedTP;
import pipeline.triangle.TexturedFragmentShadedTP;
import pipeline.triangle.TexturedTP;

/**
 * This class is a swing widget that will display the OpenGL implementation of
 * what is going on in the pipeline.
 * 
 * @author ags
 */
public class GLView extends JPanel implements GLEventListener {

  private static final float[] WHITE = new float[] {1.0f, 1.0f, 1.0f};

  /** The "image" where OpenGL will draw its results */
  protected GLJPanel canvas;

  /** The camera being used to render the scene */
  protected Camera camera;

  /** The scene being rendered */
  protected Scene scene;
  
  /**
   * Default constructor - sets the height and width of the OpenGL canvas with
   * the same parameter.
   * 
   * @param sizei The height and width of the new canvas.
   */
  public GLView(int sizei) {

    // add canvas
    GLCapabilities capabilities = new GLCapabilities();
    // Necessary, as far as I know, to make GLJPanel work. ags
    capabilities.setDoubleBuffered(false);
    canvas = GLDrawableFactory.getFactory().createGLJPanel(capabilities);
    canvas.addGLEventListener(this);

    setSize(sizei, sizei);
    setPreferredSize(new Dimension(sizei, sizei));

    setLayout(new BorderLayout());
    add(canvas, BorderLayout.CENTER);
  }

  /**
   * @return Returns the canvas.
   */
  public GLJPanel getCanvas() {

    return this.canvas;
  }

  /**
   * Sets the camera for the OpenGL view to use.
   * 
   * @param c The new OpenGL camera.
   */
  public void setCamera(Camera c) {

    camera = c;
  }

  /**
   * Sets the scene for the OpenGL view to render.
   * 
   * @param s The scene to render.
   */
  public void setScene(Scene s) {

    scene = s;
  }

  /** Used to update the GL settings during display. */
  protected Class fpClass;

  /** Used to update the GL settings during display. */
  protected Class tpClass;

  /** Used to update the GL settings during display. */
  protected boolean classesChanged = false;

  /**
   * Configures the OpenGL view to use the given fragment and triangle
   * processors. Actually, since OpenGL won't use them at all, it just attempts
   * to match their indicated capabilities as best it can.
   * 
   * @param fp The fragment processor class.
   * @param tp The triangle processor class.
   */
  public void configure(Class fp, Class tp) {

    classesChanged = true;
    fpClass = fp;
    tpClass = tp;
  }

  /**
   * @see GLEventListener#init(GLDrawable)
   */
  public void init(GLDrawable d) {

    GL gl = d.getGL();
    gl.glDepthFunc(GL.GL_LESS);
    gl.glDisable(GL.GL_DEPTH_TEST);
    gl.glCullFace(GL.GL_BACK);
    gl.glEnable(GL.GL_CULL_FACE);
    gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
    gl.glEnable(GL.GL_NORMALIZE);
    gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
    gl.glShadeModel(GL.GL_SMOOTH);

    gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);

    gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP);
    gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP);
    gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
    gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);

    gl.glLightModeli(GL.GL_LIGHT_MODEL_COLOR_CONTROL, GL.GL_SEPARATE_SPECULAR_COLOR);
    gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, new float[] { 0.1f, 0.1f, 0.1f });

    gl.glEnable(GL.GL_LIGHT0);
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, new float[] { 1.0f, 1.0f, 1.0f, 1.0f });
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, new float[] { 0.4f, 0.4f, 0.4f, 1.0f });
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, new float[] { 1f, 1f, 1f, 0.0f });

    gl.glMateriali(GL.GL_FRONT, GL.GL_SHININESS, 40);
    gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, new float[] { 1f, 1f, 1f });

    gl.glClearColor(0, 0, 0, 1);

    gl.glDisable(GL.GL_LIGHTING);
    gl.glDisable(GL.GL_COLOR_MATERIAL);
    gl.glDisable(GL.GL_DEPTH_TEST);
    gl.glDisable(GL.GL_TEXTURE_2D);
  }

  /**
   * @see GLEventListener#display(GLDrawable)
   */
  public void display(GLDrawable d) {

    GL gl = d.getGL();

    if (classesChanged) {
      if (tpClass == ConstColorTP.class || tpClass == TexturedTP.class) {
        gl.glDisable(GL.GL_LIGHTING);
        gl.glDisable(GL.GL_COLOR_MATERIAL);
        gl.glShadeModel(GL.GL_FLAT);
      }
      else if (tpClass == TexturedFragmentShadedTP.class) {
        gl.glEnable(GL.GL_LIGHTING);
        gl.glDisable(GL.GL_COLOR_MATERIAL);
        gl.glShadeModel(GL.GL_SMOOTH);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, WHITE);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, WHITE);
      }
      else {
        gl.glEnable(GL.GL_LIGHTING);
        gl.glEnable(GL.GL_COLOR_MATERIAL);
        gl.glColorMaterial(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE);
        if (tpClass != FlatShadedTP.class) {
          gl.glShadeModel(GL.GL_SMOOTH);
        }
        else {
          gl.glShadeModel(GL.GL_FLAT);
        }
      }

      if (fpClass == TrivialColorFP.class) {
        gl.glDisable(GL.GL_DEPTH_TEST);
        gl.glDisable(GL.GL_TEXTURE_2D);
      }
      else if (fpClass == TexturedFP.class || fpClass == TexturedPhongFP.class) {
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glEnable(GL.GL_TEXTURE_2D);
      }
      else {
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glDisable(GL.GL_TEXTURE_2D);
      }

      classesChanged = false;
    }

    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
    gl.glMatrixMode(GL.GL_MODELVIEW);
    camera.setProjection(d);
    gl.glViewport(0, 0, canvas.getWidth(), canvas.getHeight());
    camera.setup(d);
    scene.render(d);
  }

  /**
   * @see GLEventListener#displayChanged(GLDrawable, boolean, boolean)
   */
  public void displayChanged(GLDrawable arg0, boolean arg1, boolean arg2) {

    canvas.repaint();
  }

  /**
   * Here so that the MainFrame can manually refresh the canvas.
   */
  public void refresh() {

    canvas.repaint();
  }

  // Unused interface bits

  /**
   * @see GLEventListener#reshape(GLDrawable, int, int, int, int)
   */
  public void reshape(GLDrawable arg0, int x, int y, int width, int height) {

  }

}
