/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2023  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.listener;

import de.steamwar.inventory.SWItem;
import de.steamwar.towerrun.TowerRun;
import de.steamwar.towerrun.config.Config;
import de.steamwar.towerrun.config.WorldConfig;
import de.steamwar.towerrun.game.TowerRunGame;
import de.steamwar.towerrun.state.GameStateBukkitListener;
import de.steamwar.towerrun.state.GameStates;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.scheduler.BukkitRunnable;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

public class IngameListener extends GameStateBukkitListener {

    private int time = 0;
    private final Map<Integer, List<Block>> blocksToMelt = new HashMap<>();
    private BukkitRunnable blocksToMeltRunnable;
    private BukkitRunnable antiCampRunnable;

    public IngameListener() {
        super(EnumSet.of(GameStates.INGAME));
    }

    @Override
    public void enable() {
        super.enable();
        blocksToMeltRunnable = new BukkitRunnable() {
            @Override
            public void run() {
                List<Block> blocks = blocksToMelt.get(time);
                time++;
                if (blocks == null) {
                    return;
                }
                blocks.forEach(block -> {
                    if (!WorldConfig.MELTING_TIMES.containsKey(block.getType())) return;
                    block.setType(Material.AIR);
                    block.getWorld().playSound(block.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, 0.1F, 1);
                });
            }
        };
        blocksToMeltRunnable.runTaskTimer(TowerRun.getInstance(), 0, 1);

        antiCampRunnable = new BukkitRunnable() {
            @Override
            public void run() {
                double minY = TowerRunGame.PLAYERS_ALIVE.stream()
                        .map(p -> p.player().getLocation().getY())
                        .min(Comparator.comparing(Function.identity()))
                        .orElse(0.0);

                TowerRunGame.PLAYERS_ALIVE.forEach(towerRunPlayer -> {
                    if (towerRunPlayer.player().getLocation().getY() - minY > 20) {
                        towerRunPlayer.player().sendTitle("§a", TowerRun.getMessage().parse("CATCH_UP_WARNING", towerRunPlayer.player()), 5, 30, 5);
                    }
                    if (towerRunPlayer.player().getLocation().getY() - minY > 30) {
                        towerRunPlayer.player().damage(1.0);
                    }
                });
            }
        };
        antiCampRunnable.runTaskTimer(TowerRun.getInstance(), 100, 100);
    }

    @Override
    public void disable() {
        super.disable();
        blocksToMeltRunnable.cancel();
        blocksToMeltRunnable = null;
        blocksToMelt.clear();
        time = 0;

        antiCampRunnable.cancel();
        antiCampRunnable = null;
    }

    @EventHandler
    public void onPlayerDeath(PlayerDeathEvent event) {
        event.setDeathMessage(null);
        Bukkit.getScheduler().runTaskLater(TowerRun.getInstance(), () -> {
            if (TowerRun.getTowerGenerator() != null) {
                event.getEntity().teleport(TowerRun.getTowerGenerator().getSpawn());
            } else {
                event.getEntity().teleport(WorldConfig.SPAWN);
            }
        }, 5
        );
        event.getEntity().setGameMode(GameMode.SPECTATOR);
        Bukkit.getOnlinePlayers().forEach(player -> {
            player.sendTitle("", TowerRun.getMessage().parse("PLAYER_DIED", player, event.getEntity().getPlayer().getName()), 10, 70, 20);
            player.playSound(player.getLocation(), Sound.ENTITY_WITHER_DEATH, 1, 1);
        });
    }

    @EventHandler
    public void onBlockBreak(BlockBreakEvent event) {
        event.setDropItems(false);
        if (!Config.DESTROYABLE_BLOCKS.contains(event.getBlock().getType())) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onKeyUse(PlayerInteractEvent event) {
        if (!event.hasItem()) return;
        if (event.getItem().getType() != Material.LEVER) return;
        event.setCancelled(true);
        if (!event.hasBlock()) return;
        if (event.getClickedBlock().getType() != Material.IRON_DOOR) return;
        event.getPlayer().getInventory().setItemInMainHand(null);
        event.getClickedBlock().breakNaturally();
    }

    @EventHandler
    public void onItemSpawn(ItemSpawnEvent event) {
        event.setCancelled(true);
    }

    @EventHandler
    public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
        if (event.getDamager().getType() == EntityType.PLAYER) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
        if (event.getPlayer().getGameMode() != GameMode.SURVIVAL) {
            return;
        }

        if (event.getAction() != Action.RIGHT_CLICK_BLOCK || !event.hasBlock()) {
            return;
        }

        if (event.getClickedBlock().getType() != Material.CHEST) {
            return;
        }

        Stream<Location> locations = TowerRun.getTowerGenerator() == null ? Arrays.stream(WorldConfig.KEYS) : TowerRun.getTowerGenerator().getKeys().stream();
        if (locations.noneMatch(location -> location.equals(event.getClickedBlock().getLocation()))) {
            event.getClickedBlock().setType(Material.AIR);
            return;
        }

        event.getPlayer().getInventory().addItem(new SWItem(Material.LEVER, TowerRun.getMessage().parse("KEY_NAME", event.getPlayer())).getItemStack());

        event.getClickedBlock().setType(Material.ENDER_CHEST);
        event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1, 1);
        TowerRun.getMessage().broadcast("KEY_FOUND", event.getPlayer().getName());
    }

    @EventHandler
    public void onBlockPhysics(BlockPhysicsEvent event) {
        if (event.getSourceBlock().getType() != Material.LAVA) {
            return;
        }
        Block block = event.getSourceBlock();
        shouldMelt(block.getRelative(0, 1, 0));
        shouldMelt(block.getRelative(0, -1, 0));
        shouldMelt(block.getRelative(1, 0, 0));
        shouldMelt(block.getRelative(-1, 0, 0));
        shouldMelt(block.getRelative(0, 0, 1));
        shouldMelt(block.getRelative(0, 0, -1));
    }

    private void shouldMelt(Block block) {
        int meltingTime = WorldConfig.MELTING_TIMES.getOrDefault(block.getType(), -1);
        if (meltingTime == -1) {
            return;
        }
        blocksToMelt.computeIfAbsent(time + meltingTime * 20, integer -> new ArrayList<>()).add(block);
    }

    @EventHandler
    public void onEntityRegainHealth(EntityRegainHealthEvent event) {
        if (event.getEntityType() == EntityType.PLAYER) {
            event.setCancelled(true);
        }
    }
}
