package coords;

import java.math.*;

public class Coords {

    protected double x, y, x0, y0, z0, h0, xr, yr, zr, hr;
    protected double z = 0.0;
    protected double h = 1.0;
    //x, y, z, h refer to coordinate points
    //xo, yo, zo, ho refer to original points, for reseting purposes (a.k.a. respawn)
    //xr, yr, zr, hr are return points, where if the additional points create an error, revert to these points since they worked.

    public Coords(int x1, int y1)//If only using x, y values - for 2-D use, by general
    {
        x = (double) x1;
        y = (double) y1;
        x0 = x;//Allocates reset
        y0 = y;
        z0 = (double) z;
        h0 = (double) h;
    }

    public Coords(int x1, int y1, int z1)//If only using x, y, z values - for 3-D use, by general
    {
        x = (double) x1;
        y = (double) y1;
        z = (double) z1;
        x0 = x;//Allocates reset
        y0 = y;
        z0 = z;
        h0 = h;
    }

    public Coords(int x1, int y1, int z1, int h1) {
        x = (double) x1;
        y = (double) y1;
        z = (double) z1;
        h = (double) h1;
        x0 = x;//Allocates reset()
        y0 = y;
        z0 = z;
        h0 = h;
    }

    public Coords(double x1, double y1)//If only using x, y values - for 2-D use, by general
    {
        x = x1;
        y = y1;
        x0 = x;//Allocates reset
        y0 = y;
        z0 = z;
        h0 = h;
    }

    public Coords(double x1, double y1, double z1)//If only using x, y, z values - for 3-D use, by general
    {
        x = x1;
        y = y1;
        z = z1;
        x0 = x;//Allocates reset
        y0 = y;
        z0 = z;
        h0 = h;
    }

    public Coords(double x1, double y1, double z1, double h1) {
        x = x1;
        y = y1;
        z = z1;
        h = h1;
        x0 = x;//Allocates reset()
        y0 = y;
        z0 = z;
        h0 = h;
    }

    public Coords(int[] cords)//If only using x, y values - for 2-D use, by general
    {
        x = (double) cords[0];
        y = (double) cords[1];
        x0 = x;//Allocates reset
        y0 = y;
        if (cords.length > 2) {
            z = (double) cords[2];
            if (cords.length > 3) {
                h = (double) cords[3];
            }
        }
        z0 = z;
        h0 = h;
    }

    public Coords(double[] cords)//If only using x, y values - for 2-D use, by general
    {
        x = cords[0];
        y = cords[1];
        x0 = x;//Allocates reset
        y0 = y;
        if (cords.length > 2) {
            z = cords[2];
            if (cords.length > 3) {
                h = cords[3];
            }
        }
        z0 = z;
        h0 = h;
    }

    public Coords(Coords coo) {
        this.x = coo.getX();
        this.y = coo.getY();
        this.z = coo.getZ();
        this.h = coo.getH();
        this.x0 = x;
        this.y0 = y;
        this.z0 = z;
        this.h0 = h;
    }//End of overloading constructors

    public double getX() {

        return x;
    }

    public double getY() {
        return y;
    }

    public double getZ() {
        return z;
    }

    public double getH() {
        return h;
    }

    public double[] getCoords() {
        double[] coords = {x, y, z, h};
        return coords;
    }

    public boolean addToPart(double addition, String part) {
        xr = x;//Allows rollback to points before this step is done.
        yr = y;
        zr = z;
        hr = h;

        if (part == "x") {
            x += addition;
            return true;
        } else if (part == "y") {
            y += addition;
            return true;
        } else if (part == "z") {
            z += addition;
            return true;
        } else if (part == "h") {
            h += addition;
            return true;
        }

        return false;
    }

    public boolean multiplyToPart(double multiple, String part) {
        xr = x;//Allows rollback to points before this step is done.
        yr = y;
        zr = z;
        hr = h;
        if (part == "x") {
            x *= multiple;
            return true;
        } else if (part == "y") {
            y *= multiple;
            return true;
        } else if (part == "z") {
            z *= multiple;
            return true;
        } else if (part == "h") {
            h *= multiple;
            return true;
        }

        return false;
    }

