package de.towerdefence.server.game;

import java.lang.reflect.Constructor;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

import de.towerdefence.server.game.callbacks.EnemiesChangedCallback;
import de.towerdefence.server.game.callbacks.PlayerMoneyCallback;
import de.towerdefence.server.game.callbacks.PlayerHitpointsCallback;
import de.towerdefence.server.game.exeptions.InvalidPlacementException;
import de.towerdefence.server.game.exeptions.InvalidPlacementReason;
import de.towerdefence.server.game.exeptions.WaveInProgressException;
import de.towerdefence.server.player.Player;
import lombok.Getter;
import org.joml.Vector2i;

public class GameSession {
    final static int START_HITPOINTS = 100;
    final static int START_MONEY = 50;
    final static Vector2i MAP_SIZE = new Vector2i(10, 20);
    final static int WAVE_SPAWN_DELAY = 200; // ms
    final static Vector2i WAVE_SPAWN = new Vector2i(5, 0);
    final static Vector2i WAVE_TARGET = new Vector2i(5, 19);

    private final Player player;
    @Getter
    private int money;
    @Getter
    private int playerHitpoints;
    private final PlayerMoneyCallback moneyCallback;
    private final PlayerHitpointsCallback hitpointsCallback;
    private final EnemiesChangedCallback enemyCallback;

    private final Tower[][] towers = new Tower[MAP_SIZE.x][MAP_SIZE.y];
    private final Enemy[][] enemies = new Enemy[MAP_SIZE.x][MAP_SIZE.y];
    private List<Enemy> wave = new ArrayList<>();
    private long lastSpawn = Instant.now().toEpochMilli();

    public GameSession(
            Player player,
            PlayerMoneyCallback moneyCallback,
            PlayerHitpointsCallback hitpointsCallback,
            EnemiesChangedCallback enemyCallback) {
        this.player = player;
        this.moneyCallback = moneyCallback;
        this.hitpointsCallback = hitpointsCallback;
        this.enemyCallback = enemyCallback;
        this.money = START_MONEY;
        this.playerHitpoints = START_HITPOINTS;

    }

    public void startWave(List<Enemy> enemies) throws WaveInProgressException {
        if (wave.size() > 0) {
            throw new WaveInProgressException();
        }
        wave = enemies;
    }

    public void update() {
        List<Enemy> newEnemies = new ArrayList<>();

        List<Enemy> changedEnemies = moveEnemies();

        if (wave.size() > 0 && shouldSpawn()) {
            enemies[WAVE_SPAWN.x][WAVE_SPAWN.y] = wave.removeFirst();
            newEnemies.add(enemies[WAVE_SPAWN.x][WAVE_SPAWN.y]);
        }

        if (newEnemies.size() > 0 || changedEnemies.size() > 0) {
            enemyCallback.call(
                    player,
                    newEnemies.toArray(new Enemy[newEnemies.size()]),
                    changedEnemies.toArray(new Enemy[changedEnemies.size()]));
        }
    }

    private boolean shouldSpawn() {
        return lastSpawn + WAVE_SPAWN_DELAY < Instant.now().toEpochMilli()
                && enemies[WAVE_SPAWN.x][WAVE_SPAWN.y] == null;
    }

    private List<Enemy> moveEnemies() {
        // List<Enemy> changedEnemies = moveEnemies();

        // TODO: Implement Moving of Enemies (possibly through A*)
        throw new RuntimeException("NOT IMPLEMENTED");

        // return changedEnemies;

    }

    /**
     * @return the next position to go to
     */
    private Vector2i pathfinding(Vector2i start, Vector2i target) {

    }

    public void placeTower(Tower tower, int x, int y) throws InvalidPlacementException {
        if (x < 0 || y < 0 || x + 1 > MAP_SIZE.x || y + 1 > MAP_SIZE.y) {
            throw new InvalidPlacementException(InvalidPlacementReason.OUT_OF_BOUNDS);
        }
        if (towers[x][y] != null) {
            throw new InvalidPlacementException(InvalidPlacementReason.LOCATION_USED);
        }
        if (money < Tower.COST) {
            throw new InvalidPlacementException(InvalidPlacementReason.NOT_ENOUGH_MONEY);
        }

        // TODO: Do Pathfinding check for the placement

        money -= Tower.COST;
        moneyCallback.call(player, money);
        tower.x = x;
        tower.y = y;
        towers[x][y] = tower;
    }

    public void addMoney(int amount) {
        money += amount;
        moneyCallback.call(player, money);
    }
}