#pragma once
// Remember not to use "Broodwar" in any global class constructor!

# include "Source\CUNYAIModule.h"
# include "Source\LearningManager.h"
# include "Source\Diagnostics.h"
# include "Source\Build.h"
# include "Source\UnitInventory.h"
# include <fstream>
# include <BWAPI.h>
# include <cstdlib>
# include <cstring>
# include <ctime>
# include <string>
# include <algorithm>
# include <set>
# include <random> // C++ base random is low quality.
# include <thread>
# include <filesystem>
# include <cmath>


using namespace BWAPI;
using namespace Filter;
using namespace std;


void LearningManager::dumpStatus()
{
    CUNYAIModule::friendly_player_model.units_.printUnitInventory(Broodwar->self(), Broodwar->mapFileName());
    CUNYAIModule::friendly_player_model.casualties_.printUnitInventory(Broodwar->self(), Broodwar->mapFileName() + "casualties");
    CUNYAIModule::enemy_player_model.casualties_.printUnitInventory(Broodwar->self(), Broodwar->mapFileName() + "kills");

}

void LearningManager::onStart()
{
    // File extension including our race initial;
    myRaceChar_ = CUNYAIModule::safeString(Broodwar->self()->getRace().c_str());
    enemyRaceChar_ = CUNYAIModule::safeString(Broodwar->enemy()->getRace().c_str());
    versionChar_ = "2022.6.24";
    noStats_ = " 0 0 ";
    learningExtension_ = myRaceChar_ + "v" + enemyRaceChar_ + " " + Broodwar->enemy()->getName() + " " + versionChar_ + ".csv";
    gameInfoExtension_ = myRaceChar_ + "v" + enemyRaceChar_ + " " + Broodwar->enemy()->getName() + " " + versionChar_ + " Info.csv";

    definePremadeBuildOrders();
    Diagnostics::DiagnosticText("Build Orders Defined");
    selectDefaultBuild();
    Diagnostics::DiagnosticText("Selected Build");
    parseLearningFile();
    Diagnostics::DiagnosticText("Parsed LearningFile, size = %d", gameHistory_.size());
    selectBestBuild();
    Diagnostics::DiagnosticText("Trying Build Order: %s", getBuildNameFromEnum(currentBuild_.getBuildEnum()).c_str() );
}

void LearningManager::onEnd(bool isWinner)
{
    ifstream readFile(getReadDir() + gameInfoExtension_);
    if (readFile)
        copyFile(getReadDir() + gameInfoExtension_, getWriteDir() + gameInfoExtension_);
    ofstream gameLog(getWriteDir() + gameInfoExtension_, std::ios_base::app);
    //gameLog << std::setfill('0') << Strategy::getEnemyBuildTime().minutes << ":" << std::setw(2) << Strategy::getEnemyBuildTime().seconds << ",";

    // Print all relevant model and game characteristics.
    gameLog << getBuildName() << ','
        << CUNYAIModule::safeString(Broodwar->mapFileName().c_str()) << ','
        << isWinner << ','
        << CUNYAIModule::short_delay << ','
        << CUNYAIModule::med_delay << ','
        << CUNYAIModule::long_delay << ','
        << Broodwar->self()->getBuildingScore() << ','
        << Broodwar->self()->getKillScore() << ','
        << Broodwar->self()->getRazingScore() << ','
        << Broodwar->self()->getUnitScore() << ','
        << CUNYAIModule::enemy_player_model.getFirstAirSeen() << ','
        << CUNYAIModule::enemy_player_model.getFirstDetectorSeen() << ','
        << Broodwar->elapsedTime() << ','
        << CUNYAIModule::rushManager.getRushDetected() << ','
        << endl;

    //If your Build Order didn't work and the bot is broken, let's write that specifically.
    if (!currentBuild_.isEmptyBuildOrder()) {
        ofstream output; // Prints to brood war file while in the WRITE file.
        output.open(getWriteDir() + getBuildName() + "BuildOrderFailures.txt", ios_base::app);
        string print_value = "";

        print_value += currentBuild_.getNext().getResearch().c_str();
        print_value += currentBuild_.getNext().getUnit().c_str();
        print_value += currentBuild_.getNext().getUpgrade().c_str();

        output << "Couldn't build: " << print_value << endl;
        output << "Hatches Left?:" << CUNYAIModule::baseManager.getBaseCount() << endl;
        output << "Win:" << isWinner << endl;
        output.close();
    };

    // If training in some enviorments, I need to manually move the read to write to mimic tournament play.
    if constexpr (MOVE_OUTPUT_BACK_TO_READ) {
        ifstream readFile(getWriteDir() + gameInfoExtension_);
        if (readFile)
            copyFile(getWriteDir() + gameInfoExtension_, getReadDir() + gameInfoExtension_);
    }

    //dumpStatus();
}

