package model;

import util.Position;
import util.TargetStrategy;

import java.util.*;


public class FirefighterBoard implements Board<List<ModelElement>> {
  private final int columnCount;
  private final int rowCount;
  private final int initialFireCount;
  private final int initialFirefighterCount;
  private final TargetStrategy targetStrategy = new TargetStrategy();
  private List<FireFighter> firefighter;
  private Set<Fire> fire;
  private Map<Position, List<Position>> neighbors = new HashMap();
  private final Position[][] positions;
  private int step = 0;
  private final Random randomGenerator = new Random();

  public FirefighterBoard(int columnCount, int rowCount, int initialFireCount, int initialFirefighterCount) {
    this.columnCount = columnCount;
    this.rowCount = rowCount;
    this.positions = new Position[rowCount][columnCount];
    for (int column = 0; column < columnCount; column++)
      for (int row = 0; row < rowCount; row++)
        positions[row][column] = new Position(row, column);
    for (int column = 0; column < columnCount; column++)
      for (int row = 0; row < rowCount; row++) {
        List<Position> list = new ArrayList<>();
        if (row > 0) list.add(positions[row - 1][column]);
        if (column > 0) list.add(positions[row][column - 1]);
        if (row < rowCount - 1) list.add(positions[row + 1][column]);
        if (column < columnCount - 1) list.add(positions[row][column + 1]);
        neighbors.put(positions[row][column], list);
      }
    this.initialFireCount = initialFireCount;
    this.initialFirefighterCount = initialFirefighterCount;
    initializeElements();
  }

  public void initializeElements() {
    firefighter= new ArrayList<>();
    fire = new HashSet<>();
    for (int index = 0; index < initialFireCount; index++)
      fire.add(new Fire(randomPosition()));
    for (int index = 0; index < initialFirefighterCount; index++)
      firefighter.add(new FireFighter(randomPosition()));
  }

  private Position randomPosition() {
    return new Position(randomGenerator.nextInt(rowCount), randomGenerator.nextInt(columnCount));
  }

  @Override
  public List<ModelElement> getState(Position position) {
    List<ModelElement> result = new ArrayList<>();
    for (FireFighter firefighter : firefighter)
      if (firefighter.getFirefighterPosition().equals(position))
        result.add(ModelElement.FIREFIGHTER);

    for (Fire fire: fire)
      if (fire.getFirePositions().equals(position))
       result.add(ModelElement.FIRE);
    return result;
  }

  @Override
  public int rowCount() {
    return rowCount;
  }

  @Override
  public int columnCount() {
    return columnCount;
  }

  public List<Position> updateToNextGeneration() {
    List<Position> modifiedPositions = updateFirefighters();
    modifiedPositions.addAll(updateFires());
    step++;
    return modifiedPositions;
  }

  private List<Position> updateFires() {
    List<Position> modifiedPositions = new ArrayList<>();
    if (step % 2 == 0) {
      List<Position> newFirePositions = new ArrayList<>();
      for (Fire fire : fire) {
        newFirePositions.addAll(neighbors.get(fire));
      }
      for(Position position : newFirePositions) {
        fire.add(new Fire(position));
      }
      modifiedPositions.addAll(newFirePositions);
    }
    return modifiedPositions;

  }

  @Override
  public int stepNumber() {
    return step;
  }

  private List<Position> updateFirefighters() {
    List<Position> modifiedPosition = new ArrayList<>();
    List<Position> firefighterNewPositions = new ArrayList<>();
    for (FireFighter firefighterPosition : firefighter) {
      Position newFirefighterPosition =
              targetStrategy.neighborClosestToFire(firefighterPosition,
                      firePositions, neighbors);
      firefighterNewPositions.add(newFirefighterPosition);
      extinguish(newFirefighterPosition);
      modifiedPosition.add(firefighterPosition.getFirefighterPosition());
      modifiedPosition.add(newFirefighterPosition);
      List<Position> neighborFirePositions = neighbors.get(newFirefighterPosition).stream()
              .filter(firePositions::contains).toList();
      for (Position firePosition : neighborFirePositions)
        extinguish(firePosition);
      modifiedPosition.addAll(neighborFirePositions);
    }
    firefighterPositions = firefighterNewPositions;
    return modifiedPosition;
  }

  @Override
  public void reset() {
    step = 0;
    initializeElements();
  }

  private void extinguish(Position position) {
    fire.remove(position);
  }


  @Override
  public void setState(List<ModelElement> state, Position position) {
    extinguish(position);
    for (; ; ) {
      if (!firefighterPostion.remove(position)) break;
    }
    for (ModelElement element : state) {
      switch (element) {
        case FIRE -> fire.add(new Fire(position));
        case FIREFIGHTER -> firefighter.add(new FireFighter(position));
      }
    }
  }
}