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

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

import java.util.*;

import static cz.jzitnik.game.sprites.Pig.PigState.*;

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

    @Override
    public void nextIteration(EntityLogicProvider.EntityLogicMobDTO entityLogicMobDTO) {
        int pigX = entityLogicMobDTO.getX();
        int pigY = entityLogicMobDTO.getY();
        var game = entityLogicMobDTO.getGame();
        var pig = entityLogicMobDTO.getMob();
        var world = game.getWorld();
        var pigData = (PigData) pig.getData();

        boolean updated = false;
        int newPigX = pigX;
        int newPigY = pigY;

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

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

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

        List<Block> blocksAhead = world[pigY][pigX + direction];
        if (!game.isSolid(blocksAhead)) {
            world[pigY][pigX].remove(pig);
            world[pigY][pigX + direction].add(pig);
            newPigX = pigX + direction;
            updated = true;
            pigData.setJumpAttempts(0);
        } else {
            List<Block> blocksAboveAhead = world[pigY - 1][pigX + direction];
            List<Block> blocksTwoAboveAhead = world[pigY - 2][pigX + direction];

            if (!game.isSolid(blocksAboveAhead) && game.isSolid(blocksAhead) && !game.isSolid(blocksTwoAboveAhead)) {
                if (pigData.getJumpAttempts() < 2) {
                    world[pigY][pigX].remove(pig);
                    world[pigY - 1][pigX + direction].add(pig);
                    newPigX = pigX + direction;
                    newPigY = pigY - 1;
                    updated = true;
                    pigData.setJumpAttempts(pigData.getJumpAttempts() + 1);
                }
            }
        }

        while (updated) {
            if (!game.isSolid(world[newPigY + 1][newPigX])) {
                if (newPigY - pigY < 3) {
                    world[newPigY][newPigX].remove(pig);
                    world[newPigY + 1][newPigX].add(pig);
                    newPigY++;
                } else {
                    updated = false;
                }
            } else {
                updated = false;
            }
        }

        pigData.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 (countPrasata(lstartX, lendX, lstartY, lendY, game) < 3 && random.nextInt(100) < 2) {
            var spawnLocations = pigCanSpawn(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("pig"));
                }
            }
        }

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

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

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

        return pigAmount;
    }

    public Pig.PigState 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 amount = random.nextInt(2) + 1;
        InventoryItem inventoryItem = new InventoryItem(amount, ItemBlockSupplier.getItem("porkchop"));
        game.getInventory().addItem(inventoryItem);
    }
}