void LearningManager::definePremadeBuildOrders()
{
    // 12H muta build order!
    vector<BuildOrderElement> MutaList = { BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Overlord),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Hatchery),
       BuildOrderElement(UnitTypes::Zerg_Spawning_Pool),
       BuildOrderElement(UnitTypes::Zerg_Extractor),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Zergling),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Lair),
       BuildOrderElement(UnitTypes::Zerg_Overlord),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UpgradeTypes::Metabolic_Boost),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Spire),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Extractor),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Creep_Colony),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Creep_Colony),
       BuildOrderElement(UnitTypes::Zerg_Drone),
       BuildOrderElement(UnitTypes::Zerg_Sunken_Colony),
       BuildOrderElement(UnitTypes::Zerg_Sunken_Colony),
       BuildOrderElement(UnitTypes::Zerg_Overlord),
       BuildOrderElement(UnitTypes::Zerg_Overlord),
       BuildOrderElement(UnitTypes::Zerg_Overlord),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk),
       BuildOrderElement(UnitTypes::Zerg_Mutalisk)
    };

    double mutaParams[6] = { 0.517767817, 1.238421617 , 0.48223217, 0.439303835, 0.717060969, 0.373843463 };

    // 12pool Lurker
    vector<BuildOrderElement> lurkerList = { BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Overlord),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Spawning_Pool),
        BuildOrderElement(UnitTypes::Zerg_Extractor),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Hatchery),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Lair),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UpgradeTypes::Metabolic_Boost),
        BuildOrderElement(UnitTypes::Zerg_Hydralisk_Den),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(TechTypes::Lurker_Aspect),
        BuildOrderElement(UnitTypes::Zerg_Overlord),
        BuildOrderElement(UnitTypes::Zerg_Hydralisk),
        BuildOrderElement(UnitTypes::Zerg_Hydralisk),
        BuildOrderElement(UnitTypes::Zerg_Hydralisk),
        BuildOrderElement(UnitTypes::Zerg_Hydralisk),
        BuildOrderElement(UnitTypes::Zerg_Extractor),
        BuildOrderElement(UnitTypes::Zerg_Lurker),
        BuildOrderElement(UnitTypes::Zerg_Lurker),
        BuildOrderElement(UnitTypes::Zerg_Lurker),
        BuildOrderElement(UnitTypes::Zerg_Lurker)
    };
    double lurkerParams[6] = { 0.460984384, 1.217642114, 0.539015569, 0.543615491, 0.73221256, 0.432943104 };

    // 5pool ling
    vector<BuildOrderElement> fivePoolList = { BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Spawning_Pool),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Overlord),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Hatchery),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling)
    };

    double fivePoolParams[6] = { 0.464593318, 1.145650349, 0.535406679, 0.409437865, 0.669303145, 0.428709651 };


    // 7 pool https://liquipedia.net/starcraft/7_Pool_(vs._Terran)
    vector<BuildOrderElement> sevenPoolList = { BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Spawning_Pool),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Overlord),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Zergling),
        BuildOrderElement(UnitTypes::Zerg_Hatchery),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Drone),
        BuildOrderElement(UnitTypes::Zerg_Extractor)
    };

    double sevenPoolParams[6] = { 0.464593318, 1.145650349, 0.535406679, 0.409437865, 0.669303145, 0.428709651 };

   // // 3 Hatch Muta: https://liquipedia.net/starcraft/3_Hatch_Muta_(vs._Terran) In progress.
   // vector<BuildOrderElement> threeHatchMutaList = { BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Overlord),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Hatchery),
   //    BuildOrderElement(UnitTypes::Zerg_Spawning_Pool),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Hatchery),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Drone),
   //    BuildOrderElement(UnitTypes::Zerg_Extractor)
   //}
   // double threeHatchMutaParams[6] = { 0.517767817, 1.238421617 , 0.48223217, 0.439303835, 0.717060969, 0.373843463 };

   // 9 Pool Spire
    vector<BuildOrderElement> OneBaseSpireList = { BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Spawning_Pool),
    BuildOrderElement(UnitTypes::Zerg_Creep_Colony),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Extractor),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Sunken_Colony),
    BuildOrderElement(UnitTypes::Zerg_Overlord),
    BuildOrderElement(UnitTypes::Zerg_Zergling),
    BuildOrderElement(UnitTypes::Zerg_Zergling),
    BuildOrderElement(UnitTypes::Zerg_Zergling), //Each ling is "double counted" in BASIL
    BuildOrderElement(UnitTypes::Zerg_Lair),
    BuildOrderElement(UnitTypes::Zerg_Zergling),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Drone),
    BuildOrderElement(UnitTypes::Zerg_Spire),
    BuildOrderElement(UnitTypes::Zerg_Overlord),
    BuildOrderElement(UnitTypes::Zerg_Mutalisk),
    BuildOrderElement(UnitTypes::Zerg_Mutalisk),
    BuildOrderElement(UnitTypes::Zerg_Mutalisk),
    BuildOrderElement(UnitTypes::Zerg_Mutalisk),
    BuildOrderElement(UnitTypes::Zerg_Mutalisk),
    BuildOrderElement(UnitTypes::Zerg_Mutalisk)
    };
    double OneBaseSpireParams[6] = { 0.517767817, 1.238421617 , 0.48223217, 0.439303835, 0.717060969, 0.373843463 };

    //// 4H Macro Before Gas https://liquipedia.net/starcraft/4_Hatch_before_Gas_(vs._Protoss)
    //vector<BuildOrderElement> FourHatchCarapaceList = { BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Overlord),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Hatchery),
    //   BuildOrderElement(UnitTypes::Zerg_Spawning_Pool),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Hatchery),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Hatchery), // 16�18 � Hatchery @ Natural or 3rd Base. MacroHatch.
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Overlord), //Not listed in BO, difficult to time.
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Extractor),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Evolution_Chamber), //100% Extractor � Evolution Chamber (See note)
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Overlord), //Not listed in BO, difficult to time.
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UpgradeTypes::Zerg_Carapace), // @100% Evolution Chamber � +1 Zerg Carapace, (see note)
    //   BuildOrderElement(UnitTypes::Zerg_Overlord), //Not listed in BO, difficult to time.
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Hatchery), // @300 Minerals � Hatchery (preferably used to narrow a chokepoint)
    //   BuildOrderElement(UnitTypes::Zerg_Lair), // @100 Gas � Lair
    //   BuildOrderElement(UpgradeTypes::Metabolic_Boost), // @100 Gas � Metabolic Boost
    //   BuildOrderElement(UnitTypes::Zerg_Zergling),
    //   BuildOrderElement(UnitTypes::Zerg_Zergling),
    //   BuildOrderElement(UnitTypes::Zerg_Zergling),
    //   BuildOrderElement(UnitTypes::Zerg_Zergling),
    //   BuildOrderElement(UnitTypes::Zerg_Zergling),
    //   BuildOrderElement(UnitTypes::Zerg_Zergling),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Drone),
    //   BuildOrderElement(UnitTypes::Zerg_Overlord), //Not listed in BO, difficult to time.
    //   BuildOrderElement(UnitTypes::Zerg_Overlord), //Not listed in BO, difficult to time.
    //   BuildOrderElement(UnitTypes::Zerg_Overlord), //Not listed in BO, difficult to time.
    //   BuildOrderElement(UnitTypes::Zerg_Spire)
    //};

    //double FourHatchCarapaceParams[6] = { 0.458350597, 1.293531827 , 0.541649398, 0.33578132, 0.697611437, 0.319556148 };

    //4 hatch before pool. Gasless.
    vector<BuildOrderElement> FourHatchBeforePoolList = { BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Overlord),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Hatchery),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Hatchery),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Drone),
           BuildOrderElement(UnitTypes::Zerg_Hatchery),
           BuildOrderElement(UnitTypes::Zerg_Overlord),
           BuildOrderElement(UnitTypes::Zerg_Spawning_Pool)
    };
    double FourHatchBeforePoolParams[6] = { 0.458350597, 1.293531827 , 0.541649398, 0.33578132, 0.697611437, 0.319556148 };

    //Hardcoded build orders below.
    BuildOrderSetup MutaSetup = BuildOrderSetup(MutaList, mutaParams, BuildEnums::TwoBaseMuta);
    BuildOrderSetup OneBaseSpireSetup = BuildOrderSetup(OneBaseSpireList, OneBaseSpireParams, BuildEnums::OneBaseMuta);
    BuildOrderSetup LurkerSetup = BuildOrderSetup(lurkerList, lurkerParams, BuildEnums::Lurker);
    BuildOrderSetup fivePoolSetup = BuildOrderSetup(fivePoolList, fivePoolParams, BuildEnums::PoolFive);
    BuildOrderSetup sevenPoolSetup = BuildOrderSetup(sevenPoolList, sevenPoolParams, BuildEnums::PoolSeven);
    //BuildOrderSetup FourHatchCarapaceSetup = BuildOrderSetup(FourHatchCarapaceList, FourHatchCarapaceParams, BuildEnums::FourHatchCarapace);
    BuildOrderSetup FourHatchBeforePool = BuildOrderSetup(FourHatchBeforePoolList, FourHatchBeforePoolParams, BuildEnums::FourHatchBeforePool);

    myBuilds_.push_back(MutaSetup);
    myBuilds_.push_back(OneBaseSpireSetup);
    myBuilds_.push_back(LurkerSetup);
    myBuilds_.push_back(fivePoolSetup);
    myBuilds_.push_back(sevenPoolSetup);
    //myBuilds_.push_back(FourHatchCarapaceSetup);
    myBuilds_.push_back(FourHatchBeforePool);
}

