package org.waltermilner;

import com.sun.opengl.util.Animator;
import com.sun.opengl.util.BufferUtil;
import com.sun.opengl.util.GLUT;
import java.awt.Frame;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.nio.FloatBuffer;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;


public class LightingWithVector implements GLEventListener, KeyListener, MouseMotionListener, MouseWheelListener {

    static Animator animator = null;
    private GLU glu = new GLU();
    private GLUT glut = new GLUT();
    private WMVector camera = new WMVector(1.0f, 3.0f, 5.0f);
    private WMVector lookAt = new WMVector(0.0f, 0.0f, 0.0f);
    private WMVector up = new WMVector(0.0f, 1.0f, 0.0f);
    private int mouseX,  mouseY;
    private float forwardSpeed = 0.0f;
    private float rotSpeed = 0.0f;
    private float pitchSpeed = 0.0f;
    // key states
    boolean keyA = false; // means not currently down
    boolean keyD = false;
    boolean keyW = false;
    boolean keyS = false;
    boolean keyE = false;
    boolean keyF = false;
    private FloatBuffer whiteMaterial,  blackMaterial,  emissiveMaterial;
    private FloatBuffer light_position;
    private float lightX = 4.0f;
    private float lightY = 1.0f;
    private float lightZ = 0.0f;

