package model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import util.Direction;
import util.Position;
import util.PositionUtil;

public class EntitySpawner {
    private final Board<Square> board;
    private final Random random = new Random();

    public EntitySpawner(Board<Square> board) {
        this.board = board;
    }

    public void spawnEntities(Map<EntityFactory, Integer> entityCounts) {
        Map<EntityFactory, Integer> counts = new HashMap<>();
        for (EntityFactory factory : entityCounts.keySet()) {
            counts.put(factory, 0);
        }

        int totalEntitiesToPlace = entityCounts.values().stream().mapToInt(Integer::intValue).sum();
        int totalEntitiesPlaced = 0;

        int chance = 5;
        List<Position> positions = generateAllPositions();

        while (totalEntitiesPlaced < totalEntitiesToPlace) {
            Collections.shuffle(positions);

            for (Position pos : positions) {
                if (board.getStates(pos).isEmpty()) {
                    for (EntityFactory factory : entityCounts.keySet()) {
                        int desiredCount = entityCounts.get(factory);
                        int currentCount = counts.get(factory);

                        if (currentCount < desiredCount && random.nextInt(100) < chance) {
                            Entity entity = factory.create(pos, board);
                            board.setSquare(new Square(pos, entity));
                            counts.put(factory, currentCount + 1);
                            totalEntitiesPlaced++;

                            if (totalEntitiesPlaced == totalEntitiesToPlace) {
                                return;
                            }

                            break; // Move to the next position
                        }
                    }
                }
            }

            // Increase chance after each full traversal
            chance = Math.min(chance + 5, 100);
        }
    }

    private List<Position> generateAllPositions() {
        List<Position> positions = new ArrayList<>();
        for (int x = 0; x < board.rowCount(); x++) {
            for (int y = 0; y < board.columnCount(); y++) {
                positions.add(new Position(x, y));
            }
        }
        return positions;
    }

    public static void generateEntitiesInLine(Board<Square> board, Position anchor, EntityFactory entityFactory) {
        int xIncrement = 0;
        int yIncrement = 0;
    
        // Determine increments based on which coordinate is zero
        if (anchor.x() == 0 && anchor.y() >= 0) {
            // Starting from the left edge (x == 0), increment x to move right
            xIncrement = 1;
        } else if (anchor.y() == 0 && anchor.x() >= 0) {
            // Starting from the top edge (y == 0), increment y to move down
            yIncrement = 1;
        } else {
            // If neither x nor y is 0, cannot determine direction
            throw new IllegalArgumentException("Anchor position must have x or y equal to 0");
        }
    
        int x = anchor.x();
        int y = anchor.y();
    
        // Continue until we reach the edge of the board
        while (board.doesPositionExist(new Position(x, y))) {
            Position pos = new Position(x, y);
            // Create a new entity for each position
            Entity entity = entityFactory.create(pos, board);
            entity.setPosition(pos); // Set the position if not already set in the factory
            board.addEntityAtSquare(entity, pos);
    
            x += xIncrement;
            y += yIncrement;
        }
    }
    
