/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.towerrun.game;

import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.towerrun.TowerRun;
import de.steamwar.towerrun.config.Config;
import de.steamwar.towerrun.config.WorldConfig;
import de.steamwar.towerrun.state.GameState;
import de.steamwar.towerrun.state.GameStates;
import lombok.experimental.UtilityClass;
import net.minecraft.world.level.chunk.Chunk;
import org.bukkit.*;
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.ObjIntConsumer;

@UtilityClass
public class TowerRunGame {
    public static final List<TowerRunPlayer> PLAYERS_ALIVE = new ArrayList<>();
    public static final List<TowerRunPlayer> PLAYERS_ESCAPED = new ArrayList<>();
    private static final World world = Bukkit.getWorlds().get(0);

    public static boolean isEscaped(TowerRunPlayer player) {
        return PLAYERS_ESCAPED.contains(player);
    }

    public static boolean isAlive(TowerRunPlayer player) {
        return PLAYERS_ALIVE.contains(player);
    }

    public static void prepareTowerOrStart() {
        if (GameState.getCurrentState() == GameStates.WAITING) {
            GameState.nextState();
            if (TowerRun.getTowerGenerator() == null) {
                start();
                return;
            }

            TowerRun.getTowerGenerator().generate();
        } else {
            throw new IllegalStateException("Game is already running!");
        }
    }

    public static void start() {
        if (GameState.getCurrentState() == GameStates.GENERATING_TOWER) {
            PLAYERS_ALIVE.forEach(p -> {
                p.reset();
                p.player().setGameMode(GameMode.SURVIVAL);
            });
            GameState.nextState();
            generateLava();
            TowerRun.getMessage().broadcast("GAME_START");

            for (Location door : WorldConfig.DOORS) {
                door.getBlock().setType(Material.AIR);
                door.clone().add(0, 1, 0).getBlock().setType(Material.AIR);
            }
        } else {
            throw new IllegalStateException("Game is already running!");
        }
    }

    private static void generateLava() {
        for (int x = WorldConfig.MIN_TOWER.getBlockX(); x < WorldConfig.MAX_TOWER.getBlockX(); x += WorldConfig.LAVE_SPACE) {
            for (int z = WorldConfig.MIN_TOWER.getBlockZ(); z < WorldConfig.MAX_TOWER.getBlockZ(); z += WorldConfig.LAVE_SPACE) {
                Vector pos = new Vector(x, 0, z);
                if (Arrays.stream(WorldConfig.REGIONS).anyMatch(region -> region.contains(pos))) {
                    int offset = WorldConfig.LAVA_Y;
                    if (TowerRun.getTowerGenerator() != null) {
                        offset += TowerRun.getTowerGenerator().getHeight();
                    }
                    WorldConfig.MIN_TOWER.getWorld().getBlockAt(x, offset, z).setType(Material.LAVA, true);
                    WorldConfig.MIN_TOWER.getWorld().getBlockAt(x, offset - 1, z).setType(Material.BEDROCK, true);
                }
            }
        }
    }

    public static void remove(TowerRunPlayer towerRunPlayer) {
        PLAYERS_ALIVE.remove(towerRunPlayer);
        PLAYERS_ESCAPED.remove(towerRunPlayer);
    }

    public static void tie() {
        Bukkit.getOnlinePlayers().forEach(player -> player.setGameMode(GameMode.SPECTATOR));
        PLAYERS_ALIVE.clear();
        Bukkit.getOnlinePlayers().forEach(player -> player.sendTitle(TowerRun.getMessage().parse("GAME_TIE", player), "", 10, 70, 20));
        GameState.nextState();

        if (Config.event()) {
            Config.EVENT_FIGHT.setErgebnis(3);
        }
    }

    public static void win(TowerRunPlayer tPlayer) {
        Bukkit.getOnlinePlayers().forEach(player -> player.setGameMode(GameMode.SPECTATOR));
        PLAYERS_ALIVE.clear();
        tPlayer.player().setGameMode(GameMode.SPECTATOR);
        TowerRun.getMessage().broadcast("GAME_WIN", tPlayer.player().getName());
        Bukkit.getOnlinePlayers().forEach(player -> {
            player.sendTitle(TowerRun.getMessage().parse("GAME_WIN", player, tPlayer.player().getName()), "", 10, 70, 20);
            player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_DEATH, 1, 1);
        });
        GameState.nextState();

        if (Config.event()) {
            Config.EVENT_FIGHT.setErgebnis(SteamwarUser.get(tPlayer.player().getUniqueId()).getTeam() == Config.EVENT_TEAM_BLUE_ID ? 1 : 2);
        }
    }

    public static void reset() {
        PLAYERS_ALIVE.clear();
        PLAYERS_ESCAPED.clear();
        resetWorld();
        GameState.reset();
        Bukkit.getOnlinePlayers().forEach(player -> {
            if (TowerRun.getTowerGenerator() != null) {
                player.setGameMode(GameMode.SPECTATOR);
            } else {
                player.setGameMode(GameMode.SURVIVAL);
            }
            player.teleport(WorldConfig.SPAWN);
            PLAYERS_ALIVE.add(TowerRunPlayer.get(player));
        });
    }

    private static double posToChunk(int pos) {
        return pos / 16.0;
    }

    private static int getMinChunkX() {
        return (int) Math.floor(posToChunk(WorldConfig.MAP_MIN_X));
    }

    private static int getMaxChunkX() {
        return (int) Math.ceil(posToChunk(WorldConfig.MAP_MAX_X));
    }

    private static int getMinChunkZ() {
        return (int) Math.floor(posToChunk(WorldConfig.MAP_MIN_Z));
    }

    private static int getMaxChunkZ() {
        return (int) Math.ceil(posToChunk(WorldConfig.MAP_MAX_Z));
    }

    private static void forEachChunk(ObjIntConsumer<Integer> executor) {
        for (int x = getMinChunkX(); x <= getMaxChunkX(); x++)
            for (int z = getMinChunkZ(); z <= getMaxChunkZ(); z++)
                executor.accept(x, z);
    }

    private static void resetWorld() {
        world.getEntities().stream().filter(entity -> entity.getType() != EntityType.PLAYER).forEach(Entity::remove);

        World backup = new WorldCreator(world.getName() + "/backup").createWorld();
        assert backup != null;
        forEachChunk((x, z) -> resetChunk(backup, x, z));
        Bukkit.unloadWorld(backup, false);
    }

    private static void resetChunk(World backup, int x, int z) {
        Chunk chunk = ((CraftWorld) world).getHandle().d(x, z);
        Chunk backupChunk = ((CraftWorld) backup).getHandle().d(x, z);

        System.arraycopy(backupChunk.d(), 0, chunk.d(), 0, chunk.d().length);

        for (Player p : Bukkit.getOnlinePlayers())
            CraftbukkitWrapper.impl.sendChunk(p, x, z);
    }
}