    public boolean rotateLocallyPart(double angle, String part) {
        xr = x;//Allows rollback to points before this step is done.
        yr = y;
        zr = z;
        hr = h;
        if (part == "x") {
            x = x;
            y = (double)((int)(Math.round(y * Math.cos(angle*(Math.PI/180)))) - ((((int)Math.round(z * Math.sin(angle*(Math.PI/180)))))));
            z = (double)((int)Math.round(y * Math.sin(angle*(Math.PI/180)))) + ((((int)(Math.round(z * Math.cos(angle*(Math.PI/180)))))));
        } else if (part == "y") {
            x = (double)((int)Math.round(x * Math.cos(angle*(Math.PI/180))) + ((((int)Math.round(z * Math.sin(angle*(Math.PI/180)))))));
            y = y;
            z = (double)(((int)Math.round(z * Math.cos(angle*(Math.PI/180)))) - ((((int)Math.round(x * Math.sin(angle*(Math.PI/180)))))));
            return true;
        } else if (part == "z") {
            x = (double)(((int)Math.round(x * Math.cos(angle*(Math.PI/180)))) - (((int)Math.round(y * Math.sin(angle*(Math.PI/180))))));
            y = (double)(((int)Math.round(x * Math.sin(angle*(Math.PI/180)))) + (((int)Math.round(y * Math.cos(angle*(Math.PI/180))))));
            z = z;
            
            System.out.println("Previous point: x; " + xr + " y; " + yr + " z; " + zr);
            System.out.println("Current point: x; " + x + " y; " + y + " z; " + z);
            //The above code has rounding errors, so we're going to make it expand outward instead of inwards.
            //Might place on a higher level with a flag for this, as sometimes we want to rotate inwards rather than outwards from the origin.
            x += (x/x);//Let's us get an offet that is negative if x is negative, positive if x is positive, of 1
            y += (y/y);
            z += (z/z);
            System.out.println("Current point with Offset: x; " + x + " y; " + y + " z; " + z);
            return true;
        }

        return false;
    }

    //TODO: Add Rotational methods
    //      Local, flattened, and globally flattened
    public boolean rotateLocally(double xAngle, double yAngle, double zAngle) {

        //Because rotateLocalPart is going to readjust xr/yr/zr/hr, etc.
        //We go here to readjust that.
        double xt = x;
        double yt = y;
        double zt = z;
        double ht = h;

        rotateLocallyPart(xAngle, "x");
        rotateLocallyPart(yAngle, "y");
        rotateLocallyPart(zAngle, "z");
        
        //So there is a small bug here
        //Apparently in the rotation calculation,
        //It is possible for a number to be NaN.
        //Going to try and fix that here, either 
        //by lowering the value multiplied...
        //Or some other solution - haven't solidified that yet.
        //Currently putting it here 
        if(new Double(x).isNaN() || new Double(y).isNaN() || new Double(z).isNaN()){
            //If we throw NaN in any of the values...
            //try rotating at a lower angle.
            System.out.println("Need to fix NaN");
            x = xt;
            y = yt;
            z = zt;
            rotateLocallyPart(xAngle-2, "x");
            rotateLocallyPart(yAngle-2, "y");
            rotateLocallyPart(zAngle-2, "z");
        }

        //Just so that we can revert to before the rotation.
        xr = xt;
        yr = yt;
        zr = zt;
        hr = ht;

        return true;
    }

    public boolean changePart(double changeTo, String part) {
        xr = x;//Allows rollback to points before this step is done.
        yr = y;
        zr = z;
        hr = h;
        if (part == "x") {
            x = changeTo;
            return true;
        } else if (part == "y") {
            y = changeTo;
            return true;
        } else if (part == "z") {
            z = changeTo;
            return true;
        } else if (part == "h") {
            h = changeTo;
            return true;
        }

        return false;
    }

    public boolean changeAll(Coords next) {
        try {
            xr = x;//Allows rollback to points before this step is done.
            yr = y;
            zr = z;
            hr = h;
            x = next.getX();
            y = next.getY();
            z = next.getZ();
            h = next.getH();
            return true;
        } catch (Exception e) {
            return false;
        }

    }

    public boolean rollback()//Rolls back one step to all of last variables - can only be done once per step. Cannot do if there are no steps done last. (A.k.a initialization)
    {
        try {
            x = xr;
            y = yr;
            z = zr;
            h = hr;
            return true;
        } catch (Exception e) {
            return false;
        }

    }

    public boolean reset()//Extreme case - if need to rollback to first original coordsa, do this.
    {
        try {
            x = x0;
            y = y0;
            z = z0;
            h = h0;
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public Coords flatten() {
        //This is mostly done so that if 
        //DCoords or another class needs to flatten the coords into a 
        //specific global values
        //Use mainly if flattening to normalized global for rotations 
        //or such.
        return this;
    }

    public Coords flattenGlobal() {
        //This is mostly done so that if 
        //DCoords or another class needs to flatten the coords into a 
        //specific global values
        //This one should be used for complete flattening to 
        //Java global settings
        //That is, (0, 0) at the top left of the swing java window
        return this;
    }
    
    /**
     * Lets us get the Origin of a DCoords if needing to determine 
     * the offsets based on the origin the Coord is using.
     * Most useful for DCoords manipulation.
     * 
     */
    public Coords getOrigin(){
        return new Coords(0, 0, 0, 1);
    }
}
