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

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.utils.RegionExtensionType;
import de.steamwar.bausystem.region.utils.RegionType;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.scheduler.BukkitTask;

import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class Loadtimer implements Listener {

    @Getter
    private static final Map<Region, Loadtimer> timers = new HashMap<>();
    private final Region region;
    private final Map<Player, Integer> tntPlaced = new HashMap<>();
    private final BukkitTask task;
    private final Map<Player, BossBar> bars = new HashMap<>();
    private Stage stage;
    private final boolean finishOnActive;

    private long start = -1;
    private long activate = -1;
    private long ignite = -1;
    private long explode = -1;

    public Loadtimer(Region region, boolean finishOnActive) {
        this.finishOnActive = finishOnActive;
        this.region = region;
        this.stage = Stage.WAITING;
        Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
        task = BauSystem.runTaskTimer(BauSystem.getInstance(), () -> {
            if (stage == Stage.COUNTING) {
                long timeSinceStart = TPSUtils.currentRealTick.get() - start;
                long timeSinceHalf = timeSinceStart / 2;
                double timeSec = (timeSinceStart / 20d);
                String sec = new DecimalFormat("#.#").format(timeSec);
                AtomicInteger tnt = new AtomicInteger();
                tntPlaced.forEach((player, integer) -> tnt.addAndGet(integer));
                sendBarToRegion(BarStyle.SEGMENTED_20, "LOADTIMER_BOSSBAR", BarColor.GREEN, (timeSinceStart % 20d) / 20, timeSinceStart, timeSinceHalf, sec, tnt);
            } else if (stage == Stage.WAITING) {
                sendBarToRegion(BarStyle.SOLID, "LOADTIMER_WAITING", BarColor.GREEN, 1);
            } else if (stage == Stage.ACTIVATED || stage == Stage.IGNITION) {
                sendBarToRegion(BarStyle.SOLID, stage == Stage.ACTIVATED ? "LOADTIMER_ACTIVATED" : "LOADTIMER_IGNITION", BarColor.YELLOW, 1);
            }
        }, 1, 1);
    }

    private void sendBarToRegion(BarStyle style, String message, BarColor color, double progress, Object... objects) {
        Bukkit.getOnlinePlayers().forEach(player -> {
            if (Region.getRegion(player.getLocation()).equals(region)) {
                BossBar bar = getOrDefault(player);
                bar.setStyle(style);
                bar.setTitle(BauSystem.MESSAGE.parse(message, player, objects));
                bar.setColor(color);
                bar.setProgress(progress);
            } else if (bars.containsKey(player)) {
                bars.remove(player).removeAll();
            }
        });
    }

    public static boolean hasTimer(Region r) {
        return timers.containsKey(r);
    }

    public static Loadtimer getTimer(Region r) {
        return timers.get(r);
    }

    public static Loadtimer createLoadtimer(Region r, boolean finishOnActive) {
        return timers.computeIfAbsent(r, region1 -> new Loadtimer(region1, finishOnActive));
    }

    private BossBar getOrDefault(Player player) {
        return bars.computeIfAbsent(player, player1 -> {
            BossBar bar1 = Bukkit.createBossBar("%PLACEHOLDER%", BarColor.GREEN, BarStyle.SEGMENTED_20);
            bar1.addPlayer(player1);
            return bar1;
        });
    }

    public void onTntPlace(BlockPlaceEvent event) {
        if (stage == Stage.WAITING) {
            this.stage = Stage.COUNTING;
            this.start = TPSUtils.currentRealTick.get();
        }

        if (stage == Stage.COUNTING) {
            tntPlaced.put(event.getPlayer(), tntPlaced.getOrDefault(event.getPlayer(), 0) + 1);
        }
    }

    public void onInteract(PlayerInteractEvent event) {
        if (stage == Stage.COUNTING) {
            if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                Material type = event.getClickedBlock().getType();
                if (type == Material.IRON_TRAPDOOR) {
                    return;
                }
                if (type.name().contains("_BUTTON") || type == Material.LEVER || type.name().contains("_TRAPDOOR") || type == Material.NOTE_BLOCK) {
                    setActivate();
                }
            } else if (event.getAction() == Action.PHYSICAL) {
                setActivate();
            }
        }
    }

    public void onTntSpawn() {
        if ((stage == Stage.COUNTING || stage == Stage.ACTIVATED)) {
            stage = Stage.IGNITION;
            ignite = TPSUtils.currentRealTick.get();
            if (activate == -1)
                activate = TPSUtils.currentRealTick.get();
            if (finishOnActive) {
                stage = Stage.END;
                print();
                delete();
            }
        }
    }

    public void onTntExplode(EntityExplodeEvent event) {
        if (region.inRegion(event.getLocation(), RegionType.BUILD, RegionExtensionType.EXTENSION) && stage == Stage.IGNITION) {
            stage = Stage.END;
            explode = TPSUtils.currentRealTick.get();
            print();
            delete();
        }
    }

    private void setActivate() {
        activate = TPSUtils.currentRealTick.get();
        stage = Stage.ACTIVATED;
        if (finishOnActive) {
            print();
            delete();
        }
    }

    public void print() {
        long loadTime = activate - start;
        int allTnt = 0;
        for (Map.Entry<Player, Integer> e : tntPlaced.entrySet()) {
            allTnt += e.getValue();
        }

        long ignTime = ignite - activate;
        long explTime = explode - ignTime - activate;
        if (explTime < 0)
            explTime = loadTime;

        int finalAllTnt = allTnt;
        long finalExplTime = explTime;
        Bukkit.getOnlinePlayers().forEach(player -> {
            if (Region.getRegion(player.getLocation()).equals(region)) {
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_HEAD", player);
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_PLAYERTABLE_HEAD", player);
                for (Map.Entry<Player, Integer> e : tntPlaced.entrySet()) {
                    BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_PLAYERTABLE_PLAYER", player, e.getKey().getName(), e.getValue(), new DecimalFormat("#.#").format(e.getValue() / (loadTime / 20D)));
                }
                if (tntPlaced.size() > 1) {
                    BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_PLAYERTABLE_PLAYER", player, BauSystem.MESSAGE.parse("LOADTIMER_SUMARY_PLAYERTABLE_ALL", player), finalAllTnt, new DecimalFormat("#.#").format(finalAllTnt / (loadTime / 20D)));
                }
                player.sendMessage("");
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_HEAD", player);
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_START", player);
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_ACTIVATION", player, new DecimalFormat("#.#").format((loadTime / 20D)), loadTime);
                if (!finishOnActive) {
                    BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_IGNITION", player, new DecimalFormat("#.#").format((ignTime / 20D)), ignTime);
                    BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_EXPLOSION", player, new DecimalFormat("#.#").format((finalExplTime / 20D)), finalExplTime);
                }
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_TIMES_LAST", player);
                player.sendMessage("");
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_STATS_HEAD", player);
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_STATS_TNT", player, finalAllTnt);
                BauSystem.MESSAGE.sendPrefixless("LOADTIMER_SUMARY_STATS_FREQ", player, 60D / (loadTime / 20D), 60D / ((finalExplTime + Math.max(ignTime, 0) + loadTime) / 20D));
            }
        });
    }

    public void delete() {
        HandlerList.unregisterAll(this);
        timers.remove(region, this);
        bars.forEach((player, bossBar) -> bossBar.removeAll());
        bars.clear();
        task.cancel();
    }

    private enum Stage {
        WAITING,
        COUNTING,
        ACTIVATED,
        IGNITION,
        END
    }
}
