package com.livingworld.core.simulation;

import com.livingworld.config.SimulationConfig;
import com.livingworld.debug.LivingWorldLogger;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

/**
 * Scheduler that drives the simulation tick cycle.
 * 
 * <p>This class integrates Minecraft game ticks with living world simulation ticks,
 * manages a priority queue of update jobs, and enforces per-cycle budget limits.</p>
 */
public final class SimulationScheduler {

    private final SimulationConfig config;
    private long minecraftTickCounter;
    private long simulationTickCounter;
    private final PriorityQueue<RegionUpdateJob> updateQueue;
    public SimulationScheduler(SimulationConfig config) {
        if (config == null) {
            throw new IllegalArgumentException("config must not be null");
        }
        this.config = config;
        this.minecraftTickCounter = 0L;
        this.simulationTickCounter = 0L;
        this.updateQueue = new PriorityQueue<>(RegionUpdateJob.BY_PRIORITY_DESC);
    }

    public void onMinecraftTick() {
        this.minecraftTickCounter++;
    }

    void recordCompletedSimulationCycle() {
        this.simulationTickCounter++;
    }

    public boolean shouldRunSimulationCycle() {
        return minecraftTickCounter > 0
                && minecraftTickCounter % config.getSimulationIntervalTicks() == 0;
    }

    public void queueRegion(RegionUpdateJob job) {
        if (job == null) {
            throw new IllegalArgumentException("job must not be null");
        }
        this.updateQueue.add(job);
    }

    /**
     * Polls jobs for processing during the current simulation cycle.
     * 
     * @return list of polled jobs, may be empty if no jobs are queued or budget exceeded
     */
    public List<RegionUpdateJob> pollJobsForCycle() {
        final int maxCount = config.getMaxRegionsPerCycle();
        final ArrayList<RegionUpdateJob> result = new ArrayList<>(maxCount);

        for (int i = 0; i < maxCount && !this.updateQueue.isEmpty(); i++) {
            RegionUpdateJob job = this.updateQueue.poll();
            if (job != null) {
                result.add(job);
            } else {
                break;
            }
        }

        return result;
    }

    public void endCycle(long durationMs) {
        if (durationMs > config.getMaxMillisecondsPerCycle()) {
            LivingWorldLogger.warn(String.format(
                    "Simulation cycle exceeded time budget: %dms elapsed vs %dms allowed",
                    durationMs, config.getMaxMillisecondsPerCycle()));
        }
        final int emergencyMs = config.getEmergencyStopMilliseconds();
        if (durationMs > emergencyMs) {
            LivingWorldLogger.warn(String.format("EMERGENCY STOP: Cycle duration %dms exceeded emergency threshold %dms",
                    durationMs, emergencyMs));
        }
    }

    public long getMinecraftTickCounter() {
        return this.minecraftTickCounter;
    }

    public long getSimulationTickCounter() {
        return this.simulationTickCounter;
    }

    int getSimulationIntervalTicks() {
        return config.getSimulationIntervalTicks();
    }

    int getMaxRegionsPerCycle() {
        return config.getMaxRegionsPerCycle();
    }

    public int getMaxMillisecondsPerCycle() {
        return config.getMaxMillisecondsPerCycle();
    }

    public int getEmergencyStopMilliseconds() {
        return config.getEmergencyStopMilliseconds();
    }

    public void clearQueue() {
        this.updateQueue.clear();
    }

    public int getQueuedJobCount() {
        return this.updateQueue.size();
    }

    @Override
    public String toString() {
        return "SimulationScheduler{"
                + "minecraftTickCounter=" + minecraftTickCounter
                + ", simulationTickCounter=" + simulationTickCounter
                + ", queuedJobs=" + updateQueue.size()
                + '}';
    }

}
