package emu.grasscutter.utils;

import java.io.Serializable;
import java.util.List;

import com.google.gson.annotations.SerializedName;
import com.github.davidmoten.rtreemulti.geometry.Point;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import lombok.Getter;
import lombok.Setter;

@Entity
public class Position implements Serializable {
    private static final long serialVersionUID = -2001232313615923575L;

    @SerializedName(value="x", alternate={"_x", "X"})
    @Getter @Setter private float x;

    @SerializedName(value="y", alternate={"_y", "Y"})
    @Getter @Setter private float y;

    @SerializedName(value="z", alternate={"_z", "Z"})
    @Getter @Setter private float z;

    public Position() {}

    public Position(float x, float y) {
        set(x, y);
    }

    public Position(float x, float y, float z) {
        set(x, y, z);
    }

    public Position(List<Float> xyz) {
        switch (xyz.size()) {
            default:  // Might want to error on excess elements, but maybe we want to extend to 3+3 representation later.
            case 3:
                this.z = xyz.get(2);  // Fall-through
            case 2:
                this.y = xyz.get(1);  // Fall-through
            case 1:
                this.y = xyz.get(0);  // pointless fall-through
            case 0:
                break;
        }
    }

    public Position(String p) {
        String[] split = p.split(",");
        if (split.length >= 2) {
            this.x = Float.parseFloat(split[0]);
            this.y = Float.parseFloat(split[1]);
        }
        if (split.length >= 3) {
            this.z = Float.parseFloat(split[2]);
        }
    }

    public Position(Vector vector) {
        this.set(vector);
    }

    public Position(Position pos) {
        this.set(pos);
    }

    public Position set(float x, float y) {
        this.x = x;
        this.y = y;
        return this;
    }

    // Deep copy
    public Position set(Position pos) {
        return this.set(pos.getX(), pos.getY(), pos.getZ());
    }

    public Position set(Vector pos) {
        return this.set(pos.getX(), pos.getY(), pos.getZ());
    }

    public Position set(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
        return this;
    }

    public Position add(Position add) {
        this.x += add.getX();
        this.y += add.getY();
        this.z += add.getZ();
        return this;
    }

    public Position addX(float d) {
        this.x += d;
        return this;
    }

    public Position addY(float d) {
        this.y += d;
        return this;
    }

    public Position addZ(float d) {
        this.z += d;
        return this;
    }

    public Position subtract(Position sub) {
        this.x -= sub.getX();
        this.y -= sub.getY();
        this.z -= sub.getZ();
        return this;
    }

    /** In radians
     * */
    public Position translate(float dist, float angle) {
        this.x += dist * Math.sin(angle);
        this.y += dist * Math.cos(angle);
        return this;
    }

    public boolean equal2d(Position other) {
        // Y is height
        return getX() == other.getX() && getZ() == other.getZ();
    }

    public boolean equal3d(Position other) {
        return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ();
    }

    public double computeDistance(Position b) {
        double detX = getX()-b.getX();
        double detY = getY()-b.getY();
        double detZ = getZ()-b.getZ();
        return Math.sqrt(detX*detX+detY*detY+detZ*detZ);
    }

    public Position nearby2d(float range) {
        Position position = clone();
        position.z += Utils.randomFloatRange(-range, range);
        position.x += Utils.randomFloatRange(-range, range);
        return position;
    }
    public Position translateWithDegrees(float dist, float angle) {
        angle = (float) Math.toRadians(angle);
        this.x += dist * Math.sin(angle);
        this.y += -dist * Math.cos(angle);
        return this;
    }

    @Override
    public Position clone() {
        return new Position(x, y, z);
    }

    @Override
    public String toString() {
        return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")";
    }

    public Vector toProto() {
        return Vector.newBuilder()
            .setX(this.getX())
            .setY(this.getY())
            .setZ(this.getZ())
            .build();
    }
    public Point toPoint() {
        return Point.create(x,y,z);
    }

    /**
     * To XYZ array for Spatial Index
     */
    public double[] toDoubleArray() {
        return new double[]{ x, y, z};
    }
    /**
     * To XZ array for Spatial Index (Blocks)
     */
    public double[] toXZDoubleArray() {
        return new double[]{x, z};
    }
}