// Clockwork Castletown v1.0.0 by Professor Fartsalot for the FireHost Redux Community
#include <clientprefs>
#include <fcc_helper>
#include <morecolors>
#include <regex>
#include <sdktools>
#include <sourcemod>
#include <tf2_stocks>
#pragma newdecls required
#pragma semicolon 1
static char PLUGIN_VERSION[8] = "1.0.5";

public Plugin myinfo = {
  name = "Fartsy's Clockwork Chaos - Framework",
  author = "Fartsy",
  description = "Framework for Fartsy's Clockwork Chaos (PL Mods)",
  version = PLUGIN_VERSION,
  url = "https://forums.firehostredux.com"
};

public void OnPluginStart() {
  FccLogger(1, "####### STARTUP SEQUENCE INITIATED... PREPARE FOR THE END TIMES #######");
  RegisterAndPrecacheAllFiles();
  RegisterAllCommands();
  SetupCoreData();
  CPrintToChatAll("{darkred}Plugin Reloaded. If you do not hear music, please do !sounds and configure your preferences.");
  // cvarSNDDefault = CreateConVar("sm_fartsyscc_sound", "3", "Default sound for new users, 3 = Everything, 2 = Sounds Only, 1 = Music Only, 0 = Nothing");
  // SetCookieMenuItem(FartsysSNDSelected, 0, "Fartsys Clockwork Chaos Sound Preferences");
  Format(LoggerInfo, sizeof(LoggerInfo), "####### STARTUP COMPLETE (v%s) #######", PLUGIN_VERSION);
  FccLogger(1, LoggerInfo);
}

// Music system, see FartsysAss.sp:46
public void OnGameFrame() {
  /*  if (core.tickMusic) {
      core.ticksMusic++;
      if (core.ticksMusic >= core.refireTime) {
        if (core.shouldStopMusic) {
          for (int i = 1; i <= MaxClients; i++) {
            StopSound(i, core.SNDCHAN, core.cachedPath);
            core.shouldStopMusic = false;
          }
        }
        core.songName = BGMArray[core.BGMINDEX].songName;
        core.refireTime = BGMArray[core.BGMINDEX].refireTime;
        core.ticksMusic = (core.tickOffset ? BGMArray[core.BGMINDEX].ticksOffset : 0);
        CustomSoundEmitter(BGMArray[core.BGMINDEX].realPath, BGMArray[core.BGMINDEX].SNDLVL, true, 1, 1.0, 100);
        CreateTimer(1.0, SyncMusic);
      }
    }*/
}


// Restart music for the new client
public Action RefireMusicForClient(Handle timer, int client) {
  if (IsValidClient(client)) {
    //    if (GetClientTeam(client) == 0) CreateTimer(1.0, RefireMusicForClient, client);
    //    else if (GetClientTeam(client) == 2) CSEClient(client, BGMArray[core.BGMINDEX].realPath, BGMArray[core.BGMINDEX].SNDLVL, true, 1, 1.0, 100);
  }
  return Plugin_Stop;
}

// Queue music for new clients, also track their health.
public void OnClientPostAdminCheck(int client) {
  //  if (!IsFakeClient(client) && core.bgmPlaying) CreateTimer(1.0, RefireMusicForClient, client);
  int steamID = GetSteamAccountID(client);
  if (!steamID || steamID <= 10000) return;
  //  if (!core.bgmPlaying) SetupMusic(GetRandomInt(1, 4)); //Change these variables, I have no idea how this should work at the moment.
}

// Now that command definitions are done, lets make some things happen.
public void OnMapStart() {
  //  CreateTimer(1.0, SelectAdminTimer);
  //  FastFire("OnUser1 rain:Alpha:0:0.0:1");
  //  sudo(1002);
}

