/* Todo:
 * countdown
 */
#include <amxmodx>
#include <amxmisc>

#define MAX_NOMINATIONS 4
#define MAX_MAPS_IN_VOTE 6
#define ID_EXTEND MAX_MAPS_IN_VOTE

#define TASK_VOTE 0

new Array:g_mapName;
new g_mapCount;

new g_voting;
new Float:g_lastVoteTime;

new g_mapChoices[MAX_MAPS_IN_VOTE + 1];
new g_mapVoteCount[MAX_MAPS_IN_VOTE + 1];
new g_numChoices;

new g_countDown;
new g_numPlayersVoted;
new g_maxClients;

new const g_prefix[] = "^1[^4f0.gg^1]";

new g_nextmap[32];

new g_nomination[33] = {-1, ...};
new bool:g_voted[33];
new bool:g_rocked[33];

new CvarExtendMax, CvarExtendStep;
new CvarRtvWait, CvarRtvRatio;
new CvarTimeLimit;

public plugin_init() {
  register_plugin("rtv", "0.1", "Flummi");
  register_menucmd(register_menuid("MapVoteMenu"), 1023, "CountMapVote");
  register_clcmd("say", "CmdSay");
  CvarExtendStep = register_cvar("mapvote_extend_step", "15");
  CvarExtendMax = register_cvar("mapvote_extend_max", "90");
  CvarRtvWait = register_cvar("mapvote_rtv_wait", "15");
  CvarRtvRatio = register_cvar("mapvote_rtv_ratio", "0.7");
  CvarTimeLimit = get_cvar_pointer("mp_timelimit");
  g_maxClients = get_maxplayers() - 1;
  g_mapName = ArrayCreate(32);
  g_lastVoteTime = get_gametime() - 1000;
  loadConfigs();
  set_task(10.0, "CheckEndOfMap", 1337, _, _, "b");
  pause("ac", "mapchooser.amxx");
}

public client_disconnected(id) {
  g_rocked[id] = false;
  g_voted[id] = false;
  g_nomination[id] = -1;
}

public CmdSay(id) {
  new arg[72];
  read_args(arg, charsmax(arg));
  remove_quotes(arg);
  
  new arg1[32], arg2[32];
  parse(arg, arg1, charsmax(arg1), arg2, charsmax(arg2));
  
  new mapId = arrayFindString(g_mapName, arg1);
  if(mapId != -1) {
    nominateMap(id, mapId);
    return PLUGIN_HANDLED;
  }
  else if(equal(arg1, "/nom") || equal(arg1, "/nominate")) {
    ShowNominationMenu(id, arg2);
    return PLUGIN_HANDLED;
  }
  else if(equal(arg1, "rtv") || equal(arg1, "rockthevote")) {
    rockTheVote(id);
    return PLUGIN_HANDLED;
  }
  return PLUGIN_CONTINUE;
}

public ShowNominationMenu(id, const match[]) {
  new buffer[64];
  formatex(buffer, charsmax(buffer), "Nomination \w(search: %s)\y", match);
  new menu = menu_create(buffer, "HandleMenuNomination");
  for(new mapId = 0; mapId < g_mapCount; mapId++) {
    static mapName[32];
    ArrayGetString(g_mapName, mapId, mapName, charsmax(mapName));
    if(contain(mapName, match) > -1 || !match[0]) {
      static info[2];
      formatex(buffer, charsmax(buffer), mapName);
      if(isMapNominated(mapId))
        add(buffer, charsmax(buffer), "\r (nominated)");
      info[0] = mapId;
      menu_additem(menu, buffer, info);
    }
  }
  menu_setprop(menu, MPROP_NUMBER_COLOR, "\y");
  menu_display(id, menu);
}

public HandleMenuNomination(id, menu, item) {
  if(item == MENU_EXIT) {
    menu_destroy(menu);
    return;
  }
  new dummy, info[2], tmpname[32];
  menu_item_getinfo(menu, item, dummy, info, charsmax(info), tmpname, charsmax(tmpname), dummy);
  new mapId = info[0]
  if(!nominateMap(id, mapId))
    client_print_color(id, print_chat, "%s Map ^"^4%s^1^" has already been nominated.", g_prefix, tmpname);
  menu_destroy(menu);
}

public CheckEndOfMap() {
  if(g_voting)
    return;
  if(get_timeleft() < 130)
    MakeMapVote();
}

