package com.nightspawn.jgltut;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileReader;
import java.nio.FloatBuffer;
import java.util.Locale;
import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;
import com.sun.opengl.util.GLUT;
import com.sun.opengl.util.Screenshot;

import com.sun.opengl.util.Animator;

/**
 * main class
 */

/**
 * @author nsn
 *
 */
public class JGLtut implements GLEventListener, KeyListener {
	private GL gl;
	private GLU glu = new GLU();
	private GLUT glut = new GLUT();
	private FloatBuffer l0pos;
	private float[] eye = {0.0f, 2.0f, 5.0f};
	private float[] cnt = {0.0f, 0.0f, 0.0f};
	private int rot = 0;
	private String VertexShaderSource, FragmentShaderSource;
	private int VertexShaderHandle, FragmentShaderHandle, ShaderHandle;
	private int width = 0, height = 0;
	boolean print = false;

	public void init(GLAutoDrawable drw) {
		gl = drw.getGL();
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		VertexShaderHandle = gl.glCreateShaderObjectARB(GL.GL_VERTEX_SHADER_ARB);
		String[] srcs = new String[1];
		srcs[0] = VertexShaderSource;
		gl.glShaderSourceARB(VertexShaderHandle, 1, srcs, null);
		FragmentShaderHandle = gl.glCreateShaderObjectARB(GL.GL_FRAGMENT_SHADER_ARB);
		srcs[0] = FragmentShaderSource;
		gl.glShaderSourceARB(FragmentShaderHandle, 1, srcs, null);
		ShaderHandle = gl.glCreateProgramObjectARB();
		gl.glAttachObjectARB(ShaderHandle, VertexShaderHandle);
		gl.glAttachObjectARB(ShaderHandle, FragmentShaderHandle);
		gl.glLinkProgramARB(ShaderHandle);
	}

	public void display(GLAutoDrawable arg0) {
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		gl.glLoadIdentity();
		glu.gluLookAt(eye[0], eye[1], eye[2],
					  cnt[0], cnt[1], cnt[2],
					  0.0,	  1.0,    0.0);
		gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, l0pos);
		gl.glUseProgramObjectARB(ShaderHandle);
	
		gl.glRotatef((float)rot, 0.0f, 1.0f, 0.0f);
		glut.glutSolidTeapot(1);
		
		if (print) {
			printScreen();
			print = false;
		}
			
	}

	public void reshape(GLAutoDrawable arg0, int x, int y, int w, int h) {
		float ratio = (float)w/h;
		width = w;
		height = h;
		// set matrix mode
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity();
		
		// set new viewport an perspective
		gl.glViewport(0, 0, w, h);
		glu.gluPerspective(45, ratio, 1, 1000);
		
		// restore matrix mode
		gl.glMatrixMode(GL.GL_MODELVIEW);
	}

	public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2) {
		// TODO Auto-generated method stub

	}

	public JGLtut() {
		float[] arr = {1.0f, 0.5f, 1.0f, 0.0f};
		l0pos = FloatBuffer.wrap(arr);
		VertexShaderSource = readFile("data/perpixel.avs");
		FragmentShaderSource = readFile("data/perpixel.afs");
	}

	private void rotateView(float angle, float x, float y, float z) {
		float[] pos = new float[3];
	    pos[0] = eye[0] - cnt[0];
	    pos[1] = eye[1] - cnt[1];
	    pos[2] = eye[2] - cnt[2];

	    float[] newPos = new float[3];
	 
	    float cosTheta = (float)Math.cos((double)angle);
	    float sinTheta = (float)Math.sin((double)angle);
	 
	    newPos[0]  = (cosTheta + (1 - cosTheta) * x * x)        * pos[0];
	    newPos[0] += ((1 - cosTheta) * x * y - z * sinTheta)    * pos[1];
	    newPos[0] += ((1 - cosTheta) * x * z + y * sinTheta)    * pos[2];
	 
	    newPos[1]  = ((1 - cosTheta) * x * y + z * sinTheta)    * pos[0];
	    newPos[1] += (cosTheta + (1 - cosTheta) * y * y)        * pos[1];
	    newPos[1] += ((1 - cosTheta) * y * z - x * sinTheta)    * pos[2];
	 
	    newPos[2]  = ((1 - cosTheta) * x * z - y * sinTheta)    * pos[0];
	    newPos[2] += ((1 - cosTheta) * y * z + x * sinTheta)    * pos[1];
	    newPos[2] += (cosTheta + (1 - cosTheta) * z * z)        * pos[2];
	 
	    eye[0] = cnt[0] + newPos[0];
	    eye[1] = cnt[1] + newPos[1];
	    eye[2] = cnt[2] + newPos[2];
	}
	
	private void rotateScene(int angle) {
		rot = (rot+angle)%360;
	}
	
	private void walk(float r) {
		eye[0] += (eye[0] - cnt[0]) * r;
	    eye[1] += (eye[1] - cnt[1]) * r;
	    eye[2] += (eye[2] - cnt[2]) * r;
	}
	
	private void printScreen() {
		String fn = "";
		File f = null;
		for (int i=0; i<1000; i++) {
			fn = String.format(Locale.US, "screen%03d.png", i);
			f = new File(fn);
			if (!f.exists())
				break;
		}
		try {
			Screenshot.writeToFile(f, width, height);
			System.out.println("Screenshot written to " + fn);
		} catch (Exception e) {
			System.out.println("Error writing to " + fn);
			e.printStackTrace();
		} finally {
		}
	}
	
	private String readFile(String filename) {
		try {
			FileReader r = new FileReader(filename);
			StringBuffer sb = new StringBuffer();
			int c = 0;
			while ((c = r.read()) != -1) {
				sb.append((char)c);
			}
			return sb.toString();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
		return null;
	}
	
	public void run() {
		Frame frame = new Frame("JGLtut");
		
		GLCanvas canvas = new GLCanvas();
		canvas.addGLEventListener(this);
		frame.add(canvas);
		frame.setSize(800, 600);
		frame.addKeyListener(this);
	
		final Animator ani = new Animator(canvas);
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				ani.stop();
				System.out.println("exiting...");
				System.exit(0);
			}
		});
		
		frame.setVisible(true);
		ani.start();
	}
	
	public static void main(String[] args) {
		JGLtut tut = new JGLtut();
		tut.run();
	}

	public void keyTyped(KeyEvent arg0) {
	}

	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_A:
			rotateView( 0.3f, 0.0f, 1.0f, 0.0f);
			break;
		case KeyEvent.VK_D:
			rotateView(-0.3f, 0.0f, 1.0f, 0.0f);
			break;
		case KeyEvent.VK_Q:
			rotateScene(2);
			break;
		case KeyEvent.VK_E:
			rotateScene(-2);
			break;
		case KeyEvent.VK_W:
			walk(-0.1f);
			break;
		case KeyEvent.VK_S:
			walk( 0.1f);
			break;
		case KeyEvent.VK_X:
			System.exit(0);
			break;
		case KeyEvent.VK_PRINTSCREEN:
			print = true;
			break;
		}
	}

	public void keyReleased(KeyEvent arg0) {
	}

	
	
}