// Adverts for round information
public Action PerformMatchAdverts(Handle timer) {
  //  if (core.isMatch) {
  char buffer[16];
  char tbuffer[16];
  char HintText[256];
  //    int sPos = RoundToFloor(core.ticksMusic / 66.6666666666);
  //    int tPos = RoundToFloor(core.refireTime / 66.6666666666);
  //    Format(buffer, 16, "%02d:%02d", sPos / 60, sPos % 60);
  //    Format(tbuffer, 16, "%02d:%02d", tPos / 60, tPos % 60);
  // Format(HintText, sizeof(HintText), (bombState[0].isMoving ? "Payload: MOVING (%i/%i) | !sacpoints: %i/%i \n Music: %s (%s/%s)" : bombState[0].isReady ? "Payload: READY (%i/%i) | !sacpoints: %i/%i \n Music: %s (%s/%s)" : "Payload: PREPARING (%i/%i) | !sacpoints: %i/%i \n Music: %s (%s/%s)"), bombState[0].state, bombState[0].stateMax, core.sacPoints, core.sacPointsMax, core.songName, buffer, tbuffer);
  //    CreateTimer(2.5, PerformMatchAdverts);
  for (int i = 1; i <= MaxClients; i++) {
    if (IsValidClient(i)) {
      PrintHintText(i, HintText);
      StopSound(i, SNDCHAN_STATIC, "UI/hint.wav");
    }
  }
  //}
  return Plugin_Stop;
}

// Command: Get current song
public Action Command_GetCurrentSong(int client, int args) {
  /*char buffer[16];
  char tbuffer[16];
  int sPos = RoundToFloor(core.ticksMusic / 66.6666666666);
  int tPos = RoundToFloor(core.refireTime / 66.6666666666);
  Format(buffer, 16, "%02d:%02d", sPos / 60, sPos % 60);
  Format(tbuffer, 16, "%02d:%02d", tPos / 60, tPos % 60);
  CPrintToChat(client, "The current song is: {limegreen}%s {orange}(%s / %s)", core.songName, buffer, tbuffer);*/
  return Plugin_Handled;
}

// Command: Return the client to spawn
public Action Command_Return(int client, int args) {
  if (!IsPlayerAlive(client)) {
    CPrintToChat(client, "{red}[Core] You must be alive to use this command...");
    return Plugin_Handled;
  }
  char name[128];
  GetClientName(client, name, sizeof(name));
  CPrintToChatAll("{darkviolet}[{forestgreen}CORE{darkviolet}] {white}Client {red}%s {white}began casting {darkviolet}/return{white}.", name);
  //  CustomSoundEmitter(SFXArray[41], 65, false, 0, 1.0, 100);
  CreateTimer(5.0, ReturnClient, client);
  return Plugin_Handled;
}

// Return the client to spawn
public Action ReturnClient(Handle timer, int clientID) {
  //  TeleportEntity(clientID, Return, NULL_VECTOR, NULL_VECTOR);
  // CSEClient(clientID, SFXArray[42], 65, false, 0, 1.0, 100);
  return Plugin_Handled;
}

// Join us on Discord!
public Action Command_Discord(int client, int args) {
  CPrintToChat(client, "{darkviolet}[{forestgreen}CORE{darkviolet}] {white}Our Discord server URL is {darkviolet}https://discord.com/invite/HjQsDy6e2H{white}.");
  ShowMOTDPanel(client, "FireHostRedux Discord", "https://discord.com/invite/HjQsDy6e2H", MOTDPANEL_TYPE_URL);
  return Plugin_Handled;
}

// Check who died by what and announce it to chat.
/*
public Action EventDeath(Event Spawn_Event, const char[] Spawn_Name, bool Spawn_Broadcast) {
  int client = GetClientOfUserId(Spawn_Event.GetInt("userid"));
  int attacker = GetClientOfUserId(Spawn_Event.GetInt("attacker"));
  char name[64];
  char weapon[32];
  Format(name, sizeof(name), attacker == 0 ? "[INTENTIONAL GAME DESIGN]" : "%N", IsValidClient(attacker) ? client : attacker);
  Spawn_Event.GetString("weapon", weapon, sizeof(weapon));
  if (0 < client <= MaxClients && IsClientInGame(client)) {
    int damagebits = Spawn_Event.GetInt("damagebits");
    if (IsValidClient(attacker) && core.sacrificedByClient) SacrificeClient(client, attacker, core.bombReset);
    if (!attacker) {
        switch (damagebits){
            case 1:{

            }
        }
    }
   if (attacker != client) {
      if (!FB_Database) return Plugin_Handled;
      char query[256];
      int steamID = (IsValidClient(attacker) ? GetSteamAccountID(attacker) : GetSteamAccountID(client));
      Format(query, sizeof(query), IsValidClient(attacker) && !StrEqual(weapon, "world") ? "UPDATE ass_activity SET kills = kills +1, killssession = killssession + 1, lastkilledname = '%s', lastweaponused = '%s' WHERE steamid = %i;" : !StrEqual(weapon, "world") && !IsValidClient(attacker) ? "UPDATE ass_activity SET deaths = deaths + 1, deathssession = deathssession + 1, killedbyname = '%s', killedbyweapon = '%s' WHERE steamid = %i;" : "RETURN", name, weapon, steamID);
      if (StrEqual(query, "RETURN")) return Plugin_Handled;
      FB_Database.Query(Database_FastQuery, query);
    }
  }
  return Plugin_Handled;
}*/