public MakeMapVote() {
  g_voting = 1;
  g_lastVoteTime = get_gametime();
  g_numChoices = 0;
  new nomination[32], numNominations = 0;
  for(new i = 1; i <= g_maxClients; i++)
    if(g_nomination[i] != -1)
      nomination[numNominations++] = g_nomination[i];
  new maxChoices = min(g_mapCount, MAX_MAPS_IN_VOTE);
  new maxNomination = min(numNominations, MAX_NOMINATIONS);
  while(g_numChoices < maxChoices) {
    static map;
    if(g_numChoices < maxNomination) {
      new i = random(numNominations);
      while(isMapInMenu((map = nomination[i])))
        if(++i >= numNominations)
          i = 0;
    }
    else { // Add random maps
      map = random(g_mapCount);
      while(isMapInMenu(map))
        if(++map >= g_mapCount)
          map = 0;
    }
    g_mapChoices[g_numChoices++] = map;
  }

  g_mapChoices[ID_EXTEND] = (get_pcvar_float(CvarTimeLimit) < get_pcvar_float(CvarExtendMax));

  arrayset(g_mapVoteCount, 0, sizeof g_mapVoteCount);
  arrayset(g_voted, false, sizeof g_voted);
  remove_task(TASK_VOTE);
  set_task(1.0, "ShowMapVote", TASK_VOTE, _, _, "a", 7);
  set_task(6.0, "CheckMapVotes", TASK_VOTE);
  g_countDown = 5;
  g_numPlayersVoted = 0;
}

public ShowMapVote() {
  new numPlayers = countRealPlayers();
  static menu[512], len, keys;
  for(new player = 1; player <= g_maxClients; player++) {
    if(!is_user_connected(player) || is_user_bot(player))
      continue;
    if(g_voting == 1)
      len = formatex(menu, charsmax(menu), "\yvoting will start soon...^n^n");
    else if(g_voting == 2)
      len = formatex(menu, charsmax(menu), "\yvote for the next map^n^n");
    else
      len = formatex(menu, charsmax(menu), "\yvoting results^n^n");
    
    keys = 0;
    len += formatex(menu[len], 511-len, "\r1. \wCheckpoint^n");
    keys |= (1 << 0);
    len += formatex(menu[len], 511-len, "\r2. \wGocheck^n^n");
    keys |= (1 << 1);
    
    for(new i = 2; i < (g_numChoices + 2); i++) {
      new map = g_mapChoices[i-2];
      if(g_voting != 2)
        len += formatex(menu[len], 511-len, "\y%d. \w%a", i+1, ArrayGetStringHandle(g_mapName, map));
      else
        len += formatex(menu[len], 511-len, "\r%d. \w%a", i+1, ArrayGetStringHandle(g_mapName, map));
      if(g_voting == 3 || (g_voted[player] && g_mapVoteCount[i - 2]))
        len += formatex(menu[len], 511-len, " \y(%d%%)", (g_mapVoteCount[i - 2] * 100 / (numPlayers | 1)));
      menu[len++] = '^n';
      keys |= (1 << i);
    }
    
    new i = ID_EXTEND;
    if(g_voting != 1 && g_mapChoices[i]) {
      static mapName[32];
      get_mapname(mapName, charsmax(mapName));
      len += formatex(menu[len], 511-len, "^n%s9. \wextend %s", g_voting == 2 ? "\r" : "\y", mapName);
      if(g_voting == 3 || (g_voted[player] && g_mapVoteCount[i]))
        len += formatex(menu[len], 511-len, " \y(%d%%)", (g_mapVoteCount[i] * 100 / (numPlayers | 1)));
      keys |= MENU_KEY_9;
    }
    if(g_voting != 3 && g_countDown <= 10)
      len += formatex(menu[len], 511-len, "^n^n\wremaining time: %d", max(0, g_countDown));
    show_menu(player, keys, menu, 5, "MapVoteMenu");
  }
  g_countDown--;
}

public CountMapVote(id, key) {
  if(key == 0) {
    client_cmd(id, "say /cp");
    ShowMapVote();
    return;
  }
  else if(key == 1) {
    client_cmd(id, "say /gc");
    ShowMapVote();
    return;
  }

  if(g_voting == 2 && !g_voted[id]) {
    new username[32];
    get_user_name(id, username, charsmax(username));
    new tmpmap[32];
    ArrayGetString(g_mapName, g_mapChoices[key - 2], tmpmap, charsmax(tmpmap));

    if((key + 1) == 9) {
      g_mapVoteCount[ID_EXTEND]++;
      g_numPlayersVoted++;
      g_voted[id] = true;
      client_print_color(0, print_chat, "%s %s chose map extending", g_prefix, username);
    }
    else {
      g_mapVoteCount[key - 2]++;
      g_numPlayersVoted++;
      g_voted[id] = true;
      client_print_color(0, print_chat, "%s %s chose %s", g_prefix, username, tmpmap);
    }
  }
  ShowMapVote();
}

