/*
 *   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.bausystem.features.simulator;

import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.storage.SimulatorSaver;
import de.steamwar.bausystem.shared.Pair;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.linkage.Linked;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.util.Vector;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@UtilityClass
public class SimulatorWatcher {

    private final World WORLD = Bukkit.getWorlds().get(0);
    private Map<Simulator, REntityServer> entityServers = new HashMap<>();
    private Map<Player, Pair<Simulator, Runnable>> watchers = new HashMap<>();

    public void watch(Player player, Simulator simulator, Runnable watcher) {
        if (simulator == null || watcher == null) {
            watchers.remove(player);
        } else {
            watchers.put(player, new Pair<>(simulator, watcher));
        }
    }

    public synchronized void update(Simulator simulator) {
        REntityServer rEntityServer = entityServers.get(simulator);
        if (rEntityServer != null) {
            rEntityServer.getEntities().forEach(REntity::die);
            createSim(rEntityServer, simulator);
        }

        new ArrayList<>(watchers.values()).forEach(simulatorRunnablePair -> {
            if (simulatorRunnablePair.getKey() == simulator) {
                simulatorRunnablePair.getValue().run();
            }
        });
        SimulatorSaver.saveSimulator(SimulatorStorage.simulatorsDir, simulator);
    }

    @Linked
    public static class QuitListener implements Listener {

        @EventHandler
        public void onPlayerQuit(PlayerQuitEvent event) {
            watchers.remove(event.getPlayer());
            show(null, event.getPlayer());
        }
    }

    private REntityServer createSim(REntityServer server, Simulator simulator) {
        if (simulator == null) {
            return null;
        }
        Map<Vector, Set<Class<?>>> positionCache = new HashMap<>();
        simulator.getGroups().stream().map(group -> group.getElements().stream().map(element -> new Pair<>(group, element)).collect(Collectors.toList())).flatMap(List::stream).forEach(pair -> {
            SimulatorGroup group = pair.getKey();
            SimulatorElement<?> element = pair.getValue();

            boolean wasNotPresent = positionCache.computeIfAbsent(element.getPosition(), __ -> new HashSet<>())
                    .add(element.getClass());
            if (!wasNotPresent) return;
            Material material = group.isDisabled() || element.isDisabled() ? element.getWorldDisabledMaterial() : element.getWorldMaterial();
            Location location = element.getWorldPos().toLocation(WORLD);
            RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(server, location, material);
            rFallingBlockEntity.setNoGravity(true);
        });
        return server;
    }

    public synchronized boolean show(Simulator sim, Player player) {
        AtomicBoolean removed = new AtomicBoolean();
        entityServers.forEach((simulator, rEntityServer) -> {
            if (rEntityServer == null) return;
            if (rEntityServer.getPlayers().contains(player) && sim != simulator) {
                rEntityServer.removePlayer(player);
                removed.set(true);
            }
        });
        if (sim == null) return removed.get();
        entityServers.computeIfAbsent(sim, __ -> createSim(new REntityServer(), sim)).addPlayer(player);
        return removed.get();
    }

    synchronized List<REntity> getEntitiesOfSimulator(Simulator simulator) {
        REntityServer entityServer = entityServers.get(simulator);
        if (entityServer == null) {
            return Collections.emptyList();
        }
        return entityServer.getEntities();
    }

    synchronized REntityServer getEntityServerOfSimulator(Simulator simulator) {
        if (simulator == null) {
            return null;
        }
        return entityServers.computeIfAbsent(simulator, __ -> createSim(new REntityServer(), simulator));
    }
}
