/*
 * GDXRoot.java
 *
 * This demo shows off how to use a sprite mesh. While there are two assets
 * (two texture images), we have kept this demo simple as a single class. There
 * are no individual scenes.
 *
 * @author Walker M. White
 * @data 2/14/25
 */
package edu.cornell.cis3152.mesh;


import com.badlogic.gdx.*;
import com.badlogic.gdx.math.*;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.utils.ScreenUtils;
import edu.cornell.gdiac.graphics.*;
import edu.cornell.gdiac.math.*;
import edu.cornell.gdiac.math.PolyFactory;

/**
 * Root class for the sprite meshs demo.
 *
 * This class shows off a circle-like mesh in the middle of the screen. It
 * allows the user to distort the image by dragging the various vertices. Press
 * keys 1-6 to change the texturing options. Press +/- to change the number of
 * of vertices in the mesh.
 *
 * 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 image to texture the vertices with */
    private Texture image;
    /** The first image choice */
    private Texture texture1;
    /** The second image choice */
    private Texture texture2;

    /** The sprite batch camera */
    private OrthographicCamera camera;
    /** The GDIAC spritebatch */
    private SpriteBatch batch;
    /** The mesh to draw */
    private SpriteMesh mesh;
    /** The transform to center the image on the screen */
    private Affine2 transform;

    /** The number of edges to approximate a circle */
    private int size;
    /** Whether to color the image */
    private boolean colored;

    /** A vector to track the last mouse position */
    private Vector2 mousepos = new Vector2();
    /** 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 images and defines the (initial) sprite mesh
     */
    @Override
    public void create () {
        batch = new SpriteBatch();
        camera = new OrthographicCamera();
        camera.setToOrtho( false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight() );

        // Load the textures directly, without an asset directory
        texture1 = new Texture("walker1.png");
        texture2 = new Texture("walker2.png");
        image = texture1;

        // Create a transform to center the sprite mesh
        transform = new Affine2();
        transform.setToTranslation(Gdx.graphics.getWidth()/2,Gdx.graphics.getHeight()/2);

        // Reset the mesh
        colored = false;
        size = 8;
        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 () {
        ScreenUtils.clear(0.7f, 0.7f, 0.7f, 1);
        batch.begin(camera);
        batch.setTexture( image );
        batch.drawMesh( mesh, transform, false );
        batch.end();
    }

    /**
     * Resets the sprite mesh to match the current global state.
     *
     * The mesh created will be centered in the screen with size number of
     * edges. Any distortions applied by the mouse will be removed. If the
     * the colored attribute is true, the vertices will be colored using an
     * HSV color-wheel.
     */
    public void reset() {
        // Create a n-gon with the right number of sides
        PolyFactory factory = new PolyFactory();
        Poly2 poly = factory.makeNgon( Vector2.Zero, RADIUS,size );

        // The factory puts the starting point on the right. We want it up top
        Affine2 rot = new Affine2();
        rot.rotate( 90 );
        poly.transform( rot );

        // Make a mesh with texture coordinates defined by a box 2*Rx2*R
        mesh = new SpriteMesh(poly, -RADIUS, -RADIUS, 2*RADIUS, 2*RADIUS);
        colorMesh(colored);

        // Reset the mouse selection
        mouseindx = -1;
    }

    /**
     * Called when the Application is destroyed.
     *
     * This is preceded by a call to pause().
     */
    @Override
    public void dispose () {
        batch.dispose();
        // If you load textures directly, you MUST dispose them
        texture1.dispose();
        texture2.dispose();
    }

    /**
     * Colors the vertices of the mesh
     *
     * If radial is false, the vertex colors are all white. If it is true, then
     * they will be colored using an HSV color wheel. The choice will be
     * remembered in the attribute colored so that is carries across resets.
     *
     * @param radial    Whether to apply radial color
     */
    public void colorMesh(boolean radial) {
        colored = radial;
        Color color = new Color();
        if (radial) {
            for (int ii = 0; ii < mesh.vertexCount()-1; ii++) {
                float y = mesh.getPositionY( ii );
                float x = mesh.getPositionX( ii );
                float angle = (float)Math.atan2( y, x );
                if (angle < 0) {
                    angle += 2*Math.PI;
                }
                setRadialColor( color, angle );
                mesh.setColor( ii, color );
            }
        } else {
            color.set(1.0f,1.0f,1.0f,1.0f);
            for (int ii = 0; ii < mesh.vertexCount()-1; ii++) {
                mesh.setColor( ii, color );
            }
        }
    }

    /**
     * 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;
        float val = 1.0f;
        float sat = 1.0f;

        int hi = ((int)Math.floor(hue / 60.0)) % 6;
        float f = hue / 60.0f - hi;
        float p = val * (1.0f - sat);
        float q = val * (1.0f - f * sat);
        float t = val * (1.0f - (1.0f - f) * sat);

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

    /**
     * Returns the mesh index (if any) of the nearest vertex
     *
     * This method is typically used to find the nearest vertex to a mouse click.
     * It takes the given position, and measures the distance from it to each
     * vertex in the mesh. If any vertex has distance less than threshold, it
     * choses the nearest such one and returns the index. If no vertex is within
     * the distance threshold, it returns -1.
     *
     * The distance threshold typically corresponds to the radius of the
     * selection pointer. However, this is not required.
     *
     * @param x			The x-coordinate to search against
     * @param y			The y-coordinate to search against
     * @param thresh	The distance threshold for vertex selection.
     *
     * @return the mesh index (if any) of the nearest vertex
     */
    public int nearest(float x, float y, float thresh) {
        int minpos = -1;
        float mindist = thresh*thresh;
        for(int ii = 0; ii < mesh.vertexCount(); ii++ ) {
            float dx = x-mesh.getPositionX( ii );
            float dy = y-mesh.getPositionY( ii );
            float dist = dx*dx+dy*dy;
            if (dist < mindist) {
                mindist = dist;
                minpos = ii;
            }
        }
        return minpos;
    }


    /// 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 the image and/or color.
     *
     * @param keycode   The key pressed
     *
     * @return Whether any input was processed
     */
    public boolean keyDown(int keycode) {
        switch (keycode) {
            case Input.Keys.NUM_1:
                image = null;
                colorMesh(false);
                break;
            case Input.Keys.NUM_2:
                image = texture1;
                colorMesh(false);
                break;
            case Input.Keys.NUM_3:
                image = texture2;
                colorMesh(false);
                break;
            case Input.Keys.NUM_4:
                image = null;
                colorMesh(true);
                break;
            case Input.Keys.NUM_5:
                image = texture1;
                colorMesh(true);
                break;
            case Input.Keys.NUM_6:
                image = texture2;
                colorMesh(true);
                break;
            case Input.Keys.R:
                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) {
        float x = screenX-Gdx.graphics.getWidth()/2;
        float y = Gdx.graphics.getHeight()/2-screenY; // Inverts the y-axis
        mouseindx = nearest( x,y,TOLERANCE );

        // Remember this position if success
        if (mouseindx != -1) {
            mousepos.set(screenX,screenY);
        }
        return mouseindx != -1;
    }

    /**
     * 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
     */
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        if (mouseindx != -1) {
            int dx = screenX-(int)mousepos.x;
            int dy = (int)mousepos.y-screenY; // Inverts the y-axis

            mesh.getPosition( mouseindx, mousepos );
            mousepos.x += dx;
            mousepos.y += dy;
            mesh.setPosition( mouseindx, mousepos );
            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 (mouseindx != -1) {
            mouseindx = -1;
            return true;
        }
        return false;
    }

    /// UNUSED
    // Called when a gesture is cancelled
    public boolean touchCancelled(int screenX, int screenY, int pointer, int button) { return false; }
    // 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(float dx, float dy) { return false; }

}
