#pragma once

#include "Common.h"
#include "OpeningTimingRecord.h"
#include "OpponentPlan.h"
#include "PlayerSnapshot.h"
#include "Skill.h"

#include <exception>

// NOTE
// This class does only a little checking of its input file format. Feed it no bad files.

class game_record_read_error : public std::exception {};

namespace UAlbertaBot
{
struct GameSnapshot
{
    const int frame;
    const PlayerSnapshot us;
    const PlayerSnapshot them;

    // For reading a snapshot from a file.
    GameSnapshot(int t, const PlayerSnapshot & me, const PlayerSnapshot & you)
        : frame(t)
        , us(me)
        , them(you)
    {
    }

    // For taking a snapshot during the game.
    GameSnapshot(const PlayerSnapshot & me, const PlayerSnapshot & you)
        : frame(BWAPI::Broodwar->getFrameCount())
        , us(me)
        , them(you)
    {
    }
};

class GameRecord
{
protected:
    const int firstSnapshotTime = 2 * 60 * 24;
    const int snapshotInterval = 30 * 24;

    const std::string latestRecordFormat = "3.0";
    const std::string gameEndMark = "END GAME";

    // Each game record is labeled with a version number, to allow backward compatibility
    // when the file format changes.
    // Plan: Set the version number to the last Steamhammer release which changed the file format.
    std::string recordFormat;

    // Is this a valid record, or broken in reading?
    bool valid;

    // Is this the record of the current game, or a saved record?
    bool savedRecord;

    // About the game.
    BWAPI::Race ourRace;
    BWAPI::Race enemyRace;
    bool enemyIsRandom;

    std::string mapName;
    std::string openingName;
    int myStartingBaseID;
    int enemyStartingBaseID;
    OpeningPlan expectedEnemyPlan;
    OpeningPlan enemyPlan;
    bool win;

    // Gas steal data only for v 1.4 game records.
    int frameScoutSentForGasSteal;         // 0 if we didn't try to steal gas
    bool gasStealHappened;                 // true if a refinery building was started

    int frameWeMadeFirstCombatUnit;
    int frameWeGatheredGas;

    int frameEnemyScoutsOurBase;
    int frameEnemyGetsCombatUnits;
    int frameEnemyUsesGas;
    int frameEnemyGetsAirUnits;
    int frameEnemyGetsStaticAntiAir;
    int frameEnemyGetsMobileAntiAir;
    int frameEnemyGetsCloakedUnits;
    int frameEnemyGetsStaticDetection;     // includes spider mines
    int frameEnemyGetsMobileDetection;
    int frameGameEnds;

    // Record skill kit data as raw text so that individual skills don't have to remember it.
    std::vector <std::string> skillKitText;

    // A skill can also store data here for analysis.
    std::map< Skill *, std::map< int, std::vector<int> > > skillData;

    // We allocate the snapshots and never release them.
    std::vector<GameSnapshot *> snapshots;

    BWAPI::Race charRace(char ch);

    int readNumber(std::istream & input);
    int readNumber(std::string & s);

    void parseMatchup(const std::string & s);

    OpeningPlan readOpeningPlan(std::istream & input);

    bool readPlayerSnapshot(std::istream & input, PlayerSnapshot & snap);
    GameSnapshot * readGameSnapshot(std::istream & input);
    void skipToEnd(std::istream & input);

    void read_v3_0(std::istream & input);
    void read_v1_4(std::istream & input);
    void read(std::istream & input);

    void writePlayerSnapshot(std::ostream & output, const PlayerSnapshot & snap);
    void writeGameSnapshot(std::ostream & output, const GameSnapshot * snap);
    virtual void writeSkills(std::ostream & output) const;

    int snapDistance(const PlayerSnapshot & a, const PlayerSnapshot & b) const;

public:
    // Store opening timing skill data specially.
    OpeningTimingRecord openingTimingRecord;

    GameRecord();
    GameRecord(std::istream & input);

    void write(std::ostream & output);

    bool isValid() { return valid; };

    int distance(const GameRecord & record) const;    // similarity distance UNUSED

    bool findClosestSnapshot(int t, PlayerSnapshot & snap) const;

    bool sameMatchup(const GameRecord & record) const;

    bool getEnemyIsRandom() const { return enemyIsRandom; };
    const std::string & getMapName() const { return mapName; };
    const std::string & getOpeningName() const { return openingName; };
    OpeningPlan getExpectedEnemyPlan() const { return expectedEnemyPlan; };
    OpeningPlan getEnemyPlan() const { return enemyPlan; };
    bool getWin() const { return win; };
    int getFrameScoutSentForGasSteal() const { return frameScoutSentForGasSteal; };
    bool getGasStealHappened() const { return gasStealHappened; };
    int getFrameEnemyUsesGas() const { return frameEnemyUsesGas; };
	int getFrameEnemyGetsAirUnits() const { return frameEnemyGetsAirUnits; };

    const std::vector<int> * getSkillInfo(Skill * skill, int i) const;
    void setSkillInfo(Skill * skill, int i, const std::vector<int> & info);

    void debugLog();
};

}