package org.allaymc.api.block.data;

import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;

/**
 * Represents a 16 direction compass rose.
 * <p>
 * See <a href="https://en.wikipedia.org/wiki/Compass_rose#/media/File:Brosen_windrose.svg">wiki</a> for more information.
 *
 * @author joserobjr | Cool_Loong
 */
public enum CompassRoseDirection {
    NORTH(0, -1, BlockFace.NORTH, 0, 8),
    EAST(1, 0, BlockFace.EAST, 90, 12),
    SOUTH(0, 1, BlockFace.SOUTH, 180, 0),
    WEST(-1, 0, BlockFace.WEST, 270, 4),
    NORTH_EAST(NORTH, EAST, BlockFace.NORTH, 45, 10),
    NORTH_WEST(NORTH, WEST, BlockFace.WEST, 315, 6),
    SOUTH_EAST(SOUTH, EAST, BlockFace.EAST, 135, 14),
    SOUTH_WEST(SOUTH, WEST, BlockFace.SOUTH, 225, 2),
    WEST_NORTH_WEST(WEST, NORTH_WEST, BlockFace.WEST, 292.5, 5),
    NORTH_NORTH_WEST(NORTH, NORTH_WEST, BlockFace.NORTH, 337.5, 7),
    NORTH_NORTH_EAST(NORTH, NORTH_EAST, BlockFace.NORTH, 22.5, 9),
    EAST_NORTH_EAST(EAST, NORTH_EAST, BlockFace.EAST, 67.5, 11),
    EAST_SOUTH_EAST(EAST, SOUTH_EAST, BlockFace.EAST, 112.5, 13),
    SOUTH_SOUTH_EAST(SOUTH, SOUTH_EAST, BlockFace.SOUTH, 157.5, 15),
    SOUTH_SOUTH_WEST(SOUTH, SOUTH_WEST, BlockFace.SOUTH, 202.5, 1),
    WEST_SOUTH_WEST(WEST, SOUTH_WEST, BlockFace.WEST, 247.5, 3);

    private final static CompassRoseDirection[] VALUES = new CompassRoseDirection[]{
            SOUTH, SOUTH_SOUTH_WEST, SOUTH_WEST, WEST_SOUTH_WEST,
            WEST, WEST_NORTH_WEST, NORTH_WEST, NORTH_NORTH_WEST,
            NORTH, NORTH_NORTH_EAST, NORTH_EAST, EAST_NORTH_EAST,
            EAST, EAST_SOUTH_EAST, SOUTH_EAST, SOUTH_SOUTH_EAST
    };

    private final int modX;
    private final int modZ;
    private final BlockFace closestBlockFace;
    private final float yaw;
    private final int index;

    CompassRoseDirection(int modX, int modZ, BlockFace closestBlockFace, double yaw, int index) {
        this.modX = modX;
        this.modZ = modZ;
        this.closestBlockFace = closestBlockFace;
        this.yaw = (float) yaw;
        this.index = index;
    }

    CompassRoseDirection(CompassRoseDirection face1, CompassRoseDirection face2, BlockFace closestBlockFace, double yaw, int index) {
        this.modX = face1.getModX() + face2.getModX();
        this.modZ = face1.getModZ() + face2.getModZ();
        this.closestBlockFace = closestBlockFace;
        this.yaw = (float) yaw;
        this.index = index;
    }

    /**
     * Get the direction from the given index.
     *
     * @param index The index of the direction
     *
     * @return The direction from the given index
     */
    public static CompassRoseDirection from(int index) {
        return VALUES[index];
    }

    /**
     * Get the closes direction based on the given yaw.
     *
     * @param yaw An entity yaw
     *
     * @return The closest direction
     */
    public static CompassRoseDirection getClosestFromYaw(double yaw, @NotNull Precision precision) {
        return CompassRoseDirection.from((int) Math.round(Math.round((yaw + 180.0) * precision.directions / 360.0) * (16.0 / precision.directions)) & 0x0f);
    }

    /**
     * Get the closes direction based on the given yaw.
     *
     * @param yaw An entity yaw
     *
     * @return The closest direction
     */
    public static CompassRoseDirection getClosestFromYaw(double yaw) {
        return getClosestFromYaw(yaw, Precision.SECONDARY_INTER_CARDINAL);
    }

    /**
     * Get the amount of X-coordinates to modify to get the represented block
     *
     * @return Amount of X-coordinates to modify
     */
    public int getModX() {
        return modX;
    }

    /**
     * Get the amount of Z-coordinates to modify to get the represented block
     *
     * @return Amount of Z-coordinates to modify
     */
    public int getModZ() {
        return modZ;
    }

    /**
     * Get the closest face for this direction. For example, NNE returns N.
     * Even directions like NE will return the direction to the left, N in this case.
     *
     * @return The closest face for this direction
     */
    public BlockFace getClosestBlockFace() {
        return closestBlockFace;
    }

    /**
     * Get the index of this direction.
     *
     * @return The index of this direction
     */
    public int getIndex() {
        return index;
    }

    /**
     * Get the opposite face of this direction.
     *
     * @return The opposite face of this direction
     */
    public CompassRoseDirection getOppositeFace() {
        return switch (this) {
            case NORTH -> CompassRoseDirection.SOUTH;
            case SOUTH -> CompassRoseDirection.NORTH;
            case EAST -> CompassRoseDirection.WEST;
            case WEST -> CompassRoseDirection.EAST;
            case NORTH_EAST -> CompassRoseDirection.SOUTH_WEST;
            case NORTH_WEST -> CompassRoseDirection.SOUTH_EAST;
            case SOUTH_EAST -> CompassRoseDirection.NORTH_WEST;
            case SOUTH_WEST -> CompassRoseDirection.NORTH_EAST;
            case WEST_NORTH_WEST -> CompassRoseDirection.EAST_SOUTH_EAST;
            case NORTH_NORTH_WEST -> CompassRoseDirection.SOUTH_SOUTH_EAST;
            case NORTH_NORTH_EAST -> CompassRoseDirection.SOUTH_SOUTH_WEST;
            case EAST_NORTH_EAST -> CompassRoseDirection.WEST_SOUTH_WEST;
            case EAST_SOUTH_EAST -> CompassRoseDirection.WEST_NORTH_WEST;
            case SOUTH_SOUTH_EAST -> CompassRoseDirection.NORTH_NORTH_WEST;
            case SOUTH_SOUTH_WEST -> CompassRoseDirection.NORTH_NORTH_EAST;
            case WEST_SOUTH_WEST -> CompassRoseDirection.EAST_NORTH_EAST;
        };
    }

    /**
     * Get the yaw that represents this direction.
     *
     * @return The yaw value that can be used by entities to look at this direction
     */
    public float getYaw() {
        return yaw;
    }

    @RequiredArgsConstructor
    public enum Precision {
        /**
         * North, South, East, West.
         */
        CARDINAL(4),

        /**
         * N, E, S, W, NE, NW, SE, SW.
         */
        PRIMARY_INTER_CARDINAL(8),

        /**
         * N, E, S, W, NE, NW, SE, SW, WNW, NNW, NNE, ENE, ESE, SSE, SSW, WSW.
         */
        SECONDARY_INTER_CARDINAL(16);
        final int directions;
    }
}
