// This file added in headers queue
// File: "Sources.h"
#include "resource.h"

namespace GOTHIC_ENGINE {

    //variables
    int togglekey = KEY_P;
    //int undokey = KEY_F13;
    int resetkey = KEY_F11;
    int resetmodetogglekey = KEY_F12;
    string GTPrefix = "Game Time: ";
    string RTPrefix = "Real Time: ";
    bool ManualResetMode, ManualResetMode_Reset = false;
    int count = 0;
    uint64 Undo_SessionTimerBegin = 0;
    uint64 gameTimehelp = 0;
    uint64 resettime;
    bool useDefaultEndings = false;
    bool DisplayModName = false;
    string modname = "uninit";


    //main timer variables
    zCView* TimerView = Null;
    zCView* RealTimeView = Null;
    zCView* EndView = Null;
    zCView* ModView = Null;
    bool viewEnd = false;
    uint64 SessionTimerBegin = 0;
    bool SessionEnd = false;
    bool CanUpdateTimerView = true;
    uint64 realtimeAdd = 0;
    uint64 LastRealTime = 0;

    bool showGameTime = false;
    bool showRealTime = false;
    bool Standard = true;
    bool FinishCloseGame = false;
    string FinishVideo, FinishText, FinishDialogueAppears, FinishDocumentText = "NOT SET // DISABLED";

    bool inLoad, newLoad = false;
    uint64 loadStart, loadEnd = 0;
    uint64 realTime, gameTime, loadTime = 0;
    bool start = false;

    int FreezeTimerFrames = -1;


    uint64 GetMicroSeconds()
    {
        LARGE_INTEGER x, f;
        QueryPerformanceCounter(&x);
        QueryPerformanceFrequency(&f);

        x.QuadPart *= 1000000;
        x.QuadPart /= f.QuadPart;
        return x.QuadPart;
    }

    int KeyGetter(string& help) {
        if (help.HasWord("=F10")) { return KEY_F10; }
        if (help.HasWord("=F11")) { return KEY_F11; }
        if (help.HasWord("=F12")) { return KEY_F12; }
        if (help.HasWord("=F13")) { return KEY_F13; }
        if (help.HasWord("=F14")) { return KEY_F14; }
        if (help.HasWord("=F15")) { return KEY_F15; }
        if (help.HasWord("=F1")) { return KEY_F1; }
        if (help.HasWord("=F2")) { return KEY_F2; }
        if (help.HasWord("=F3")) { return KEY_F3; }
        if (help.HasWord("=F4")) { return KEY_F4; }
        if (help.HasWord("=F5")) { return KEY_F5; }
        if (help.HasWord("=F6")) { return KEY_F6; }
        if (help.HasWord("=F7")) { return KEY_F7; }
        if (help.HasWord("=F8")) { return KEY_F8; }
        if (help.HasWord("=F9")) { return KEY_F9; }
        if (help.HasWord("=A")) { return KEY_A; }
        if (help.HasWord("=B")) { return KEY_B; }
        if (help.HasWord("=C")) { return KEY_C; }
        if (help.HasWord("=D")) { return KEY_D; }
        if (help.HasWord("=E")) { return KEY_E; }
        if (help.HasWord("=F")) { return KEY_F; }
        if (help.HasWord("=G")) { return KEY_G; }
        if (help.HasWord("=H")) { return KEY_H; }
        if (help.HasWord("=I")) { return KEY_I; }
        if (help.HasWord("=J")) { return KEY_J; }
        if (help.HasWord("=K")) { return KEY_K; }
        if (help.HasWord("=L")) { return KEY_L; }
        if (help.HasWord("=M")) { return KEY_M; }
        if (help.HasWord("=N")) { return KEY_N; }
        if (help.HasWord("=O")) { return KEY_O; }
        if (help.HasWord("=P")) { return KEY_P; }
        if (help.HasWord("=R")) { return KEY_R; }
        if (help.HasWord("=S")) { return KEY_S; }
        if (help.HasWord("=T")) { return KEY_T; }
        if (help.HasWord("=U")) { return KEY_U; }
        if (help.HasWord("=V")) { return KEY_V; }
        if (help.HasWord("=W")) { return KEY_W; }
        if (help.HasWord("=X")) { return KEY_X; }
        if (help.HasWord("=Y")) { return KEY_Y; }
        if (help.HasWord("=Z")) { return KEY_Z; }
    }

