package me.km.utils;

import java.util.Objects;
import java.util.regex.PatternSyntaxException;
import me.km.world.WorldManager;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IWorld;

public class Location {
    private final IWorld w;
    private double x;
    private double y;
    private double z;
    private float yaw;
    private float pitch;

    public Location(IWorld w, double x, double y, double z, float yaw, float pitch) {
        this.w = w;
        this.x = x;
        this.y = y;
        this.z = z;
        this.yaw = yaw;
        this.pitch = pitch;
    }

    public Location(IWorld w, Vec3d pos, float yaw, float pitch) {
        this(w, pos.x, pos.y, pos.z, yaw, pitch);
    }

    public Location(IWorld w, Vec3d pos) {
        this(w, pos, 0, 0);
    }

    public Location(IWorld w, BlockPos pos, float yaw, float pitch) {
        this(w, pos.getX(), pos.getY(), pos.getZ(), yaw, pitch);
    }

    public Location(IWorld w, BlockPos pos) {
        this(w, pos, 0, 0);
    }

    public Location(Entity ent) {
        this(ent.world, ent.getPosX(), ent.getPosY(), ent.getPosZ(), ent.rotationYaw, ent.rotationPitch);
    }

    public Location(MinecraftServer server, String location) {
        try {
            String[] parts = location.split(":");
            if(parts.length >= 6) {
                this.w = WorldManager.get(server, parts[0]);
                this.x = Double.parseDouble(parts[1]);
                this.y = Double.parseDouble(parts[2]);
                this.z = Double.parseDouble(parts[3]);
                this.yaw = Float.parseFloat(parts[4]);
                this.pitch = Float.parseFloat(parts[5]);
                return;
            } else if(parts.length >= 4) {
                this.w = WorldManager.get(server, parts[0]);
                this.x = Double.parseDouble(parts[1]);
                this.y = Double.parseDouble(parts[2]);
                this.z = Double.parseDouble(parts[3]);
                this.yaw = 0;
                this.pitch = 0;
                return;
            }
            throw new IllegalArgumentException(String.format("'%s' is not a valid location string", location));
        } catch(PatternSyntaxException | NumberFormatException ex) {
            throw new IllegalArgumentException(String.format("'%s' is not a valid location string", location));
        }
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 17 * hash + Objects.hashCode(this.w);
        hash = 17 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32));
        hash = 17 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32));
        hash = 17 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32));
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        final Location other = (Location) obj;
        return Double.doubleToLongBits(this.x) == Double.doubleToLongBits(other.x)
                && Double.doubleToLongBits(this.y) == Double.doubleToLongBits(other.y)
                && Double.doubleToLongBits(this.z) == Double.doubleToLongBits(other.z)
                && Objects.equals(this.w, other.w);
    }

    public void add(double x, double y, double z) {
        this.x += x;
        this.y += y;
        this.z += z;
    }

    public Location copyAdd(double x, double y, double z) {
        return new Location(w, this.x + x, this.y + y, this.z + z, this.yaw, this.pitch);
    }

    public void addX(double x) {
        this.x += x;
    }

    public void addY(double y) {
        this.y += y;
    }

    public void addZ(double z) {
        this.z += z;
    }

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }

    public void setZ(double z) {
        this.z = z;
    }

    public void set(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public double getZ() {
        return z;
    }

    public void round() {
        this.x = MathHelper.floor(this.x);
        this.y = MathHelper.floor(this.y);
        this.z = MathHelper.floor(this.z);
    }

    public IWorld getWorld() {
        return w;
    }

    public Vec3d getPos() {
        return new Vec3d(x, y, z);
    }

    public BlockPos getBlockPos() {
        return new BlockPos(x, y, z);
    }

    public float getYaw() {
        return yaw;
    }

    public float getPitch() {
        return pitch;
    }

    public void setPitch(float pitch) {
        this.pitch = pitch;
    }

    public void setYaw(float yaw) {
        this.yaw = yaw;
    }

    public BlockState getBlockState() {
        return w.getBlockState(new BlockPos(x, y, z));
    }

    public BlockState getRelativeBlockState(int x, int y, int z) {
        return w.getBlockState(new BlockPos(this.x + x, this.y + y, this.z + z));
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(WorldManager.getName(w));
        sb.append(":");
        sb.append(x);
        sb.append(":");
        sb.append(y);
        sb.append(":");
        sb.append(z);
        sb.append(":");
        sb.append(yaw);
        sb.append(":");
        sb.append(pitch);
        return sb.toString();
    }

    public String toBlockString() {
        StringBuilder sb = new StringBuilder(WorldManager.getName(w));
        sb.append(":");
        sb.append(MathHelper.floor(x));
        sb.append(":");
        sb.append(MathHelper.floor(y));
        sb.append(":");
        sb.append(MathHelper.floor(z));
        return sb.toString();
    }

    public Location copy() {
        return new Location(w, x, y, z, yaw, pitch);
    }
}
