//
// Created by Dan Crosby on 6/24/20.
//

#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/TsvParser.hpp"
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/GameStateHelpers.hpp"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"
#include "src/main/protobuf/net/eagle0/shardok/storage/game.pb.h"

using net::eagle0::shardok::storage::Game;
using Unit = net::eagle0::shardok::storage::fb::Unit;

void PrintChangedUnits(const net::eagle0::shardok::storage::ActionResult& result);

auto MakeEngine(const net::eagle0::shardok::storage::Game& game, int includeResultCount)
        -> std::shared_ptr<shardok::ShardokEngine>;

auto main(int argc, char** argv) -> int {
    if (argc < 2) {
        printf("Please supply a path.");
        return 1;
    }

    const auto* path = argv[1];
    FilesystemUtils::SetExecPath(argv[0]);

    printf("Using path %s\n", path);

    const auto protoBytes = byte_vector::FromPath(path);
    Game game{};
    game.ParseFromArray(protoBytes.data(), protoBytes.size());

    auto engine = MakeEngine(game, 200);
    auto result = game.action_result(201).action_result();

    auto availableCommands = engine->GetAvailableCommandProtos(0, true);
    for (size_t i = 0; i < availableCommands.size(); i++) {
        auto cmd = availableCommands[i];
        if (result.has_actor() == cmd.has_actor() &&
            result.actor().value() == cmd.actor().value() && result.has_target_coords() &&
            result.target_coords() == cmd.target()) {
            printf("Command: %s\n", cmd.DebugString().c_str());
            engine->PostCommand(0, i);
            break;
        }
    }

    availableCommands = engine->GetAvailableCommandProtos(0, true);
    for (size_t i = 0; i < availableCommands.size(); i++) {
        auto cmd = availableCommands[i];
        if (cmd.type() == net::eagle0::shardok::common::END_TURN_COMMAND) {
            printf("Command: %s\n", cmd.DebugString().c_str());
            engine->PostCommand(0, i);
            break;
        }
    }

    availableCommands = engine->GetAvailableCommandProtos(1, true);
    for (size_t i = 0; i < availableCommands.size(); i++) {
        auto cmd = availableCommands[i];
        if (cmd.type() == net::eagle0::shardok::common::END_TURN_COMMAND) {
            printf("Command: %s\n", cmd.DebugString().c_str());
            engine->PostCommand(1, i);
            break;
        }
    }

    result = game.action_result(218).action_result();
    availableCommands = engine->GetAvailableCommandProtos(0, true);
    for (size_t i = 0; i < availableCommands.size(); i++) {
        auto cmd = availableCommands[i];
        if (result.has_actor() == cmd.has_actor() &&
            result.actor().value() == cmd.actor().value() && result.has_target_coords() &&
            result.target_coords() == cmd.target()) {
            printf("Command: %s\n", cmd.DebugString().c_str());
            engine->PostCommand(0, i);
            break;
        }
    }

    //    for (int i=180; i<219; i++) {
    //        const auto& result = game.action_result(i).action_result();
    //      //  if (result.type() == net::eagle0::shardok::common::REDUCE || result.type() ==
    //      net::eagle0::shardok::common::METEOR_UNIT_DAMAGE) {
    //            printf("%d  ", i);
    //            PrintChangedUnits(result);
    //            printf("id 0 after size: %d\n\n",
    //            game.action_result(i).state_after().units().at(0).battalion().size());
    //    //    }
    //    }

    //    for (const auto& kv : firstState.units()) {
    //        printf("id: %d  name: %s\n", kv.second.attached_hero().eagle_hero_id(),
    //        kv.second.attached_hero().name().c_str());
    //    }
    printf("Finished");

    return 0;
}

void PrintChangedUnits(const net::eagle0::shardok::storage::ActionResult& result) {
    printf("%s\n", net::eagle0::shardok::common::ActionType_Name(result.type()).c_str());
    for (const string& changedUnitData : result.changed_units_fb()) {
        const auto& changedUnit = *((Unit*)changedUnitData.data());
        printf("id: %d  name: %s  sizeAfter: %d\n",
               changedUnit.unit_id(),
               changedUnit.has_attached_hero() ? changedUnit.attached_hero().name().c_str() : "N/A",
               changedUnit.battalion().size());
    }
    printf("\n");
}

auto MakeEngine(const net::eagle0::shardok::storage::Game& game, int includeResultCount)
        -> std::shared_ptr<shardok::ShardokEngine> {
    auto settings = std::make_shared<shardok::GameSettings>();

    TsvParser parser;
    const string settingsPath = "src/main/resources/net/eagle0/shardok/settings.tsv";
    printf("Settings path is %s\n", settingsPath.c_str());
    string settingsTsv = string(byte_vector::FromPath(settingsPath));
    auto setter = shardok::GameSettings::Setter(*settings);

    auto valuesAndTypes = parser.ParseColumnEntryTsv(settingsTsv);
    setter.SetFromTypesAndValues(valuesAndTypes[1], valuesAndTypes[0]);

    const string battalionTypesPath = "src/main/resources/net/eagle0/shardok/battalionTypes.tsv";
    string tsv = string(byte_vector::FromPath(battalionTypesPath));
    auto battalionMaps = parser.ParseColumnEntryTsv(tsv);
    for (const std::unordered_map<string, string>& map : battalionMaps) {
        setter.RegisterBattalionType(shardok::BattalionType::NewBattalionTypeFromMap(map));
    }

    return std::make_shared<shardok::ShardokEngine>(
            settings,
            shardok::fb::GameStateW::FromByteString(
                    game.action_result(includeResultCount).state_after_fb()));
}