    string ReadType(string help) {
        string type;
        if (help.HasWord("closegame")) {
            FinishCloseGame = true;
            Standard = false;
        }
        else if (help.HasWord("video")) {
            type = "video";
            Standard = false;
        }
        else if (help.HasWord("textdisplay")) {
            type = "textdisplay";
            Standard = false;
        }
        else if (help.HasWord("dialogchoiceselectionappears")) {
            type = "dialogchoiceselectionappears";
            Standard = false;
        }
        else if (help.HasWord("document")) {
            type = "document";
            Standard = false;
        }
        return type;
    };

    void ReadComparison(string type, string& line) {
        if (type == "video")
            FinishVideo = line;
        else if (type == "textdisplay")
            FinishText = line;
        else if (type == "dialogchoiceselectionappears")
            FinishDialogueAppears = line;
        else if (type == "document")
            FinishDocumentText = line;
    };

    void ReadSettings() {

        //get mod name
        COption& option = Union.GetGameOption();
        option.Read(modname, "INFO", "title", "");

        //load settings
        CDocument Settings;
        Settings.ReadFromFile(Union.GetSystemDirectory() + "Autorun/zSpeedRunTimer_Settings.txt");
        string type;

        //read default mod endings
        for (uint i = 0; i < Settings.GetNum(); i++) {
            string& line = Settings[i];
            if (line == "[EXPLANATIONS]")
                break;
            if (line == "useDefaultEndings=1") {
                useDefaultEndings = true;
                break;
            }
        }

        if (useDefaultEndings) {
            CDocument Endings;
            Endings.ReadFromFile(Union.GetSystemDirectory() + "Autorun/zSpeedRunTimer_Endings.txt");

            for (uint i = 0; i < Endings.GetNum(); i++) {
                string& line = Endings[i];
                if (line == "[EXPLANATIONS]" || line.HasWord("//"))
                    break;
                if (modname.HasWord(line)) {
                    if (i + 1 < Endings.GetNum()) {
                        line = Endings[i + 1];
                        type = ReadType(line.Replace("C1=", ""));
                    }
                    if (i + 2 < Endings.GetNum()) {
                        line = Endings[i + 2];
                        ReadComparison(type, line.Replace("C2=", ""));
                    }
                    break;
                }
            }
        }
        else {
            for (uint i = 0; i < Settings.GetNum(); i++) {
                string& line = Settings[i];
                if (line == "[EXPLANATIONS]")
                    break;
                if (line.HasWord("CustomEndingCondition"))
                    type = ReadType(line.Replace("CustomEndingCondition=", ""));
                if (line.HasWord("CustomEndingComparison"))
                    ReadComparison(type, line.Replace("CustomEndingComparison=", ""));
            }
        }

        //other timer settings

        for (uint i = 0; i < Settings.GetNum(); i++) {
            string& line = Settings[i];
            //timer
            if (line == "[EXPLANATIONS]")
                break;
            if (line == "showgametime=1")
                showGameTime = true;
            else if (line == "showrealtime=1")
                showRealTime = true;
            else if (line.HasWord("GTPrefix="))
                GTPrefix = line.Replace("GTPrefix=", "");
            else if (line.HasWord("RTPrefix="))
                RTPrefix = line.Replace("RTPrefix=", "");
            else if (line.HasWord("realtimetogglekey="))
                togglekey = KeyGetter(line);
            // else if (line.HasWord("undokey="))
            //     undokey = KeyGetter(line);
            else if (line == "ManualReset=1")
                ManualResetMode = true;
            else if (line.HasWord("resetkey="))
                resetkey = KeyGetter(line);
            else if (line.HasWord("resetmodetogglekey="))
                resetmodetogglekey = KeyGetter(line);
            else if (line == "DisplayModName=1")
                DisplayModName = true;
        }
    }

    void EndSession() {
        SessionEnd = true;
    }

    //Document Ending
    HOOK Hook_oCDocumentManager_PrintLines PATCH(&oCDocumentManager::PrintLines, &oCDocumentManager::PrintLines_Union);
    void __fastcall oCDocumentManager::PrintLines_Union(int a1, int a2, zSTRING& text) {
        //Sequel Phoenix Quest
#if ENGINE == Engine_G1A
        if (Standard && text.HasWordI("Du hast das R")) {
            EndSession();
        }
#endif

        //mod end
        if (text.HasWordI(FinishDocumentText)) {
            EndSession();
        }
        THISCALL(Hook_oCDocumentManager_PrintLines)(a1, a2, text);
    }

    //stop game closing after finish in G1
    HOOK Hook_CGameManager_ExitGame AS(&CGameManager::ExitGame, &CGameManager::ExitGame_Union);
    int CGameManager::ExitGame_Union() {
        //stop timer for mods
        if (FinishCloseGame) {
            EndSession();
        }
        viewEnd = true;
        return 0;
    }

