#include "repository_graph.h"

#include <iostream>
#include <random>
#include <stdexcept>

namespace
{
void Require(bool condition, const char* message)
{
    if (!condition) throw std::runtime_error(message);
}

void RunDeterministicTests()
{
    ReposGraph graph;
    VectorTime empty;
    Require(graph.CheckIn({0, 0}, empty) == ReposError::Ok, "first root failed");
    Require(graph.CheckIn({1, 0}, empty) == ReposError::Ok, "second root failed");
    Require(graph.GetRootCount() == 2, "concurrent operations were not roots");

    VectorTime both;
    both.Add(0, 1); both.Add(1, 1);
    Require(graph.CheckIn({0, 1}, both) == ReposError::Ok, "merged check-in failed");
    Require(graph.GetNodeCount() == 3, "wrong node count");
    Require(graph.GetEdgeCount() == 2, "wrong edge count");
    Require(graph.HasDirectEdge({0, 0}, {0, 1}), "first predecessor edge missing");
    Require(graph.HasDirectEdge({1, 0}, {0, 1}), "second predecessor edge missing");

    VectorTime next = both;
    next.Add(0, 2);
    Require(graph.CheckIn({1, 1}, next) == ReposError::Ok, "causal successor failed");
    Require(graph.HasDirectEdge({0, 1}, {1, 1}), "direct predecessor edge missing");
    Require(!graph.HasDirectEdge({0, 0}, {1, 1}), "transitively redundant edge stored");

    VectorTime invalid;
    invalid.Add(0, 2);
    Require(graph.ValidateVectorTime(invalid) == ReposError::VectorTimeViolatesCausality,
            "causality violation was accepted");

    Require(graph.CheckIn({0, 0}, empty) == ReposError::OperationAlreadyPresent,
            "duplicate operation was accepted");
    VectorTime missing;
    missing.Add(2, 1);
    Require(graph.CheckIn({2, 1}, missing) == ReposError::OperationMissing,
            "missing operation was accepted");
    VectorTime unavailable = graph.GetVectorTime();
    unavailable.Add(3, 1);
    Require(graph.CheckIn({2, 0}, unavailable) == ReposError::VectorTimeNotASubset,
            "unavailable execution context was accepted");
}

void RunDifferentialTests()
{
    std::mt19937 random(182);
    constexpr int numSites = 3;
    for (int run = 0; run < 10000; ++run)
    {
        ReposGraph graph;
        ReferenceReposGraph reference;
        for (int event = 0; event < 2000; ++event)
        {
            const int s = static_cast<int>(random() % numSites);
            const VectorTime& vr = graph.GetVectorTime();
            VectorTime v;
            for (int site = 0; site < numSites; ++site)
            {
                const int upper = vr(site) + 1;
                v.Add(site, static_cast<int>(random() % static_cast<unsigned>(upper + 1)));
            }
            const OpId opId{s, v(s)};
            const ReposError actual = graph.CheckIn(opId, v);
            const ReposError expected = reference.CheckIn(opId, v);
            Require(actual == expected, "graph and reference model disagree");
            Require(graph.GetVectorTime() == reference.GetVectorTime(), "repository vector times disagree");
        }
    }
}
}

int main()
{
    try
    {
        RunDeterministicTests();
        RunDifferentialTests();
        std::cout << "All deterministic tests and 10,000 check-in-only differential simulations "
                     "of 2,000 attempts passed.\n";
        return 0;
    }
    catch (const std::exception& error)
    {
        std::cerr << "Test failed: " << error.what() << '\n';
        return 1;
    }
}
