package cz.jzitnik.game.mobs.services.sheep;

import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.EntityHurtAnimationHandler;
import cz.jzitnik.game.annotations.EntityKillHandler;
import cz.jzitnik.game.annotations.EntityLogic;
import cz.jzitnik.game.annotations.EntitySpawn;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.mobs.*;
import cz.jzitnik.game.sprites.Sheep;
import cz.jzitnik.tui.ScreenMovingCalculationProvider;
import org.jline.terminal.Terminal;

import java.util.*;

import static cz.jzitnik.game.sprites.Sheep.SheepState.*;

@EntitySpawn
@EntityLogic("sheep")
@EntityHurtAnimationHandler("sheep")
@EntityKillHandler("sheep")
public class SheepLogic
        implements EntityLogicInterface, EntitySpawnInterface, EntityHurtAnimationChanger, EntityKillInterface {
    private final Random random = new Random();

    @Override
    public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) {
        int sheepX = entityLogicMobDTO.getX();
        int sheepY = entityLogicMobDTO.getY();
        var game = entityLogicMobDTO.getGame();
        var sheep = entityLogicMobDTO.getMob();
        var world = game.getWorld();
        var sheepData = (SheepData) sheep.getData();

        boolean updated = false;
        int newSheepX = sheepX;
        int newSheepY = sheepY;

        if (sheepData.getMovementCooldown() > 0) {
            sheepData.setMovementCooldown(sheepData.getMovementCooldown() - 1);
            return;
        }

        int direction = sheepData.getLastDirection();
        if (random.nextInt(10) < 1) { // 10% chance to change direction
            direction = -direction;
        }
        sheepData.setLastDirection(direction);

        Sheep.SheepState newState = getSheepState(sheep, sheepData, direction);
        sheep.setSpriteState(newState);

        List<Block> blocksAhead = world[sheepY][sheepX + direction];
        if (!game.isSolid(blocksAhead)) {
            world[sheepY][sheepX].remove(sheep);
            world[sheepY][sheepX + direction].add(sheep);
            newSheepX = sheepX + direction;
            updated = true;
            sheepData.setJumpAttempts(0);
        } else {
            List<Block> blocksAboveAhead = world[sheepY - 1][sheepX + direction];
            List<Block> blocksTwoAboveAhead = world[sheepY - 2][sheepX + direction];

            if (!game.isSolid(blocksAboveAhead) && game.isSolid(blocksAhead) && !game.isSolid(blocksTwoAboveAhead)) {
                if (sheepData.getJumpAttempts() < 2) {
                    world[sheepY][sheepX].remove(sheep);
                    world[sheepY - 1][sheepX + direction].add(sheep);
                    newSheepX = sheepX + direction;
                    newSheepY = sheepY - 1;
                    updated = true;
                    sheepData.setJumpAttempts(sheepData.getJumpAttempts() + 1);
                }
            }
        }

        while (updated) {
            if (!game.isSolid(world[newSheepY + 1][newSheepX])) {
                if (newSheepY - sheepY < 3) {
                    world[newSheepY][newSheepX].remove(sheep);
                    world[newSheepY + 1][newSheepX].add(sheep);
                    newSheepY++;
                } else {
                    updated = false;
                }
            } else {
                updated = false;
            }
        }

        sheepData.setMovementCooldown(random.nextInt(3) + 1); // 1-3 iterations cooldown
    }

    private static Sheep.SheepState getSheepState(Block sheep, SheepData sheepData, int direction) {
        boolean isHurt = sheep.getSpriteState().get().name().endsWith("_HURT");
        Sheep.SheepState newState = switch (sheepData.getVariant()) {
            case WHITE -> isHurt ? (direction == 1 ? WHITE_RIGHT_HURT : WHITE_LEFT_HURT)
                    : (direction == 1 ? WHITE_RIGHT : WHITE_LEFT);

            case LIGHT_GRAY -> isHurt ? (direction == 1 ? LIGHT_GRAY_RIGHT_HURT : LIGHT_GRAY_LEFT_HURT)
                    : (direction == 1 ? LIGHT_GRAY_RIGHT : LIGHT_GRAY_LEFT);

            case GRAY -> isHurt ? (direction == 1 ? GRAY_RIGHT_HURT : GRAY_LEFT_HURT)
                    : (direction == 1 ? GRAY_RIGHT : GRAY_LEFT);

            case BLACK -> isHurt ? (direction == 1 ? BLACK_RIGHT_HURT : BLACK_LEFT_HURT)
                    : (direction == 1 ? BLACK_RIGHT : BLACK_LEFT);

            case BROWN -> isHurt ? (direction == 1 ? BROWN_RIGHT_HURT : BROWN_LEFT_HURT)
                    : (direction == 1 ? BROWN_RIGHT : BROWN_LEFT);

            case PINK -> isHurt ? (direction == 1 ? PINK_RIGHT_HURT : PINK_LEFT_HURT)
                    : (direction == 1 ? PINK_RIGHT : PINK_LEFT);
        };
        return newState;
    }

    @Override
    public void spawn(int playerX, int playerY, Game game, Terminal terminal) {
        // Coordinates where player can see
        int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(),
                terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);
        var world = game.getWorld();
        int startX = data[0];
        int endX = data[1];

        // Left side
        int lstartX = startX - 20;
        int lendX = startX - 5;
        int lstartY = playerY - 15;
        int lendY = playerY + 15;

        if (countSheep(lstartX, lendX, lstartY, lendY, game) < 3 && random.nextInt(100) < 2) {
            var spawnLocations = sheepCanSpawn(lstartX, lendX, playerY, game);
            if (!spawnLocations.isEmpty()) {
                for (int i = 0; i < Math.min(4, spawnLocations.size()); i++) {
                    var randomLocation = getRandomEntry(spawnLocations);
                    int x = randomLocation.getKey();
                    int y = randomLocation.getValue();

                    world[y][x].add(ItemBlockSupplier.getEntity("sheep"));
                }
            }
        }

        // Right side
        int rstartX = endX + 5;
        int rendX = endX + 20;
        int rstartY = playerY - 15;
        int rendY = playerY + 15;

        if (countSheep(rstartX, rendX, rstartY, rendY, game) < 3 && random.nextInt(100) < 2) {
            var spawnLocations = sheepCanSpawn(rstartX, rendX, playerY, game);
            if (!spawnLocations.isEmpty()) {
                for (int i = 0; i < Math.min(4, spawnLocations.size()); i++) {
                    var randomLocation = getRandomEntry(spawnLocations);
                    int x = randomLocation.getKey();
                    int y = randomLocation.getValue();

                    world[y][x].add(ItemBlockSupplier.getEntity("sheep"));
                }
            }
        }
    }

    public static <K, V> Map.Entry<K, V> getRandomEntry(HashMap<K, V> map) {
        List<Map.Entry<K, V>> entryList = new ArrayList<>(map.entrySet());
        Random random = new Random();
        return entryList.get(random.nextInt(entryList.size()));
    }

    private HashMap<Integer, Integer> sheepCanSpawn(int startX, int endX, int playerY, Game game) {
        var map = new HashMap<Integer, Integer>();
        var world = game.getWorld();
        for (int x = startX; x <= endX; x++) {
            for (int y = Math.max(0, playerY - 30); y < Math.min(world.length, playerY + 30); y++) {
                if (world[y][x].stream().anyMatch(i -> i.getBlockId().equals("grass"))) {
                    map.put(x, y - 1);
                }
            }
        }

        return map;
    }

    private long countSheep(int startX, int endX, int startY, int endY, Game game) {
        long sheepAmount = 0;
        for (int y = startY; y <= endY; y++) {
            for (int x = startX; x <= endX; x++) {
                sheepAmount += game.getWorld()[y][x].stream().filter(i -> i.getBlockId().equals("sheep")).count();
            }
        }

        return sheepAmount;
    }

    public Sheep.SheepState setHurtAnimation(boolean hurt, Enum current) {
        if (hurt) {
            return switch (current) {
                case WHITE_LEFT, WHITE_LEFT_HURT -> WHITE_LEFT_HURT;
                case WHITE_RIGHT, WHITE_RIGHT_HURT -> WHITE_RIGHT_HURT;

                case LIGHT_GRAY_LEFT, LIGHT_GRAY_LEFT_HURT -> LIGHT_GRAY_LEFT_HURT;
                case LIGHT_GRAY_RIGHT, LIGHT_GRAY_RIGHT_HURT -> LIGHT_GRAY_RIGHT_HURT;

                case GRAY_LEFT, GRAY_LEFT_HURT -> GRAY_LEFT_HURT;
                case GRAY_RIGHT, GRAY_RIGHT_HURT -> GRAY_RIGHT_HURT;

                case BLACK_LEFT, BLACK_LEFT_HURT -> BLACK_LEFT_HURT;
                case BLACK_RIGHT, BLACK_RIGHT_HURT -> BLACK_RIGHT_HURT;

                case BROWN_LEFT, BROWN_LEFT_HURT -> BROWN_LEFT_HURT;
                case BROWN_RIGHT, BROWN_RIGHT_HURT -> BROWN_RIGHT_HURT;

                case PINK_LEFT, PINK_LEFT_HURT -> PINK_LEFT_HURT;
                case PINK_RIGHT, PINK_RIGHT_HURT -> PINK_RIGHT_HURT;
                default -> throw new IllegalStateException("Unexpected value: " + current);
            };
        } else {
            return switch (current) {
                case WHITE_LEFT, WHITE_LEFT_HURT -> WHITE_LEFT;
                case WHITE_RIGHT, WHITE_RIGHT_HURT -> WHITE_RIGHT;

                case LIGHT_GRAY_LEFT, LIGHT_GRAY_LEFT_HURT -> LIGHT_GRAY_LEFT;
                case LIGHT_GRAY_RIGHT, LIGHT_GRAY_RIGHT_HURT -> LIGHT_GRAY_RIGHT;

                case GRAY_LEFT, GRAY_LEFT_HURT -> GRAY_LEFT;
                case GRAY_RIGHT, GRAY_RIGHT_HURT -> GRAY_RIGHT;

                case BLACK_LEFT, BLACK_LEFT_HURT -> BLACK_LEFT;
                case BLACK_RIGHT, BLACK_RIGHT_HURT -> BLACK_RIGHT;

                case BROWN_LEFT, BROWN_LEFT_HURT -> BROWN_LEFT;
                case BROWN_RIGHT, BROWN_RIGHT_HURT -> BROWN_RIGHT;

                case PINK_LEFT, PINK_LEFT_HURT -> PINK_LEFT;
                case PINK_RIGHT, PINK_RIGHT_HURT -> PINK_RIGHT;
                default -> throw new IllegalStateException("Unexpected value: " + current);
            };
        }
    }

    @Override
    public void killed(Game game, Block mob, int x, int y) {
        int amount = random.nextInt(3) + 1;
        var sheepData = (SheepData) mob.getData();

        InventoryItem mutton = new InventoryItem(amount, ItemBlockSupplier.getItem("mutton"));
        InventoryItem wool = new InventoryItem(1, ItemBlockSupplier.getItem(switch (sheepData.getVariant()) {
            case WHITE -> "white_wool";
            case LIGHT_GRAY -> "light_gray_wool";
            case GRAY -> "gray_wool";
            case BLACK -> "black_wool";
            case BROWN -> "brown_wool";
            case PINK -> "pink_wool";
        }));

        game.getInventory().addItem(mutton);
        game.getInventory().addItem(wool);
    }
}