    //Text Display
    HOOK Hook_zCView_CreateText AS(&zCView::CreateText, &zCView::CreateText_Union);
    zCViewText* __thiscall zCView::CreateText_Union(int a1, int a2, zSTRING const& a3, float a4, struct zCOLOR& a5, int a6, int a7) {
        // End session!
        if (a3.HasWordI(FinishText)) {
            EndSession();
        }
        return THISCALL(Hook_zCView_CreateText)(a1, a2, a3, a4, a5, a6, a7);
    }

    //Video Ending
    void PlayVideo_Help(zSTRING name) {
        // End session!
        if (name.HasWordI(FinishVideo)
            || (Standard && (name.HasWordI("Extro.bik") || name.HasWordI("Extro_Xardas")))
            || (Standard && name.HasWordI("Extro_") && modname.HasWordI("Undead Dragon Kill"))
            ) {
            EndSession();
        }

        // Don't update timer in video
        CanUpdateTimerView = false;
    }

    void PlayVideo_After() {
        CanUpdateTimerView = true;
    }

    HOOK Hook_CGameManager_PlayVideo AS(&CGameManager::PlayVideo, &CGameManager::PlayVideo_Union);
#if ENGINE >= Engine_G2
    int CGameManager::PlayVideo_Union(zSTRING name) {
#else
    int CGameManager::PlayVideo_Union(zSTRING name, int a2) {
#endif
        PlayVideo_Help(name);
#if ENGINE >= Engine_G2
        int Ok = THISCALL(Hook_CGameManager_PlayVideo)(name);
#else
        int Ok = THISCALL(Hook_CGameManager_PlayVideo)(name, a2);
#endif
        PlayVideo_After();
        return Ok;
    }

#if ENGINE >= Engine_G2
    HOOK Hook_CGameManager_PlayVideoEx AS(&CGameManager::PlayVideoEx, &CGameManager::PlayVideoEx_Union);
    int CGameManager::PlayVideoEx_Union(zSTRING name, short a2, int a3) {
        PlayVideo_Help(name);
        int Ok = THISCALL(Hook_CGameManager_PlayVideoEx)(name, a2, a3);
        PlayVideo_After();
        return Ok;
    }
#endif

    //DialogChoice Ending
    HOOK Hook_zCViewDialogChoice_AddChoice AS(&zCViewDialogChoice::AddChoice, &zCViewDialogChoice::AddChoice_Union);
    void __fastcall zCViewDialogChoice::AddChoice_Union(zSTRING & a1, int a2) {
        //Sequel WIP
#if ENGINE == Engine_G1A
        if (Standard && a1.HasWordI("(WORKINPROGRESS)")) {
            EndSession();
        }
#endif

        //mod ending
        if (a1.HasWordI(FinishDialogueAppears)) {
            EndSession();
        }
        THISCALL(Hook_zCViewDialogChoice_AddChoice)(a1, a2);
    }


    string GetTimeNumberFormated(uint64 timePtr, uint length) {
        string number = timePtr;
        while (number.Length() < length)
            number = "0" + number;

        return number;
    }

    string GetTimeStringFormated_Help(uint64 timePtr) {
        timePtr /= 1000;
#pragma warning(disable:4244)
        long hr = timePtr / 3600000;
        timePtr = timePtr - 3600000 * hr;

        long min = timePtr / 60000;
        timePtr = timePtr - 60000 * min;

        long sec = timePtr / 1000;
        timePtr = timePtr - 1000 * sec;
#pragma warning(default:4244)



        string hour = string::Combine("%i:", hr);
        string rest = string::Combine("%s:%s.%s", GetTimeNumberFormated(min, 2), GetTimeNumberFormated(sec, 2), GetTimeNumberFormated(timePtr, 3));
        if (hr > 0)
            return hour + rest;
        else
            return rest;
    }

    string GetTimeStringFormated(uint64 timePtr, string pre) {
        return pre + GetTimeStringFormated_Help(timePtr);
    }

    //detect normal gameplay
    HOOK Hook_zCCamera_PostRenderProcessing AS(&zCCamera::PostRenderProcessing, &zCCamera::PostRenderProcessing_Union);
    void zCCamera::PostRenderProcessing_Union(void) {

        CanUpdateTimerView = true;

        if (FreezeTimerFrames != -1 && FreezeTimerFrames-- == 0)
        {
            FreezeTimerFrames = 1;

            if (inLoad == true) {
                inLoad = false;
                loadEnd = GetMicroSeconds();
                newLoad = true;
                //detect new game
                if (start == false) {
                    loadTime = 0;
                    realtimeAdd = GetMicroSeconds() - loadStart;
                    SessionTimerBegin = GetMicroSeconds();
                    start = true;
                    loadStart, loadEnd = 0;
                }
            }
        }

        THISCALL(Hook_zCCamera_PostRenderProcessing)();
    }



    //detect loading/saving
    HOOK Hook_zCViewProgressBar_Draw AS(&zCViewProgressBar::Draw, &zCViewProgressBar::Draw_Union);
    void __thiscall zCViewProgressBar::Draw_Union() {
        viewEnd = false;
        CanUpdateTimerView = false;

        if (inLoad == false) {
            loadStart = GetMicroSeconds();
            inLoad = true;
        }

        THISCALL(Hook_zCViewProgressBar_Draw)();
    }

    void StartTimer() {
        start = false;
        //Undo_SessionTimerBegin = SessionTimerBegin;
        SessionTimerBegin = GetMicroSeconds();
        SessionEnd = false;
        //inLoad = true;
        //count++;
        gameTimehelp = 0;
    }


  void Game_Entry() {
      ReadSettings();
  }
  
  void Game_Init() {
      TimerView = new zCView();
      screen->InsertItem(TimerView, True);
      TimerView->SetPos(0, 100);
      TimerView->SetSize(8000, 1000);
      TimerView->SetFont("FONT_OLD_20_WHITE.TGA");
      if (showGameTime)
          TimerView->Print(100, 100, GetTimeStringFormated(0, GTPrefix));

      RealTimeView = new zCView();
      screen->InsertItem(RealTimeView, True);
      RealTimeView->SetPos(0, 400);
      RealTimeView->SetSize(8000, 1000);
      RealTimeView->SetFont("FONT_OLD_20_WHITE.TGA");
      if (showRealTime)
          RealTimeView->Print(100, 100, GetTimeStringFormated(0, RTPrefix));

      if (DisplayModName) {
          ModView = new zCView();
          screen->InsertItem(ModView, True);
          ModView->SetPos(0, 0);
          ModView->SetSize(8000, 1000);
          ModView->SetFont("FONT_OLD_10_WHITE.TGA");
          ModView->Print(100, 100, "Mod: " + modname);
      }

      EndView = new zCView();
      screen->InsertItem(EndView, True);
      EndView->SetPos(55, 700);
      EndView->SetSize(3000, 1000);
      EndView->SetFont("FONT_OLD_20_WHITE.TGA");

      SessionTimerBegin = GetMicroSeconds();
      SessionEnd = false;
  }

  void Game_Exit() {
  }

  void Game_PreLoop() {
  }

  void Game_Loop() {
      //update total time
      realTime = GetMicroSeconds() - SessionTimerBegin;
      //add new load time
      if (newLoad == true) {
          inLoad = false;
          loadEnd = loadEnd - loadStart;
          if (loadEnd > 0) {
              loadTime += loadEnd;
          }
          loadStart, loadEnd = 0;
          newLoad = false;
      }
      if (loadTime > realTime) {
          loadTime = 0;
      }
      //subtract loads from total time
      gameTime = realTime - loadTime;
      realTime = realTime + realtimeAdd;

      if (CanUpdateTimerView) {
          if (showGameTime) {
              TimerView->ClrPrintwin();
              if (SessionEnd == false) {
                  TimerView->Print(100, 100, GetTimeStringFormated(gameTime, GTPrefix));
                  gameTimehelp = gameTime;
              }
              else {
                  TimerView->Print(100, 100, GetTimeStringFormated(gameTimehelp, GTPrefix));
              }
          }

          if (SessionEnd == false)
              LastRealTime = realTime;

          RealTimeView->ClrPrintwin();
          if (showRealTime) {
              if (SessionEnd == false) {
                  RealTimeView->Print(100, 100, GetTimeStringFormated(realTime, RTPrefix));
              }
              else {
                  RealTimeView->Print(100, 100, GetTimeStringFormated(LastRealTime, RTPrefix));
              }
          }

          EndView->ClrPrintwin();
          if (viewEnd)
              EndView->Print(100, 100, "Game ended");
          if (GetMicroSeconds() < resettime + 2000000 && ManualResetMode) {
              EndView->Print(100, 100, "manual reset on");
          }
          if (GetMicroSeconds() < resettime + 2000000 && !ManualResetMode) {
              EndView->Print(100, 100, "manual reset off");
          }
      }

      //keys
      if (zKeyToggled(togglekey))
          showRealTime = !showRealTime;
      /*
      if (zKeyToggled(undokey)) {
          if (count > 1) {
              SessionTimerBegin = Undo_SessionTimerBegin + realtimeAdd;
              }
      }
      */
      if (zKeyToggled(resetkey)) {
          if (ManualResetMode)
              ManualResetMode_Reset = true;
      }

      if (zKeyToggled(resetmodetogglekey)) {
          resettime = GetMicroSeconds();
          ManualResetMode = !ManualResetMode;
          ManualResetMode_Reset = false;
      }
  }

  void Game_PostLoop() {
  }

  void Game_MenuLoop() {
  }

  // Information about current saving or loading world
  TSaveLoadGameInfo& SaveLoadGameInfo = UnionCore::SaveLoadGameInfo;

  void Game_SaveBegin() {
  }

  void Game_SaveEnd() {
  }

  void LoadBegin() {
  }

  void LoadEnd() {
      FreezeTimerFrames = 1;
      if (ManualResetMode && ManualResetMode_Reset) {
          ManualResetMode_Reset = false;
          StartTimer();
      }
  }

  void Game_LoadBegin_NewGame() {
    LoadBegin();
  }

  void Game_LoadEnd_NewGame() {
    LoadEnd();
    if (!ManualResetMode || (ManualResetMode && ManualResetMode_Reset)) {
        StartTimer();
    }
  }

  void Game_LoadBegin_SaveGame() {
    LoadBegin();
  }

  void Game_LoadEnd_SaveGame() {
    LoadEnd();
  }

  void Game_LoadBegin_ChangeLevel() {
    LoadBegin();
  }

  void Game_LoadEnd_ChangeLevel() {
    LoadEnd();
  }

  void Game_LoadBegin_Trigger() {
  }
  
  void Game_LoadEnd_Trigger() {
  }
  
  void Game_Pause() {
  }
  
  void Game_Unpause() {
  }
  
  int zSpeedrunTimer_End()
  {
      zCParser* par = zCParser::GetParser();
      if (Standard)
          EndSession();
      return 0;
  }

  void Game_DefineExternals() {
      parser->DefineExternal("zSpeedrunTimer_End",
          zSpeedrunTimer_End,
          zPAR_TYPE_VOID,
          zPAR_TYPE_VOID
      );
  }

  void Game_ApplyOptions() {


  }

  /*
  Functions call order on Game initialization:
    - Game_Entry           * Gothic entry point
    - Game_DefineExternals * Define external script functions
    - Game_Init            * After DAT files init
  
  Functions call order on Change level:
    - Game_LoadBegin_Trigger     * Entry in trigger
    - Game_LoadEnd_Trigger       *
    - Game_Loop                  * Frame call window
    - Game_LoadBegin_ChangeLevel * Load begin
    - Game_SaveBegin             * Save previous level information
    - Game_SaveEnd               *
    - Game_LoadEnd_ChangeLevel   *
  
  Functions call order on Save game:
    - Game_Pause     * Open menu
    - Game_Unpause   * Click on save
    - Game_Loop      * Frame call window
    - Game_SaveBegin * Save begin
    - Game_SaveEnd   *
  
  Functions call order on Load game:
    - Game_Pause              * Open menu
    - Game_Unpause            * Click on load
    - Game_LoadBegin_SaveGame * Load begin
    - Game_LoadEnd_SaveGame   *
  */

#define AppDefault True
  CApplication* lpApplication = !CHECK_THIS_ENGINE ? Null : CApplication::CreateRefApplication(
    Enabled( AppDefault ) Game_Entry,
    Enabled( AppDefault ) Game_Init,
    Enabled( AppDefault ) Game_Exit,
    Enabled( AppDefault ) Game_PreLoop,
    Enabled( AppDefault ) Game_Loop,
    Enabled( AppDefault ) Game_PostLoop,
    Enabled( AppDefault ) Game_MenuLoop,
    Enabled( AppDefault ) Game_SaveBegin,
    Enabled( AppDefault ) Game_SaveEnd,
    Enabled( AppDefault ) Game_LoadBegin_NewGame,
    Enabled( AppDefault ) Game_LoadEnd_NewGame,
    Enabled( AppDefault ) Game_LoadBegin_SaveGame,
    Enabled( AppDefault ) Game_LoadEnd_SaveGame,
    Enabled( AppDefault ) Game_LoadBegin_ChangeLevel,
    Enabled( AppDefault ) Game_LoadEnd_ChangeLevel,
    Enabled( AppDefault ) Game_LoadBegin_Trigger,
    Enabled( AppDefault ) Game_LoadEnd_Trigger,
    Enabled( AppDefault ) Game_Pause,
    Enabled( AppDefault ) Game_Unpause,
    Enabled( AppDefault ) Game_DefineExternals,
    Enabled( AppDefault ) Game_ApplyOptions
  );
}