public CheckMapVotes() {
  if(g_voting == 1) {
    g_voting = 2;
    client_cmd(0, "spk Gman/Gman_Choose%d", random_num(1, 2));
    remove_task(TASK_VOTE);
    set_task(1.0, "ShowMapVote", TASK_VOTE, _, _, "a", 15);
    set_task(16.0, "CheckMapVotes", TASK_VOTE);
    g_countDown = 15;
    return;
  }
  
  new numPlayers = countRealPlayers();
  g_voting = 3;
  ShowMapVote();

  new best;
  for(new i = 0; i <= MAX_MAPS_IN_VOTE; i++)
    if(g_mapVoteCount[i] > g_mapVoteCount[best])
      best = i;

  new sameVotes[MAX_MAPS_IN_VOTE + 1], num = 0;
  for(new i = 0; i <= MAX_MAPS_IN_VOTE; i++)
    if(g_mapVoteCount[i] > 0 && g_mapVoteCount[i] == g_mapVoteCount[best])
      sameVotes[num++] = i;

  if(num > 1) {
    best = sameVotes[random(num)];
    client_print_color(0, print_chat, "%s since there are %d results that are the same, one of them is randomly selected.", g_prefix, num);
  }
  
  new mapName[32];

  if(best == ID_EXTEND || (g_numPlayersVoted == 0 && numPlayers != 0)) { // extend map
    g_voting = 0;
    get_mapname(mapName, charsmax(mapName));
    
    if(get_timeleft() < 130)
      set_cvar_float("mp_timelimit", get_cvar_float("mp_timelimit") + get_pcvar_float(CvarExtendStep));
    
    client_print_color(0, print_chat, "%s voting is over. %s will be extended by %d minutes.", g_prefix, mapName, get_pcvar_num(CvarExtendStep));
  }
  else {
    ArrayGetString(g_mapName, g_mapChoices[best], g_nextmap, charsmax(g_nextmap));
    server_print("(after vote) g_nextmap: %s", g_nextmap);
    set_cvar_string("amx_nextmap", g_nextmap);
    set_task(10.0, "changeLevel", TASK_VOTE);
    message_begin(MSG_ALL, SVC_INTERMISSION);
    message_end();
    client_print_color(0, print_chat, "%s voting is over. the next map will be %s.", g_prefix, g_nextmap);
  }
}

public changeLevel() {
  server_print("(changeLevel) g_nextmap: %s", g_nextmap);
  client_print_color(0, print_chat, "%s changing map to %s...", g_prefix, g_nextmap);
  server_cmd("changelevel ^"%s^"", g_nextmap);
}

loadConfigs() {
  new currentMap[32];
  get_mapname(currentMap, charsmax(currentMap));
  
  new filePath[100];
  get_configsdir(filePath, charsmax(filePath));
  add(filePath, charsmax(filePath), "/maps.ini");
  
  new file = fopen(filePath, "r");
  if(file) {
    while(!feof(file)) {
      static data[64];
      fgets(file, data, charsmax(data));
      
      static mapName[32];
      parse(data, mapName, charsmax(mapName));
      
      if(is_map_valid(mapName) && !equal(currentMap, mapName) && !isMapExists(mapName)) {
        ArrayPushString(g_mapName, mapName);
        g_mapCount++;
      }
    }
    
    fclose(file);
  }
  
  if(!g_mapCount)
    set_fail_state("Could not load any map.");
}

rockTheVote(id) {
  if(g_voting) {
    client_print_color(id, print_chat, "%s voting is in progress", g_prefix);
    return;
  }
  
  new Float:timeWait = g_lastVoteTime + get_pcvar_float(CvarRtvWait) - get_gametime();
  if(timeWait > 0.0) {
    client_print_color(id, print_chat, "%s next vote in (%.f seconds)", g_prefix, timeWait);
    return;
  }
  
  if(g_rocked[id]) {
    g_rocked[id] = false;
    client_print_color(id, print_chat, "%s you canceled the vote to change the map.", g_prefix);
    return;
  }
  
  g_rocked[id] = true;
  
  new maxVotes = floatround(get_pcvar_float(CvarRtvRatio) * countRealPlayers());
  new numVotes = countRocked();
  
  if(numVotes >= maxVotes) {
    arrayset(g_rocked, false, sizeof g_rocked);
    MakeMapVote();
    
    client_print_color(0, print_chat, "%s %n voted to change the map.", g_prefix, id);
  }
  else
    client_print_color(0, print_chat, "%s %n rocked the vote (%d players are needed).", g_prefix, id, maxVotes - numVotes);
}

bool:nominateMap(id, map) {
  if(isMapNominated(map))
    return false;
  g_nomination[id] = map;
  client_print_color(0, print_chat, "%s %n nominated the map %a.", g_prefix, id, ArrayGetStringHandle(g_mapName, map));
  return true;
}

stock bool:isMapInMenu(map) {
  for(new i = 0; i < g_numChoices; i++)
    if(g_mapChoices[i] == map)
      return true
  return false;
}

stock bool:isMapExists(const map[]) {
  return (arrayFindString(g_mapName, map) != -1);
}

stock arrayFindString(Array:which, const string[]) {
  new size = ArraySize(which);
  for(new i = 0; i < size; i++) {
    static buffer[128];
    ArrayGetString(g_mapName, i, buffer, charsmax(buffer));
    if(equal(string, buffer))
      return i;
  }
  return -1;
}

stock bool:isMapNominated(map) {
  for(new i = 1; i <= g_maxClients; i++)
    if(g_nomination[i] == map)
      return true;
  return false;
}

stock countRocked() {
  new count = 0;
  for(new i = 1; i <= g_maxClients; i++)
    if(g_rocked[i])
      count++;
  return count;
}

stock countRealPlayers() {
  new count = 0;
  for(new i = 1; i <= g_maxClients; i++)
    if(is_user_connected(i) && !is_user_bot(i))
      count++;
  return count;
}