// Create a temp entity and fire an input - ADVANCED Mode
public Action FastFire(char[] input) {
  int entity = CreateEntityByName("info_target");
  if (!IsValidEdict(entity)) return Plugin_Handled;
  DispatchSpawn(entity);
  ActivateEntity(entity);
  SetVariantString(input);
  AcceptEntityInput(entity, "AddOutput");
  AcceptEntityInput(entity, "FireUser1");
  CreateTimer(0.0, DeleteEdict, entity);
  return Plugin_Continue;
}

// Custom sound emitter, I don't know how many fucking times I've rewritten this! See potato.sp
// int flags:
//	SND_NOFLAGS= 0,             /**< Nothing */
//	SND_CHANGEVOL = 1,          /**< Change sound volume */
//	SND_CHANGEPITCH = 2,        /**< Change sound pitch */
//	SND_STOP = 3,               /**< Stop the sound */
//	SND_SPAWNING = 4,           /**< Used in some cases for ambients */
//	SND_DELAY = 5,              /**< Sound has an initial delay */
//	SND_STOPLOOPING = 6,        /**< Stop looping all sounds on the entity */
//	SND_SPEAKER = 7,            /**< Being played by a mic through a speaker */
//	SND_SHOULDPAUSE = 8         /**< Pause if game is paused */
void CustomSoundEmitter(char[] sndName, int TSNDLVL, bool isBGM, int flags, float vol, int pitch) {
  for (int i = 1; i <= MaxClients; i++) {
    if (!IsValidClient(i)) continue;
    //    if (isBGM && (soundPreference[i] == 1 || soundPreference[i] == 3) || !isBGM && soundPreference[i] >= 2) EmitSoundToClient(i, sndName, _, core.SNDCHAN, TSNDLVL, flags, vol, pitch, _, _, _, _, _);
  }
}
// Play sound to client. Ripped straight from potato. Allows us to play sounds directly to people when they join.
void CSEClient(int client, char[] sndName, int TSNDLVL, bool isBGM, int flags, float vol, int pitch) {
  if (!IsValidClient(client)) return;
  //  if (isBGM && (soundPreference[client] == 1 || soundPreference[client] == 3) || !isBGM && soundPreference[client] >= 2) EmitSoundToClient(client, sndName, _, core.SNDCHAN, TSNDLVL, flags, vol, pitch, _, _, _, _, _);
}

// Remove edict allocated by temp entity
public Action DeleteEdict(Handle timer, any entity) {
  if (IsValidEdict(entity)) RemoveEdict(entity);
  return Plugin_Stop;
}

// Log debug info
void FccLogger(int logLevel, char[] logData) {
  switch (logLevel) {
    case 0: {
      LogMessage("[DEBUG]: %s", logData);
    }
    case 1: {
      LogMessage("[INFO]: %s", logData);
    }
    case 2: {
      LogMessage("[WARN]: %s", logData);
    }
    case 3: {
      LogMessage("[ERROR]: %s", logData);
    }
  }
}

// Operator, core of the entire map
public Action Command_Operator(int args) {
  char arg1[16];
  GetCmdArg(1, arg1, sizeof(arg1));
  int x = StringToInt(arg1);
  PrintToServer("Directly calling sudo with %i", args);
  sudo(x);
  return Plugin_Continue;
}