    public static List<Position> generateEntitiesInRandomLine(Board<Square> board, Position anchor, int maxStepsInDirection, int minimumRoadLength) {
        Random random = new Random();
        List<Position> path = new ArrayList<>();
        int x = anchor.x();
        int y = anchor.y();
    
        // Toutes les directions possibles
        List<Direction> allDirections = Arrays.asList(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST);
    
        // Choisir une direction initiale aléatoire
        Direction initialDirection = allDirections.get(random.nextInt(allDirections.size()));
        path.add(new Position(x, y)); // Ajouter la position de l'ancre au chemin
        int roadLength = 1;
    
        // Déterminer la direction interdite (opposée à la direction initiale)
        Direction forbiddenDirection = PositionUtil.getOppositeDirection(initialDirection);
    
        // Initialiser la direction courante
        Direction currentDirection = initialDirection;
        int stepsInCurrentDirection = 0;
    
        // Ensemble des directions définitivement exclues (direction initiale)
        Set<Direction> permanentlyExcludedDirections = new HashSet<>();
        permanentlyExcludedDirections.add(initialDirection);
    
        // Ensemble des directions temporairement exclues (initialement vide)
        Set<Direction> temporarilyExcludedDirections = new HashSet<>();
    
        while (true) {
            // Calculer la prochaine position dans la direction courante
            int nextX = x + getXIncrement(currentDirection);
            int nextY = y + getYIncrement(currentDirection);
            Position nextPos = new Position(nextX, nextY);
    
            if (board.doesPositionExist(nextPos)) {
                // Ajouter la position au chemin
                path.add(nextPos);
                x = nextX;
                y = nextY;
                roadLength++;
                stepsInCurrentDirection++;
            } else {
                // La position dans la direction courante est invalide
                if (roadLength < minimumRoadLength) {
                    // Exclure temporairement la direction courante
                    temporarilyExcludedDirections.add(currentDirection);
    
                    // Choisir une nouvelle direction valide
                    Direction newDirection = chooseNewDirection(allDirections, currentDirection, forbiddenDirection, 
                            permanentlyExcludedDirections, temporarilyExcludedDirections, board, x, y, random);
    
                    if (newDirection == null) {
                        // Aucune direction valide disponible pour atteindre la longueur minimale
                        break;
                    }
    
                    // Mettre à jour la direction courante
                    currentDirection = newDirection;
                    forbiddenDirection = PositionUtil.getOppositeDirection(currentDirection);
                    stepsInCurrentDirection = 0;
                    continue; // Recommencer avec la nouvelle direction
                } else {
                    // La longueur minimale est atteinte, arrêter la génération
                    break;
                }
            }
    
            // Vérifier s'il est temps de changer de direction
            if (stepsInCurrentDirection >= maxStepsInDirection) {
                // Choisir une nouvelle direction
                Direction newDirection = chooseNewDirection(allDirections, currentDirection, forbiddenDirection, 
                        permanentlyExcludedDirections, temporarilyExcludedDirections, board, x, y, random);
    
                if (newDirection == null) {
                    // Aucune direction valide disponible
                    break;
                }
    
                // Mettre à jour la direction courante
                currentDirection = newDirection;
                forbiddenDirection = PositionUtil.getOppositeDirection(currentDirection);
                stepsInCurrentDirection = 0;
            }
        }
    
        return path;  // Retourner la liste des positions formant le serpent
    }
    
    /**
     * Choisit une nouvelle direction valide en tenant compte des exclusions permanentes et temporaires.
     *
     * @param allDirections Toutes les directions possibles.
     * @param currentDirection La direction actuelle.
     * @param forbiddenDirection La direction opposée à la direction actuelle (interdite).
     * @param permanentlyExcludedDirections Les directions définitivement exclues.
     * @param temporarilyExcludedDirections Les directions temporairement exclues.
     * @param board Le plateau de jeu.
     * @param x La coordonnée X actuelle.
     * @param y La coordonnée Y actuelle.
     * @param random Une instance de Random pour le choix aléatoire.
     * @return La nouvelle direction choisie ou null si aucune direction valide n'est disponible.
     */
    private static Direction chooseNewDirection(List<Direction> allDirections, Direction currentDirection, Direction forbiddenDirection,
                                               Set<Direction> permanentlyExcludedDirections, Set<Direction> temporarilyExcludedDirections,
                                               Board<Square> board, int x, int y, Random random) {
        // Créer une liste de directions valides en excluant :
        // - La direction actuelle
        // - La direction interdite (opposée à la direction actuelle)
        // - Les directions définitivement exclues
        // - Les directions temporairement exclues
        List<Direction> validDirections = new ArrayList<>(allDirections);
        validDirections.remove(currentDirection);
        validDirections.remove(forbiddenDirection);
        validDirections.removeAll(permanentlyExcludedDirections);
        validDirections.removeAll(temporarilyExcludedDirections);
    
        // Filtrer les directions qui permettent de continuer le chemin
        validDirections.removeIf(dir -> !board.doesPositionExist(new Position(x + getXIncrement(dir), y + getYIncrement(dir))));
    
        if (validDirections.isEmpty()) {
            // Aucune direction valide disponible
            return null;
        }
    
        // Choisir une nouvelle direction aléatoirement
        return validDirections.get(random.nextInt(validDirections.size()));
    }
    
    private static int getXIncrement(Direction direction) {
        switch (direction) {
            case NORTH: return -1;
            case SOUTH: return 1;
            default:    return 0;
        }
    }
    
    private static int getYIncrement(Direction direction) {
        switch (direction) {
            case EAST:  return 1;
            case WEST:  return -1;
            default:    return 0;
        }
    }
    

    
    
    

    
    

    
}