/*
 * GDXRoot.java
 *
 * This is the primary class file for running the game.  It is the "static main" of
 * LibGDX.  Typically we use this class with player modes and make the input a separate
 * class.  This time, we have put all of the game logic in this class so that you can
 * see what is happening in a single glance.
 *
 * Author: Walker M. White
 * LibGDX version, 2/2/2017
 */
package edu.cornell.gdiac.vertices;

import com.badlogic.gdx.*;
import com.badlogic.gdx.math.*;
import com.badlogic.gdx.graphics.*;
import edu.cornell.gdiac.g2d.*;

/**
 * Root class for the texture demo.
 *
 * This class shows off a circle-like texture in the middle of the screen.  It allows
 * the user to distort the image by dragging the various vertices.
 * 
 * This class is technically not the ROOT CLASS. Each platform has another class above
 * this (e.g. PC games use DesktopLauncher) which serves as the true root.  However, 
 * those classes are unique to each platform, while this class is the same across all 
 * plaforms. In addition, this functions as the root class all intents and purposes, 
 * and you would draw it as a root class in an architecture specification.  
 */
public class GDXRoot extends ApplicationAdapter implements InputProcessor {
    /** The radius of the circle-like texture */
    private static float RADIUS  = 100.0f;
    /** The minimum number of edges to approximate a circle */
    private static int MIN_SIZE  = 3;
    /** The maximum number of edges to approximate a circle */
    private static int MAX_SIZE  = 20;
    /** The distance tolerance for clicking on a vertex */
    private static int TOLERANCE = 10;

    /** The variant of the spritebatch for drawing vertices */
    private VertexBatch batch;
    /** The buffer containing the vertex data to draw */
    private VertexBuffer vertices;
    /** The indices to define triangles from the vertices */
    private short[] indices;

    /** The image to texture the vertices with */
    private Texture img;
    /** The transform to center the image on the screen */
    private Affine2 transform;

    /** Whether or not to display the texture */
    private boolean texture;
    /** The number of edges to approximate a circle */
    private int size;

    /** A vector to track the last mouse position */
    private Vector2 mousepos = null;
    /** A vector to track which vertex was last chosen */
    private int mouseindx = -1;

    /** 
     * Called when the Application is first created.
     * 
     * This is method loads the image and defines the (initial) vertex buffer
     */
    @Override
    public void create () {
        // Create the VertexBatch for drawing
        batch = new VertexBatch();
        img = new Texture("white.png");
        
        // Create a transform to center the polygon
        transform = new Affine2();
        transform.setToTranslation(Gdx.graphics.getWidth()/2,Gdx.graphics.getHeight()/2);
        
        // Reset the polygon
        size = 8;
        texture = true;
        reset();
        
        // Prepare to receive input events
        Gdx.input.setInputProcessor(this);
    }