void LearningManager::parseLearningFile()
{
    //Required Elements
    gameHistory_.clear();
    int nRows = countLines(getReadDir() + gameInfoExtension_);

    //Grab File
    ifstream myFile;
    myFile.open(getReadDir() + gameInfoExtension_);
    if (!myFile.is_open()) {
        Diagnostics::DiagnosticText("No file to read in to ParseCSV");
    }

    string rowHolder;
    for (int i = 0; i < nRows; i++) {
        History row;
        getline(myFile, rowHolder, ','); //First element is a string to enum problem.
        row.buildName = cleanBuildName(rowHolder);
        getline(myFile, rowHolder, ',');
        row.mapName = rowHolder;
        getline(myFile, rowHolder, ',');
        row.isWinner = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.shortDelay = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.medDelay = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.longDelay = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.scoreBuilding = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.scoreKill = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.scoreRaze = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.scoreUnit = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.firstAirFrame = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.firstDetectFrame = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.gameDuration = std::stoi(rowHolder);
        getline(myFile, rowHolder, ',');
        row.wasRushed = std::stoi(rowHolder);
        //Now that all files have been added, append this to the output vector.
        gameHistory_.push_back(row);
    }

    myFile.close();
}

bool LearningManager::checkValidBuild(BuildEnums b)
{
    //Mark all cases where the build is not ok.
    switch (Broodwar->enemy()->getRace()) {
    case Races::Protoss:
        if (b == BuildEnums::OneBaseMuta)
            return false;
        break;
    case Races::Terran:
        if (b == BuildEnums::OneBaseMuta)
            return false;
        break;
    case Races::Zerg:
        if (b == BuildEnums::Lurker)
            return false;
        break;
    default: //Random or Unknown.
        break;
    }

    return true;
}