void sudo(int task) {
  Format(LoggerDbg, sizeof(LoggerDbg), "Calling sudo with %i", task);
  FccLogger(0, LoggerDbg);
  switch (task) {
    // Match End, pick winning team based on distance
    case 0: {
      FastFire(PLR > PLB ? "OnUser1 WinRed:RoundWin::0.0:1" : PLR < PLB ? "OnUser1 WinBlu:RoundWin::0.0:1" : "OnUser1 WinStalemate:RoundWin::0.0:1");
      return;
    }
    // Setup Begin
    case 1: {
      FccLogger(0, "Setup beginning....");
      FastFire("OnUser1 PL1.TrackTrain:Stop::0.5:1");
      FastFire("OnUser1 UI.PLCore:SwitchOverlay:1::0.0:1");
      FastFire("OnUser1 UI.PLBlue00:SwitchOverlay:1::0.0:1");
      FastFire("OnUser1 UI.PLBlue01:SwitchOverlay:1::0.0:1");
      FastFire("OnUser1 UI.PLBlue02:SwitchOverlay:1::0.0:1");
      if(RecedeTimer != INVALID_HANDLE) KillTimer(RecedeTimer);
      isMatch = true;
      PLB = 0;
      PLL = 0;
      PLM = false;
      PLR = 0;
      PLT = "N/A";
      PLRL = 20;
      BLC = false;
      REC = false;
      RECEDE = false;
      CreateTimer(0.5, PayloadUpdateTimer);
      CPrintToChatAll("{darkgreen} Clockwork Castletown ({lime}v%s{darkgreen}) - Escort the crystal the longest distance to win!", PLUGIN_VERSION);
      return;
    }
    // Setup End / round start
    case 2: {
      FccLogger(1, "Match started!");
      return;
    }
    case 3: {
      // Payload started moving by blue
      PLT = "BLU";
      PLM = true;
      FastFire("OnUser1 PL1.TrackTrain:StartForward::0.0:1");
      KillTimer(RecedeTimer);
      RecedeTimer = INVALID_HANDLE;
      RECEDE = false;
      return;
    }
    // Payload started moving by red
    case 4: {
      PLT = "RED";
      PLM = true;
      FastFire("OnUser1 PL1.TrackTrain:StartBackward::0.0:1");
      KillTimer(RecedeTimer);
      RecedeTimer = INVALID_HANDLE;
      RECEDE = false;
      return;
    }
    // Payload stopped moving
    case 5: {
      PLT = "NEUTRAL";
      PLM = false;
      FastFire("OnUser1 PL1.TrackTrain:Stop::0.0:1");
      RecedeTimer = CreateTimer(45.0, BeginRecede, PLL);
      return;
    }
    // Payload on blue side of map
    case 6: {
      PLL = 2;
      return;
    }
    // Payload red side of map
    case 7: {
      PLL = 1;
      return;
    }
    // Point 1 capture (this would be 75% capture from blue's perspective)
    case 8: {
      FastFire("OnUser1 PL4.CP:SetOwner:3:0.0:1"); //Change this to instead enable a capture area, stop the payload and disable capture area while the CP is capturing.. Re enable it after 15s. Check if blue is pushing and not receding.
      return;
    }
    // Point -1 capture (this would be 75% capture from red's perspective)
    case 9: {
      FastFire("OnUser1 PL2.CP:SetOwner:2:0.0:1"); // see case 8
      return;
    }
    // Op codes for payload progression
    case 10: {
      PrintToServer("Got 10, PLL %i", PLL);
      switch(PLL){
        case 1:{
          PrintToChatAll("DEBUG: Checking if RECEDE: %b STREQUAL PLT RED: %b, PLB %i < PLPOS %i PLRL: %i...", RECEDE, StrEqual(PLT, "RED"), PLB, PLPOS[PLRL], PLRL);
          if(RECEDE || StrEqual(PLT, "RED")) PLRL--; //If it's receding back to mid or being pushed by red back to mid... This is occuring on red's side of the map.
          else PLRL++;//It's moving forward by blue to red.
          if(PLB < PLPOS[PLRL]){
            PLB+=5;
            FastFire("OnUser1 test:scorebluepoints::0.0:1");
          }
          return;
        }
        case 2:{
          PrintToChatAll("DEBUG: Checking if RECEDE: %b STREQUAL PLT BLU: %b, PLR %i < PLPOS %i PLRL: %i...", RECEDE, StrEqual(PLT, "BLU"), PLR, PLPOS[PLRL], PLRL);
          if(RECEDE || StrEqual(PLT, "BLU")) PLRL++; //If it's receding back to mid or being pushed by blu back to mid... this is occuring on blu's side of the map.
          else PLRL--; //It's moving forward by red to blue.
          if(PLR < PLPOS[PLRL]){
            PLR+=5;
            FastFire("OnUser1 test:scoreredpoints::0.0:1");
          }
          return;
        }
      }
//      PrintToChatAll("BLU %i% RED %i%, PLM: %b, PLL: %i, PLT: %s", PLB, PLR, PLM, PLL, PLT);
      return;
    }
    //Blu reached end of track (FF)
    case 11: {
        //FastFire("OnUser1 WinBlu:RoundWin::0.0:1"); //Change this to instead enable a capture area, stop the payload, and disable payload capture area while the CP is capturing... Victory after 15s (onCapTeam2)
    }
    //Red reached end of track (00)
    case 12:{
        //FastFire("OnUser1 WinRed:RoundWin::0.0:1"); //Change this, see case 11...
    }
    //Payload reached neutral point
    case 13:{
        PLT = "N/A";
        PLL = 0;
        PLM = false;
        RECEDE = false;
        PLRL = 20;
        FastFire("OnUser1 PL1.TrackTrain:Stop::0.0:1");
    }
    //Debug
    case 42:{
      PLR+=5;
      return;
    }
    case 69:{
      PLB +=5;
      return;
    }
  }
}

