//
// Created by Dan Crosby on 3/29/21.
//

#include <memory>
#include <numeric>
#include <random>
#include <thread>

#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/UnitConversions.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
#include "src/main/cpp/net/eagle0/shardok/server/ShardokGamesManager.hpp"
#include "src/main/protobuf/net/eagle0/common/random_units.pb.h"

using net::eagle0::common::RandomUnits;
using std::shared_ptr;
using std::vector;

constexpr int THREAD_COUNT = 16;

#define FIXED_SEED true

#if FIXED_SEED
std::mt19937 g;
#else
std::random_device rd;
std::mt19937 g(rd());
#endif

vector<string> fileNames{};

using namespace shardok;

ShardokGamesManager* manager;

auto randInt(const int lowInc, const int hiInc) {
    return std::uniform_int_distribution(lowInc, hiInc)(g);
}

void runBattle(const vector<shared_ptr<const UnitProto>>& units) {
    auto mapName = fileNames[randInt(0, (int)fileNames.size() - 1)];
    auto month = randInt(1, 12);

    auto p1UnitCount = randInt(1, 20);
    vector<UnitProto> p1Units{};
    for (int i = 0; i < p1UnitCount; i++) {
        auto unit = *units[i];
        unit.set_player_id(0);
        p1Units.push_back(unit);
    }

    auto p2UnitCount = randInt(1, 20);
    vector<UnitProto> p2Units{};
    for (int i = p1UnitCount; i < p1UnitCount + p2UnitCount; i++) {
        auto unit = *units[i];
        unit.set_player_id(1);
        p2Units.push_back(unit);
    }

    printf("map name is %s, %d attacking %d\n", mapName.c_str(), p1UnitCount, p2UnitCount);

    vector<VictoryConditionProto> attVictoryConditions{
            VictoryConditionProto::VICTORY_CONDITION_HOLDS_CRITICAL_TILES,
            VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING};
    vector<VictoryConditionProto> defVictoryConditions{
            VictoryConditionProto::VICTORY_CONDITION_LAST_PLAYER_STANDING,
            VictoryConditionProto::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS};

    auto p1 = PlayerInfoWithUnits(0, true, 5, false, 10000, attVictoryConditions, {}, p1Units);
    auto p2 = PlayerInfoWithUnits(1, false, 7, false, 10000, defVictoryConditions, {}, p2Units);

    string gameId = std::to_string(randInt(INT_MIN, INT_MAX));

    manager->LockedCreateSpecifiedGame(gameId, {p1, p2}, mapName, "", month, "");
}

auto LoadUnits() -> vector<shared_ptr<const UnitProto>> {
    const auto bytes = byte_vector::FromPath("/tmp/random_heroes.bin");

    RandomUnits commonUnits;
    commonUnits.ParseFromArray(bytes.data(), (int)bytes.size());
    printf("Loaded %d units\n", commonUnits.units_size());

    vector<shared_ptr<const UnitProto>> units{};
    units.reserve(commonUnits.units_size());

    for (const auto& commonUnit : commonUnits.units()) {
        // FIXME: assumes 2 players
        units.push_back(std::make_shared<const UnitProto>(ConvertUnit(commonUnit, 0, {0, 1})));
    }
    std::shuffle(units.begin(), units.end(), g);

    return units;
}

[[noreturn]] void runBattlesThread(const vector<shared_ptr<const UnitProto>>& units) {
    while (true) { runBattle(units); }
}

auto main(int argc, char** argv) -> int {
#if FIXED_SEED
    std::srand(4564564);
    g.seed(378473);
#else
    std::srand(std::time(nullptr));
#endif

    FilesystemUtils::SetExecPath(argv[0]);

    auto randomUnits = LoadUnits();

    printf("Map file directory is %s\n", FilesystemUtils::MapFilesDirectory().c_str());
    fileNames = FilesystemUtils::FilesInDirectory(FilesystemUtils::MapFilesDirectory(), ".e0m");

    for (auto& fileName : fileNames) { fileName.resize(fileName.size() - 4); }

    manager = new ShardokGamesManager(nullptr, std::vector<std::string>());

    std::thread threads[THREAD_COUNT];

    for (int i = 0; i < THREAD_COUNT; i++) {
        threads[i] = std::thread(runBattlesThread, randomUnits);
    }

    for (auto& thread : threads) { thread.join(); }
}