void LearningManager::selectDefaultBuild() {
    switch (Broodwar->enemy()->getRace() ) {
    case Races::Protoss:
        currentBuild_.initializeBuildOrder(findMatchingBO(BuildEnums::TwoBaseMuta));
        break;
    case Races::Terran:
        currentBuild_.initializeBuildOrder(findMatchingBO(BuildEnums::Lurker));
        break;
    case Races::Zerg:
        currentBuild_.initializeBuildOrder(findMatchingBO(BuildEnums::OneBaseMuta));
        break;
    default: //Random or Unknown.
        currentBuild_.initializeBuildOrder(findMatchingBO(BuildEnums::PoolFive));
    }
}

void LearningManager::selectBestBuild()
{
    map<BuildEnums, double> storedUCB;
    Diagnostics::DiagnosticText("Selecting Best Build.");

    for (auto b : BuildStringsTable_) {
        Diagnostics::DiagnosticText("Enumerating Builds Within BuildTableName: %s", b.first.c_str());
    }

    for (auto b : BuildStringsTable_) {
        int win = 0, loss = 0, unmatched = 0;
        Diagnostics::DiagnosticText("On Start: Build, win, loss:", b.first.c_str(), win, loss);
        for (auto &game : gameHistory_) {
            if (game.buildName == b.first) {
                game.isWinner ? win++ : loss++;
            }
            else {
                unmatched++;
            }
        }
        Diagnostics::DiagnosticText("Game that matched: Build, win, loss, unmatched, UCB:", b.first.c_str(), win, loss, unmatched, getUpperConfidenceBound(win, loss));
        storedUCB.insert({ b.second , getUpperConfidenceBound(win, loss) });
        Diagnostics::DiagnosticText("Build: %s is rated %d", b.first.c_str(), getUpperConfidenceBound(win, loss));
    }

    BuildEnums bestBuild = currentBuild_.getBuildEnum(); //Start with the default.
    double bestScore = storedUCB.find(bestBuild)->second;

    for (auto &s : storedUCB) {
        if (s.second > bestScore && checkValidBuild(s.first)) {
            bestScore = s.second;
            bestBuild = s.first;
            Diagnostics::DiagnosticText("New best Build:", getBuildNameFromEnum(bestBuild) );
            currentBuild_.initializeBuildOrder(findMatchingBO(bestBuild));
        }
    }
}

