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

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.Cow;
import cz.jzitnik.tui.ScreenMovingCalculationProvider;
import org.jline.terminal.Terminal;

import java.util.*;

import static cz.jzitnik.game.sprites.Cow.CowState.*;

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

    @Override
    public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) {
        int cowX = entityLogicMobDTO.getX();
        int cowY = entityLogicMobDTO.getY();
        var game = entityLogicMobDTO.getGame();
        var cow = entityLogicMobDTO.getMob();
        var world = game.getWorld();
        var cowData = (CowData) cow.getData();

        boolean updated = false;
        int newCowX = cowX;
        int newCowY = cowY;

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

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

        if (direction == 1) {
            if (cow.getSpriteState().get() == RIGHT_HURT || cow.getSpriteState().get() == LEFT_HURT) {
                cow.setSpriteState(RIGHT_HURT);
            } else {
                cow.setSpriteState(RIGHT);
            }
        } else {
            if (cow.getSpriteState().get() == RIGHT_HURT || cow.getSpriteState().get() == LEFT_HURT) {
                cow.setSpriteState(LEFT_HURT);
            } else {
                cow.setSpriteState(LEFT);
            }
        }

        List<Block> blocksAhead = world[cowY][cowX + direction];
        if (!game.isSolid(blocksAhead)) {
            world[cowY][cowX].remove(cow);
            world[cowY][cowX + direction].add(cow);
            newCowX = cowX + direction;
            updated = true;
            cowData.setJumpAttempts(0);
        } else {
            List<Block> blocksAboveAhead = world[cowY - 1][cowX + direction];
            List<Block> blocksTwoAboveAhead = world[cowY - 2][cowX + direction];

            if (!game.isSolid(blocksAboveAhead) && game.isSolid(blocksAhead) && !game.isSolid(blocksTwoAboveAhead)) {
                if (cowData.getJumpAttempts() < 2) {
                    world[cowY][cowX].remove(cow);
                    world[cowY - 1][cowX + direction].add(cow);
                    newCowX = cowX + direction;
                    newCowY = cowY - 1;
                    updated = true;
                    cowData.setJumpAttempts(cowData.getJumpAttempts() + 1);
                }
            }
        }

        while (updated) {
            if (!game.isSolid(world[newCowY + 1][newCowX])) {
                if (newCowY - cowY < 3) {
                    world[newCowY][newCowX].remove(cow);
                    world[newCowY + 1][newCowX].add(cow);
                    newCowY++;
                } else {
                    updated = false;
                }
            } else {
                updated = false;
            }
        }

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

    @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 (countCows(lstartX, lendX, lstartY, lendY, game) < 3 && random.nextInt(100) < 2) {
            var spawnLocations = cowCanSpawn(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("cow"));
                }
            }
        }

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

        if (countCows(rstartX, rendX, rstartY, rendY, game) < 3 && random.nextInt(100) < 2) {
            var spawnLocations = cowCanSpawn(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("cow"));
                }
            }
        }
    }

    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> cowCanSpawn(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 countCows(int startX, int endX, int startY, int endY, Game game) {
        long cowAmount = 0;
        for (int y = startY; y <= endY; y++) {
            for (int x = startX; x <= endX; x++) {
                cowAmount += game.getWorld()[y][x].stream().filter(i -> i.getBlockId().equals("cow")).count();
            }
        }

        return cowAmount;
    }

    public Cow.CowState setHurtAnimation(boolean hurt, Enum current) {
        if (hurt) {
            return switch (current) {
                case LEFT_HURT, LEFT -> LEFT_HURT;
                case RIGHT_HURT, RIGHT -> RIGHT_HURT;
                default -> throw new IllegalStateException("Unexpected value: " + current);
            };
        }

        return switch (current) {
            case LEFT_HURT, LEFT -> LEFT;
            case RIGHT_HURT, RIGHT -> RIGHT;
            default -> throw new IllegalStateException("Unexpected value: " + current);
        };
    }

    @Override
    public void killed(Game game, Block mob, int x, int y) {
        int leatherAmount = random.nextInt(3);
        InventoryItem inventoryItem = new InventoryItem(leatherAmount, ItemBlockSupplier.getItem("leather"));
        int beefAmount = random.nextInt(3) + 1;
        InventoryItem inventoryItem1 = new InventoryItem(beefAmount, ItemBlockSupplier.getItem("beef"));

        game.getInventory().addItem(inventoryItem);
        game.getInventory().addItem(inventoryItem1);
    }
}
