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

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.tpslimit.TPSUtils;
import de.steamwar.bausystem.features.tracer.TraceRecorder;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.TickEndEvent;
import de.steamwar.bausystem.utils.TickStartEvent;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

@Linked
@MinVersion(19)
public class SimulatorExecutor implements Listener {

    private static final World WORLD = Bukkit.getWorlds().get(0);
    private static Set<Simulator> currentlyRunning = new HashSet<>();
    private static Map<Long, Map<Integer, List<SimulatorAction>>> tickStartActions = new HashMap<>();
    private static Map<Long, List<SimulatorAction>> tickEndActions = new HashMap<>();

    public static boolean run(Simulator simulator) {
        if (currentlyRunning.contains(simulator)) return false;
        currentlyRunning.add(simulator);

        long currentTick = TPSUtils.currentRealTick.get();
        AtomicLong lastTick = new AtomicLong();
        simulator.toSimulatorActions((tickOffset, simulatorAction) -> {
            lastTick.set(Math.max(lastTick.get(), tickOffset));
            tickStartActions.computeIfAbsent(currentTick + tickOffset, __ -> new HashMap<>())
                    .computeIfAbsent(simulatorAction.getOrder(), __ -> new ArrayList<>())
                    .add(simulatorAction);
        }, (tickOffset, simulatorAction) -> {
            lastTick.set(Math.max(lastTick.get(), tickOffset));
            tickEndActions.computeIfAbsent(currentTick + tickOffset, __ -> new ArrayList<>())
                    .add(simulatorAction);
        });

        tickEndActions.computeIfAbsent(currentTick + lastTick.get() + 4, __ -> new ArrayList<>())
                .add(new SimulatorAction(0, 1) {
                    @Override
                    public void accept(World world) {
                        currentlyRunning.remove(simulator);

                        if (simulator.isAutoTrace()) {
                            simulator.getGroups()
                                    .stream()
                                    .map(SimulatorGroup::getElements)
                                    .flatMap(List::stream)
                                    .map(SimulatorElement::getPosition)
                                    .map(pos -> pos.toLocation(WORLD))
                                    .map(Region::getRegion)
                                    .distinct()
                                    .forEach(region -> {
                                        TraceRecorder.instance.stopRecording(region);
                                    });
                        }
                    }
                });

        if (simulator.isAutoTrace()) {
            simulator.getGroups()
                    .stream()
                    .map(SimulatorGroup::getElements)
                    .flatMap(List::stream)
                    .map(SimulatorElement::getPosition)
                    .map(pos -> pos.toLocation(WORLD))
                    .map(Region::getRegion)
                    .distinct()
                    .forEach(region -> {
                        TraceRecorder.instance.startRecording(region);
                    });
        }
        return true;
    }

    @EventHandler
    public void onTickStart(TickStartEvent event) {
        long currentTick = TPSUtils.currentRealTick.get();
        Map<Integer, List<SimulatorAction>> actionsToRun = tickStartActions.remove(currentTick);
        if (actionsToRun == null) return;

        List<Integer> keys = new ArrayList<>(actionsToRun.keySet());
        keys.sort(null);
        for (int actionKey : keys) {
            runActions(actionsToRun.get(actionKey));
        }
    }

    @EventHandler
    public void onTickEnd(TickEndEvent event) {
        long currentTick = TPSUtils.currentRealTick.get() - 1;
        List<SimulatorAction> actionsToRun = tickEndActions.remove(currentTick);
        if (actionsToRun == null) return;
        runActions(actionsToRun);
    }

    private void runActions(List<SimulatorAction> actionsToRun) {
        while (!actionsToRun.isEmpty()) {
            Collections.shuffle(actionsToRun);
            for (int i = actionsToRun.size() - 1 ; i >= 0; i--) {
                SimulatorAction action = actionsToRun.get(i);
                action.accept(WORLD);
                action.setCount(action.getCount() - 1);
                if (action.getCount() == 0) {
                    actionsToRun.remove(i);
                }
            }
        }
    }
}