    private void drawSheet(GL gl) {
        boolean flag = true;
        float[] n = {0.0f, 1.0f, 0.0f};
        for (int i = -10; i < 11; i++) {
            for (int j = -10; j < 11; j++) {
                if (flag) {
                    gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, whiteMaterial);
                } else {
                    gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, blackMaterial);
                }
                flag = !flag;
                gl.glBegin(GL.GL_QUADS);

                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i, 0, j);
                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i, 0, j - 1);
                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i - 1, 0, j - 1);
                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i - 1, 0, j);
                gl.glEnd();
            }
        }
    }



    public void display(GLAutoDrawable gLDrawable) {
        processKeys();
        boolean stopped;
        if (forwardSpeed > 0.0f) {
            forwardSpeed -= 1e-6f;
        }
        if (forwardSpeed < 0.0f) {
            forwardSpeed += 1e-5f;
        }
        if (forwardSpeed < 1e-8 && forwardSpeed > -1e-8) {
            stopped = true;
        } else {
            stopped = false;
        }
        if (!stopped) {
            moveForward();
        }
        boolean rotStopped;
        if (rotSpeed > 0.0f) {
            rotSpeed -= 1e-5f;
        }
        if (rotSpeed < 0.0f) {
            rotSpeed += 1e-5f;
        }
        if (rotSpeed < 1e-8 && rotSpeed > -1e-8) {
            rotStopped = true;
        } else {
            rotStopped = false;
        }
        if (!rotStopped) {
            turnRight();
        }
        boolean pitchStopped;
        if (pitchSpeed > 0.0f) {
            pitchSpeed -= 1e-5f;
        }
        if (pitchSpeed < 0.0f) {
            pitchSpeed += 1e-5f;
        }
        if (pitchSpeed < 1e-8 && pitchSpeed > -1e-8) {
            pitchStopped = true;
        } else {
            pitchStopped = false;
        }
        if (!pitchStopped) {
            lookUp();
        }
        final GL gl = gLDrawable.getGL();
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        gl.glLoadIdentity();									// Reset The View    

        glu.gluLookAt(camera.getX(), camera.getY(), camera.getZ(), lookAt.getX(), lookAt.getY(), lookAt.getZ(), up.getX(), up.getY(), up.getZ());

        
        glut.glutSolidSphere(1.0, 20, 20);
        
        drawSheet(gl);
        gl.glTranslated(lightX, lightY, lightZ);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, light_position);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, emissiveMaterial);
        glut.glutSolidSphere(1.0, 20, 20);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, blackMaterial);
    }

    public void displayChanged(GLAutoDrawable gLDrawable, boolean modeChanged, boolean deviceChanged) {
    }

    private FloatBuffer make(float[] values) {
        FloatBuffer floatBuffer = BufferUtil.newFloatBuffer(values.length);
        for (int i = 0; i < values.length; i++) {
            floatBuffer.put(values[i]);
        }
        floatBuffer.rewind();
        return floatBuffer;
    }

    public void init(GLAutoDrawable gLDrawable) {
        final GL gl = gLDrawable.getGL();

        whiteMaterial = make(new float[]{1.0f, 1.0f, 1.0f, 1.0f});
        blackMaterial = make(new float[]{0.0f, 0.0f, 0.0f, 1.0f});
        emissiveMaterial = make(new float[]{1.0f, 1.0f, 0.0f, 1.0f});

        FloatBuffer mat_shininess = make(new float[]{100.0f});

        FloatBuffer lmodel_ambient = make(new float[]{0.2f, 0.2f, 0.2f, 1.0f});
        gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);

        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, whiteMaterial);
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, mat_shininess);
        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, whiteMaterial);
        // light is positional
        light_position = make(new float[]{2.0f, 2.0f, 0.0f, 1.0f});
        FloatBuffer light_diffuse = make(new float[]{1.0f, 0.0f, 0.0f, 1.0f});
        FloatBuffer light_specular = make(new float[]{0.0f, 0.0f, 1.0f, 1.0f});
       
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, light_diffuse);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, light_specular);

        gl.glEnable(GL.GL_LIGHTING);
        gl.glEnable(GL.GL_LIGHT0);
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glClearDepth(1.0f);                      // Depth Buffer Setup
        gl.glDepthFunc(GL.GL_LEQUAL);					

        gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);	

        gLDrawable.addKeyListener(this);
        gLDrawable.addMouseMotionListener(this);
        gLDrawable.addMouseWheelListener(this);

    }

    public void reshape(GLAutoDrawable gLDrawable, int x, int y, int width, int height) {
        GL gl = gLDrawable.getGL();

        if (height <= 0) // avoid a divide by zero error!
        {
            height = 1;
        }
        final float h = (float) width / (float) height;
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(45.0f, h, 1.0, 40.0);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    // move the camera forward, in the direction it is looking at
    private void moveForward() {
        WMVector lookDir = lookAt.sub(camera).normalise();
        WMVector movement = lookDir.times(forwardSpeed);
        camera = camera.add(movement);
        lookAt = lookAt.add(movement);

    }

    // change the lookAt point to look right 
    private void turnRight() {
        // make vector from where we are to point we are looking at
        WMVector lookDir = lookAt.sub(camera);
        // find magnitude of this =  how far away we are
        float lookMag = lookDir.getMag();
        // form vector at right angles to camera up and direction we are looking
        WMVector c = up.cross(lookDir);
        // form vector temp = lookDir - rotSpeed * c
        WMVector temp = lookDir.sub(c.times(rotSpeed));
        // alter magnitude to what it was
        temp = temp.setMag(lookMag);
        // set lookAt =camera + temp
        lookAt = camera.add(temp);
    }

        // change the lookAt point to look up 
    private void lookUp() {
        // make vector from where we are to point we are looking at
        WMVector lookDir = lookAt.sub(camera);
        // find magnitude of this =  how far away we are
        float lookMag = lookDir.getMag();
        // vector at right angle to lookdir and up - needed to reset up
        WMVector sideWays = up.cross(lookDir);
        // form vector temp = lookDir + 0.05up
        WMVector temp = lookDir.add(up.times(pitchSpeed));
        // temp is new lookDir - restore magnitude
        temp = temp.setMag(lookMag);
        // set lookAt =camera + temp
        lookAt = camera.add(temp);
        lookDir = lookAt.sub(camera);
        // make sure up is at right angles to new look at 
        up = lookDir.cross(sideWays);
        up = up.normalise();


    }

    private void processKeys() {
        if (keyA) {
            rotSpeed -= 1e-4f;
        }
        if (keyD) {
            rotSpeed += 1e-4f;
        }
        if (keyW) {
            forwardSpeed += 1e-4f;
        }
        if (keyS) {
            forwardSpeed -= 1e-4f;
        }
        if (keyE) {
            pitchSpeed += 1e-4f;
        }
        if (keyF) {
            pitchSpeed -= 1e-4f;
        }
    }

    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_W:
                keyW = true;
                break;
            case KeyEvent.VK_S:
                keyS = true;
                break;
            case KeyEvent.VK_D:
                keyD = true;
                break;
            case KeyEvent.VK_A:
                keyA = true;
                break;
            case KeyEvent.VK_E:
                keyE = true;
                break;
            case KeyEvent.VK_F:
                keyF = true;
                break;
            case KeyEvent.VK_T:
                lightX += 0.05f;
                break;
            case KeyEvent.VK_Z:
                lightZ += 0.05f;
                break;
            case KeyEvent.VK_ESCAPE:
                animator.stop();
                System.exit(0);
                break;
        }
    }

    public void keyReleased(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_W:
                keyW = false;
                break;
            case KeyEvent.VK_S:
                keyS = false;
                break;
            case KeyEvent.VK_D:
                keyD = false;
                break;
            case KeyEvent.VK_A:
                keyA = false;
                break;
            case KeyEvent.VK_E:
                keyE = false;
                break;
            case KeyEvent.VK_F:
                keyF = false;
                break;

        }
    }

    public void keyTyped(KeyEvent e) {
    }

    /** Program's main entry point
     * @param args command line arguments.
     */
    public static void main(String[] args) {
        Frame frame = new Frame("Lighting with Vector");
        GLCanvas canvas = new GLCanvas();
        canvas.addGLEventListener(new LightingWithVector());
        frame.add(canvas);
        frame.setSize(640, 480);
        animator = new Animator(canvas);
        frame.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {
                animator.stop();
                System.exit(0);
            }
        });
        frame.setVisible(true);
        animator.start();
        canvas.requestFocus();
    }

    public void mouseDragged(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
    
        if (x > mouseX) 
        rotSpeed += 1e-4f;   
        if (x<mouseX) 
        rotSpeed -= 1e-4f;
        if (y < mouseY) 
        pitchSpeed += 1e-4f;    
        if (y>mouseY) 
         pitchSpeed -= 1e-4f;
        mouseX = x;
        mouseY = y;


    }

    public void mouseMoved(MouseEvent e) {
    }

    public void mouseWheelMoved(MouseWheelEvent e) {
        int notches = e.getWheelRotation();
        forwardSpeed += notches * 1e-3f;
    }
}


