#include "CommandManager.h"

#include <utility>

#include "Command.h"
#include "ReplayRecorder.h"
#include "Simulation.h"
#include "Tick.h"

namespace
{
// Periodic RNG checksum cadence (see docs/replay_design.md "Cadence").
constexpr Tick kChecksumIntervalTicks = 30;
}  // namespace

CommandManager::CommandManager(Simulation& simulation)
    : m_simulation(simulation)
{
}

CommandManager::~CommandManager() = default;

void CommandManager::enqueue(std::shared_ptr<const Command> command)
{
    if (m_replayMode)
    {
        return;  // playback is driven by the recorded stream; ignore live input
    }
    if (command)
    {
        m_queue.push_back(std::move(command));
    }
}

void CommandManager::drain()
{
    // Apply in FIFO order. A queued ResetCommand reinitializes the simulation in
    // place; the reference stays valid and any commands after it apply to the
    // fresh state.
    for (const std::shared_ptr<const Command>& command : m_queue)
    {
        if (command->kind == CommandKind::Reset)
        {
            m_simulation.apply(*command);
            if (m_recorder)
            {
                // Restart is a file boundary: a fresh file with the new seed.
                m_recorder->startNewRun(m_simulation.getSeed(),
                                        m_simulation.getRngFingerprint());
            }
        }
        else
        {
            // Commands drain before the tick batch, so currentTick is the count of
            // completed ticks the command is pinned to.
            const Tick tick = m_simulation.getCurrentTick();
            m_simulation.apply(*command);
            if (m_recorder)
            {
                m_recorder->recordCommand(tick, *command, m_simulation.getRngFingerprint());
            }
        }
    }
    m_queue.clear();
}

bool CommandManager::hasPending() const
{
    return !m_queue.empty();
}

void CommandManager::setRecorder(std::unique_ptr<ReplayRecorder> recorder)
{
    m_recorder = std::move(recorder);
    if (m_recorder)
    {
        m_recorder->startNewRun(m_simulation.getSeed(), m_simulation.getRngFingerprint());
    }
}

void CommandManager::setReplayMode(bool replayMode)
{
    m_replayMode = replayMode;
}

void CommandManager::recordTickCheckpoint()
{
    if (m_recorder && (m_simulation.getCurrentTick() % kChecksumIntervalTicks == 0))
    {
        m_recorder->recordChecksum(m_simulation.getCurrentTick(), m_simulation.getRngFingerprint());
    }
}
