/**
 * Anima: A singular journey
 * CS470 Capstone Project
 * Shawn Aldridge
 */


package anima;

import com.sun.opengl.util.Animator;
import com.sun.opengl.util.FPSAnimator;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
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;
import javax.swing.JFrame;
import java.awt.event.*;
import com.sun.opengl.util.GLUT;
import javax.media.opengl.GLCapabilities;
import java.util.LinkedList;
import javax.swing.Timer;




/**
 * Display and controls for first level of Anima
 */
public class Level1 extends JFrame implements GLEventListener {

    GLCanvas canvas;
    float xgap;
    GL gl;
    int avPosition, ballPosition, counter, problem, problemCounter, solution, answer, time;
    float[] columnPosition, rowPosition;
    LinkedList problems, problemBits, rowPositions, solutions;
    Timer timer;
    Sound sound;
    Sound background;
    boolean lost, out;
    int loseTime;

    


    public Level1()
    {
        super();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        canvas = new GLCanvas();


        setUndecorated(true);
        setBounds(400, 400, 800, 600);

        xgap = (float)(10.0 / 14.0);//gap between columns

        //x positions for the falling orbs in each column
        columnPosition = new float[12];
        columnPosition[11] = 1.05f;
        for(int i = 10; i >= 0; i--)
        {
            columnPosition[i] = columnPosition[i+1] + xgap;
        }

        //y positions for the falling orbs in each column
        ballPosition = 0;
        rowPosition = new float[18];
        for(int i = 0; i < 18; i++)
        {
            rowPosition[i] = (float) (9.5 - (i * 0.5));
        }
        
        counter = 0;
        problemCounter = 0;
        problemBits = new LinkedList();
        problems = new LinkedList();
        rowPositions = new LinkedList();
        solutions = new LinkedList();

        sound = new Sound();
        background = new Sound();
        background.play("C:/Users/Owner/Documents/NetBeansProjects/anima/resist.wav");

        GLCapabilities caps = new GLCapabilities();
        caps.setDoubleBuffered(true);
        caps.setHardwareAccelerated(true);

        canvas.setFocusable(true);
        canvas.addMouseListener(new mouseHandler());
        canvas.addKeyListener(new keyHandler());
        add(canvas);


        time = 42;
        lost = false;
        out = false;
        final FPSAnimator animator = new FPSAnimator(canvas, 60);
        InnerHandler timeHandle = new InnerHandler();
        new Timer(2000, (ActionListener)timeHandle).start();

        setLocationRelativeTo(null);
        setVisible(true);
        animator.start();

    }

    /**
     * Sets my intial OpenGL state variables
     * @param drawable
     */
    public void init(GLAutoDrawable drawable) {

        gl = drawable.getGL();
        GLU glu = new GLU();

        // Setup the drawing area and shading mode
        gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        gl.glShadeModel(GL.GL_SMOOTH);
        gl.glEnable(GL.GL_DEPTH_TEST);

        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrtho(0, 10, 0, 10, -10, 10);
        gl.glMatrixMode(GL.GL_MODELVIEW);

    }

    /**
     * Method called when window is resized, included strictly as backup, as application
     * is displayed in full screen
     * @param drawable
     * @param x
     * @param y
     * @param width
     * @param height
     */
    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        gl = drawable.getGL();

        gl.glViewport(0, 0, width, height);

