#include "ReplayRecorder.h"

#include <iomanip>
#include <sstream>
#include <utility>

#include <QByteArray>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QString>
#include <QStringList>

#include "Command.h"
#include "CommandSerializer.h"
#include "StateChecksum.h"

namespace
{
constexpr const char* kReplayFormatVersion = "1";

// Build fingerprint: even a new local build can desync old replays (float
// reasons), so the header carries a per-build tag to warn on mismatch.
const std::string kBuildTag = std::string(__DATE__) + " " + __TIME__;

std::string toHex(std::uint64_t value)
{
    std::ostringstream out;
    out << std::hex << std::setw(16) << std::setfill('0') << value;
    return out.str();
}
}  // namespace

ReplayRecorder::ReplayRecorder(std::string configDir, std::string outputDir)
    : m_configDir(std::move(configDir))
    , m_outputDir(std::move(outputDir))
{
}

ReplayRecorder::~ReplayRecorder()
{
    close();
}

std::string computeReplayConfigHash(const std::string& configDir)
{
    Hasher hasher;
    QDir dir(QString::fromStdString(configDir));
    const QStringList files =
        dir.entryList(QStringList() << "*.toml", QDir::Files, QDir::Name);
    for (const QString& name : files)
    {
        hasher.append(name.toStdString());
        QFile file(dir.filePath(name));
        if (file.open(QIODevice::ReadOnly))
        {
            const QByteArray bytes = file.readAll();
            hasher.appendBytes(bytes.constData(), static_cast<std::size_t>(bytes.size()));
        }
    }
    return toHex(hasher.getValue());
}

void ReplayRecorder::startNewRun(unsigned int seed, std::uint64_t initialRngFingerprint)
{
    close();

    QDir().mkpath(QString::fromStdString(m_outputDir));
    const QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
    const QString fileName  = timestamp + "_" + QString::number(seed) + ".replay";
    m_filePath = QDir(QString::fromStdString(m_outputDir)).filePath(fileName).toStdString();

    m_stream.open(m_filePath, std::ios::out | std::ios::trunc);
    if (!m_stream.is_open())
    {
        return;
    }

    m_stream << "# dota_factory replay\n";
    m_stream << "version "     << kReplayFormatVersion << "\n";
    m_stream << "build "       << kBuildTag << "\n";
    m_stream << "seed "        << seed << "\n";
    m_stream << "config_hash " << computeReplayConfigHash(m_configDir) << "\n";
    m_stream << "timestamp "
             << QDateTime::currentDateTime().toString(Qt::ISODate).toStdString() << "\n";
    m_stream << "---\n";
    m_stream << "# checksum 0 " << toHex(initialRngFingerprint) << "\n";
    m_stream.flush();
}

void ReplayRecorder::recordCommand(Tick tick, const Command& command,
                                   std::uint64_t rngFingerprint)
{
    if (!m_stream.is_open())
    {
        return;
    }
    m_stream << tick << ' ' << serializeCommand(command) << "\n";
    m_stream << "# checksum " << tick << ' ' << toHex(rngFingerprint) << "\n";
    m_stream.flush();
}

void ReplayRecorder::recordChecksum(Tick tick, std::uint64_t rngFingerprint)
{
    if (!m_stream.is_open())
    {
        return;
    }
    m_stream << "# checksum " << tick << ' ' << toHex(rngFingerprint) << "\n";
    m_stream.flush();
}

void ReplayRecorder::close()
{
    if (m_stream.is_open())
    {
        m_stream.flush();
        m_stream.close();
    }
}

bool ReplayRecorder::isOpen() const
{
    return m_stream.is_open();
}

const std::string& ReplayRecorder::getCurrentFilePath() const
{
    return m_filePath;
}