// Recede payload after 45 seconds
public Action BeginRecede(Handle timer, int side) {
  if (side > 0) FastFire(side == 1 ? "OnUser1 PL1.TrackTrain:StartBackward::0.0:1" : "OnUser1 PL1.TrackTrain:StartForward::0.0:1");
  RECEDE = true;
  return Plugin_Stop;
}

// Show payload state
public Action PayloadUpdateTimer(Handle timer){
  if(!isMatch) return Plugin_Stop;
  CCH[30].BLU = BLC ? "hud/cconflict/cc_blu0B" : "hud/cconflict/cc_blu0A";
  CCH[10].RED = REC ? "hud/cconflict/cc_red20" : "hud/cconflict/cc_red1F";
  char state[64];
  Format(state, sizeof(state), StrEqual(PLT, "BLU") ? CCH[PLRL].BLU : StrEqual(PLT, "RED") ? CCH[PLRL].RED :  RECEDE ? CCH[PLRL].NEUTRAL : CCH[PLRL].IDLE);
  ShowOverlayAll(state);
  PrintToServer(state);
  CreateTimer(0.5, PayloadUpdateTimer);
  return Plugin_Stop;
}

// Recede payload after 45 seconds
public Action BeginRecede(Handle timer, int side) {
  if (side > 0) FastFire(side == 1 ? "OnUser1 PL1.TrackTrain:StartBackward::0.0:1" : "OnUser1 PL1.TrackTrain:StartForward::0.0:1");
  RECEDE = true;
  return Plugin_Stop;
}

// Show payload state
public Action PayloadUpdateTimer(Handle timer){
  if(!isMatch) return Plugin_Stop;
  CCH[30].BLU = BLC ? "hud/cconflict/cc_blu0B" : "hud/cconflict/cc_blu0A";
  CCH[10].RED = REC ? "hud/cconflict/cc_red20" : "hud/cconflict/cc_red1F";
  char state[64];
  Format(state, sizeof(state), StrEqual(PLT, "BLU") ? CCH[PLRL].BLU : StrEqual(PLT, "RED") ? CCH[PLRL].RED :  RECEDE ? CCH[PLRL].NEUTRAL : CCH[PLRL].IDLE);
  ShowOverlayAll(state);
  PrintToServer(state);
  CreateTimer(0.5, PayloadUpdateTimer);
  return Plugin_Stop;
}

//Finish adding idle huds
//Add real spawn points and collisions

//Fix payload not going to final blue point...

//Finish adding map collisions, use Invisible for areas that are blocked by mesh...
//Finish fixing up clipping flag models and investigate clipping fence at spawns

//Music system and mini status hud
//Add back the %score% variable to hud. Make sure to use the actual score entry from base playerdestruction...
//Add capture point logic, each point taking 30 seconds to cap
//Add map easter eggs and other hazards...

//Brainstorming...
/* Crystal: MOVING (Team) //or Crystal: RECEDING (in 45s)?
* Song: FFXIV - Locus (XX:XX/XX:XX)
*/