        gl.glMatrixMode(GL.GL_PROJECTION); /* switch matrix mode */
        gl.glLoadIdentity();
        gl.glOrtho(0, 10, 0, 10, -10, 10);
        gl.glMatrixMode(GL.GL_MODELVIEW); /* return to modelview mode */
    }

    /**
     * Draws the falling orbs in the first level using a 180 segment triangle fan
     * @param gl
     * @param position
     * @param y
     */
    private void orbs(GL gl, int position, float y)
    {
        gl.glColor3f(0.0f, 1.0f, 0.0f);
        gl.glBegin(GL.GL_TRIANGLE_FAN);
        for(int i = 0; i < 180; i++)
        {
            float xcoor = (float)(0.1 * Math.cos(i) + columnPosition[position]);
            float ycoor = (float)(0.1 * Math.sin(i) + y);
            gl.glVertex2f(xcoor, ycoor);

            xcoor = (float)(0.1 * Math.cos(i + 0.1) + columnPosition[position]);
            ycoor = (float)(0.1 * Math.sin(i + 0.1) + y);
            gl.glVertex2f(xcoor, ycoor);
        }
        gl.glEnd();
    }

    /**
     * Draws the smaller orb used as an avatar in the first level
     * @param gl
     * @param avPosition
     * @param avY
     */
    private void avatar(GL gl, int avPosition, float avY)
    {
        gl.glBegin(GL.GL_TRIANGLE_FAN);
        for(int i = 0; i < 180; i++)
        {
            float xcoor = (float)(0.05 * Math.cos(i) + columnPosition[avPosition]);
            float ycoor = (float)(0.05 * Math.sin(i) + avY);
            gl.glVertex2f(xcoor, ycoor);

            xcoor = (float)(0.05 * Math.cos(i + 0.1) + columnPosition[avPosition]);
            ycoor = (float)(0.05 * Math.sin(i + 0.1) + avY);
            gl.glVertex2f(xcoor, ycoor);
        }
        gl.glEnd();
    }

    /**
     * Generates a different colored ball just below the current problem
     * in the same column as the avatar to represent the solution the player is generating
     * @param gl
     */
    private void avBall(GL gl, int position)
    {
        
        gl.glColor3f(0.0f, 0.0f, 1.0f);
        gl.glBegin(GL.GL_TRIANGLE_FAN);
        for(int i = 0; i < 180; i++)
        {
            float xcoor = (float)(0.1 * Math.cos(i) + columnPosition[position]);
            float ycoor = (float)(0.1 * Math.sin(i) + rowPosition[ballPosition + 1]);
            gl.glVertex2f(xcoor, ycoor);

            xcoor = (float)(0.1 * Math.cos(i + 0.1) + columnPosition[position]);
            ycoor = (float)(0.1 * Math.sin(i + 0.1) + rowPosition[ballPosition + 1]);
            gl.glVertex2f(xcoor, ycoor);
        }
        gl.glEnd();
    }

    /**
     * Takes as input a number and outputs the bits pattern of that number to be shown
     * as a graphical problem
     * @param gl
     * @param incoming = the problem to be represented
     */
    private void generateProblem(GL gl, int incoming)
    {
        int workingProblem = incoming;
        int bit = 0;
        LinkedList bits = new LinkedList();

        //generate orbs while values still in workingProblem
        while(workingProblem > 0)
        {
            bit = workingProblem % 2;
            bits.add(bit);
            workingProblem = workingProblem / 2;
        }
        problemBits.add(bits);        
    }

    public void loseScreen()
    {
        gl.glRasterPos2f(4.5f, 5.0f);
        GLUT glut = new GLUT();
        glut.glutBitmapString(glut.BITMAP_TIMES_ROMAN_24, "You Lose");
    }

    /**
     * Display method for OpenGL, gets called in a continuous loop by the animator
     * @param drawable
     */
    public void display(GLAutoDrawable drawable) {
        gl = drawable.getGL();
        GLUT glut = new GLUT();


        // Clear the drawing area
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        // Reset the current matrix to the "identity"
        gl.glLoadIdentity();

        //set properties for dividing lines
        gl.glColor3f(0.3f, 0.3f, 1.0f);
        gl.glLineWidth(3.0f);
        gl.glBegin(GL.GL_LINES);

        //draw the dividing lines for each column
        for(int i = 0; i < 13; i++)
        {
            gl.glVertex3f((i + 1) * xgap, 10,0);
            gl.glVertex3f((i + 1) * xgap, 9,0);
        }
        gl.glEnd();

        //draw the column shading to indicate the position of the avatar
        gl.glColor3f(0.0f, 0.1f, 0.0f);
        gl.glBegin(GL.GL_QUADS);
        gl.glVertex3f(columnPosition[avPosition] - (xgap / 2), 10, -1);
        gl.glVertex3f(columnPosition[avPosition] + (xgap / 2), 10, -1);
        gl.glVertex3f(columnPosition[avPosition] + (xgap / 2), -10, -1);
        gl.glVertex3f(columnPosition[avPosition] - (xgap / 2), -10, -1);
        gl.glEnd();


        
        //only generate a problem once every 60 iterations, or roughly once per 2 seconds
        if((problemCounter > 60))
        {
            //generate the decimal value for a given problem in the range of 1-1024
            problem = (int)((Math.random() * 1024) + 1);
            problems.add(problem);
            rowPositions.add(0);
            generateProblem(gl, problem);
            problemCounter = 0;//reset problem counter
            sound.play("C:/Users/Owner/Documents/NetBeansProjects/anima/beep.wav");
        }
        problemCounter++;


        //if we have a problem, display it
        if(problemBits.size() > 0)
        {
            for(int j = 0; j < problems.size(); j++)
            {
                LinkedList temp = (LinkedList)problemBits.get(j);
                for(int i = 0; i < temp.size(); i++)
                {
                    int ball = (Integer)temp.get(i);
                    if(ball == 1) orbs(gl, i , rowPosition[(Integer)rowPositions.get(j)]);
                }
            }
        }

        //if we have a solution, display it
        if(solutions.size() > 0)
        {
            for(int i = 0; i < solutions.size(); i++)
            {
                Integer temp = (Integer)solutions.get(i);
                avBall(gl, temp);
            }
        }

        //change color to red and call avatar display method
        gl.glColor3f(1.0f, 0.0f, 0.0f);
        avatar(gl, avPosition, 1);


        //increment the rowPositions to cause the orbs to fall down the screen
        if((counter > 40) && (ballPosition < 17))
        {
            for(int i = 0; i < rowPositions.size(); i++)
            {
                Integer temp = (Integer)rowPositions.get(i);
                temp++;
                rowPositions.set(i, temp);
            }
            ballPosition++;
            counter = 0;            
        }
        counter++;

        //if we have two problems on the screen generate an answer to match with the solution
        if(problems.size() > 1)
        {
            Integer answerA = (Integer)problems.get(0);
            Integer answerB = (Integer)problems.get(1);

            answer = answerA + answerB;
        }


        if(ballPosition > 16)
        {
            loseScreen();
            if(!(lost))
            {
                loseTime = time;
                lost = true;
            }
            if(time + 5 < loseTime)
            {
                //this.dispose();
                //background.stop();
                out = true;
            }
        }else{
            //draw time in at bottom corner of screen
            gl.glRasterPos3f(0.2f,0.2f,0);
            glut.glutBitmapString(glut.BITMAP_HELVETICA_18, "Time: " + time);
        }

        if(time < 0)
        {
            gl.glRasterPos2f(4.5f, 5.0f);
            glut.glutBitmapString(glut.BITMAP_TIMES_ROMAN_24, "Evolving");
        }
        if(time < -5)out = true;


        // Flush all drawing operations to the graphics card
        gl.glFlush();

    }

    //required method for OpenGL, not used in this implementation
    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}

    /**
     * Inner class to handle generated events
     */
    class InnerHandler implements ActionListener
    {

        public InnerHandler()
        {

        }

        public void actionPerformed(ActionEvent e)
        {
            time--;
        }

    }//end class InnerHandler

    /**
     * Inner class to handle keyboard events
     */
    class keyHandler implements KeyListener
    {
        public void keyReleased(KeyEvent e){}

        public void keyPressed(KeyEvent e)
        {

            //allow player to move avatar left and right
            if(e.getKeyCode() == e.VK_LEFT)
            {
                if(avPosition < 11) avPosition += 1;
            }

            if(e.getKeyCode() == e.VK_RIGHT)
            {
                if(avPosition > 0) avPosition -= 1;
            }

            //generate a segment of a solution and display a marker on screen
            if(e.getKeyCode() == e.VK_SPACE)
            {
                solutions.add(avPosition);
                if(avPosition == 0) solution += 1;
                else solution += (Math.pow(2, avPosition));
            }

            //if enter is pressed check solution against the sum of the two closest problems
            if(e.getKeyCode() == e.VK_ENTER)
            {
                if(ballPosition > 3)
                {
                    //System.out.println("Answer = " + answer);
                    //System.out.println("Solution = " + solution);
                    //if solution is correct remove those two problems from the queues
                    if(solution == answer)
                    {
                        //System.out.println("Score!!");
                        problems.remove(0);
                        problems.remove(0);
                        problemBits.remove(0);
                        problemBits.remove(0);
                        rowPositions.remove(0);
                        rowPositions.remove(0);
                        ballPosition -= 3;
                    }
                    solution = 0;
                    solutions.clear();
                }
            }

        }

        public void keyTyped(KeyEvent e){}

    }//end class keyHandler

    /**
     * Inner class to handle mouse events, not used yet
     */
    class mouseHandler implements MouseListener
    {
        public void mousePressed(MouseEvent e) {}

        public void mouseReleased(MouseEvent e) {}

        public void mouseEntered(MouseEvent e) {}

        public void mouseExited(MouseEvent e) {}

        public void mouseClicked(MouseEvent e)
        {

            if(e.getButton() == e.BUTTON1)
            {

                
            }

            if(e.getButton() == e.BUTTON3)
            {

            }

        }

    }//end inner class mouseHandler
}