    /**
     * Renders the polygon to the screen
     *
     * We do not need an update() call for this class, that is handled by the
     * InputProcessor methods.
     */
    @Override
    public void render () {
        Gdx.gl.glClearColor(0.7f, 0.7f, 0.7f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();
        batch.draw(texture ? img : null,vertices,indices,transform);
        batch.end();
    }

    /**
     * Resets the polygon to match the current global state.
     *
     * The polygon created will be centered in the screen with size number of
     * edges.  Any distortions applied by the mouse will be removed.  If the texture
     * is turned off, the vertices will be colored using an HSV color-wheel.
     */
    public void reset() {
        // Cache objects to create the vertex buffer
        Vector2 position = new Vector2();
        Vector2 texcoord = new Vector2();
        Color color = new Color();

        // Go around in a circle, starting at the top
        float step = (float)(Math.PI*2)/size;
        vertices = new VertexBuffer(size);

        float dx, dy;
        for(int ii = 0; ii < size; ii++) {
            // Compute position on unit circle
            double angle = ii*step+Math.PI/2.0f;
            dx = (float)Math.cos(angle);
            dy = (float)Math.sin(angle);

            // Set the position
            position.set(dx*RADIUS,dy*RADIUS);

            // Set the texture coords.
            texcoord.set((1+dx)/2,(1-dy)/2);

            // Set the color and append to vertex buffer
            if (texture) {
                //setRadialColor(color, (float) angle);
                //vertices.append(position, color, texcoord);
                vertices.append(position, Color.WHITE, texcoord);
            } else {
                setRadialColor(color, (float) angle);
                vertices.append(position, color, texcoord);
            }
        }
        
        // Create the indices as a fan to the top
        indices = new short[size*3];
        for(int ii = 0; ii < size-2; ii++) {
            indices[3*ii  ]  = 0;
            indices[3*ii+1]  = (short)(ii+1);
            indices[3*ii+2]  = (short)(ii+2);
        }
        
        // Reset the mouse selection
        mousepos  = null;
        mouseindx = -1;
    }

    /** 
     * Called when the Application is destroyed. 
     *
     * This is preceded by a call to pause().
     */
    @Override
    public void dispose () {
        batch.dispose();
        img.dispose();
    }
    
    /**
     * Sets the given color according to the given angle on the HSV color wheel
     *
     * This method assumes that saturation and value are 1. Only the hue is 
     * determined by angle.
     *
     * @param color The color to update
     * @param angle The color angle in radians
     */
    public static void setRadialColor(Color color, float angle) {
        float hue = 180.0f*angle/(float)Math.PI;
        int hi = ((int)Math.floor(hue / 60.0)) % 6;
        float f = hue * 60.0f - hi;
        float p = 0.0f;
        float q = 1.0f * (1 - f);
        float t = 1.0f * (1 - (1 - f));

        switch (hi) {
            case 0:
                color.set(1.0f,t,p,1.0f);
                break;
            case 1:
                color.set(q,1.0f,p,1.0f);
                break;
            case 2:
                color.set(p,1.0f,t,1.0f);
                break;
            case 3:
                color.set(p,q,1.0f,1.0f);
                break;
            case 4:
                color.set(t,p,1.0f,1.0f);
                break;
            case 5:
                color.set(1.0f,p,q,1.0f);
                break;
            default:
        }
    }

    /// INPUT HANDLING
    /**
     * Called when a key was pressed
     * 
     * We use this method to allow the user to reset the polygon, change the number
     * of edges, or toggle between image and color.
     *
     * @param keycode   The key pressed
     *
     * @return Whether any input was processed
     */
    public boolean keyDown(int keycode) {
        switch (keycode) {
            case Input.Keys.R:
                reset();
                break;
            case Input.Keys.T:
                texture = !texture;
                reset();
                break;
            case Input.Keys.EQUALS:
                if (size < MAX_SIZE) {
                    size++;
                    reset();
                }
                break;
            case Input.Keys.MINUS:
                if (size > MIN_SIZE) {
                    size--;
                    reset();
                }
                break;
            default:
                return false;
        }
        return true;
    }

    /**
     * Called when the screen was touched or a mouse button was pressed.
     *
     * This method checks to see if the mouse was clicked within TOLERANCE pixels
     * of a vertex.  If so, it allows the user to drag the vertex.
     *
     * @param screenX   The x-coordinate in screen space (origin top left)
     * @param screenY   The y-coordinate in screen space (origin top left)
     * @param pointer   The choice of pointer or mouse
     * @param button    The mouse button pressed
     */
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        int x = screenX-Gdx.graphics.getWidth()/2;
        int y = Gdx.graphics.getHeight()/2-screenY; // Inverts the y-axis
        mouseindx = vertices.nearest(x,y,TOLERANCE);
        
        // Remember this position if success
        if (mouseindx != -1) {
            mousepos = new Vector2(screenX,screenY);
        }
        return mousepos != null;
    }

    /**
     * Called when a finger or the mouse was dragged.
     *
     * If no vertex is selected, nothing happens.  Otherwise, the vertex is nudged
     * by the change in mouse position.
     *
     * @param screenX   The x-coordinate in screen space (origin top left)
     * @param screenY   The y-coordinate in screen space (origin top left)
     * @param pointer   The choice of pointer or mouse
     * @param button    The mouse button held
     */
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        if (mousepos != null) {
            int dx = screenX-(int)mousepos.x;
            int dy = (int)mousepos.y-screenY; // Inverts the y-axis
            vertices.nudge(mouseindx,dx,dy);
            mousepos.set(screenX,screenY);
            return true;
        }
        return false;
    }
    
    /**
     * Called when a finger was lifted or a mouse button was released.
     *
     * This method releases any active mouse vertices
     *
     * @param screenX   The x-coordinate in screen space (origin top left)
     * @param screenY   The y-coordinate in screen space (origin top left)
     * @param pointer   The choice of pointer or mouse
     * @param button    The mouse button released
     */
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        if (mousepos != null) {
            mousepos = null;
            return true;
        }
        return false;
    }

    /// UNUSED
    // Called when a key was typed
    public boolean keyTyped(char character) { return false; }
    //Called when a key was released
    public boolean keyUp(int keycode) { return false; }
    // Called when the mouse was moved without any buttons being pressed.
    public boolean mouseMoved(int screenX, int screenY) { return false; }
    // Called when the mouse wheel was scrolled.
    public boolean scrolled(int amount) { return false; }

}