//https://www.aionlinecourse.com/tutorial/machine-learning/upper-confidence-bound-%28ucb%29
double LearningManager::getUpperConfidenceBound(int win, int lose) {
    if (win + lose == 0) {
        return DBL_MAX;
    }
    return double(win) / double(lose) + sqrt( 1.5 * log(double(gameHistory_.size()))/double(win + lose) );
}

string LearningManager::getBuildNameFromEnum(const BuildEnums b) const
{
    for (auto i : BuildStringsTable_) {
        if (i.second == b)
            return i.first;
    }
    string errorMsg = "Error: Build Not Found in Build Strings Table";
    return errorMsg;
}

string LearningManager::getBuildName() const
{
    return getBuildNameFromEnum(currentBuild_.getBuildEnum());
}

const Build LearningManager::inspectCurrentBuild() const {
    return currentBuild_;
}

Build* LearningManager::modifyCurrentBuild()
{
    return &currentBuild_;
}

string LearningManager::getReadDir() const
{
    return readDirectory_;
}

string LearningManager::getWriteDir() const
{
    return writeDirectory_;
}


const int LearningManager::countLines(string fileIn) {
    ifstream myFile;
    myFile.open(fileIn);
    if (!myFile.is_open()) {
        Diagnostics::DiagnosticText("No file to read in at: %s", fileIn);
        return 0;
    }
    int n = 0;
    string rowHolder;
    while (getline(myFile, rowHolder)) {
        n++;
    }
    myFile.close();
    return n;
}

const void LearningManager::copyFile(string source, string destination) {
    std::ifstream src(source, std::ios::binary);
    std::ofstream dest(destination, std::ios::binary);
    dest << src.rdbuf();
}

string LearningManager::cleanBuildName(string messyString)
{
    string cleanup = messyString;
    cleanup.erase(std::remove(cleanup.begin(), cleanup.end(), '\n'), cleanup.end());
    return cleanup;
}

BuildOrderSetup LearningManager::findMatchingBO(BuildEnums b)
{
    for(auto i : myBuilds_)
        if(i.getBuildEnum() == b)
            return i;
    return BuildOrderSetup(); // else return an empty BO.
}

map<string, BuildEnums> LearningManager::BuildStringsTable_ ={
    { "MutaTwoBase", BuildEnums::TwoBaseMuta},
    { "Lurker", BuildEnums::Lurker },
    { "PoolFive", BuildEnums::PoolFive } ,
    { "PoolSeven", BuildEnums::PoolSeven } ,
    { "MutaOneBase", BuildEnums::OneBaseMuta },
    //{ "FourHatchCarapace", BuildEnums::FourHatchCarapace },
    { "FourHatchBeforePool", BuildEnums::FourHatchBeforePool }
};
