#include "director.h"

#include <boost/format.hpp>

#include "boxes.h"
#include "galaxy.h"
#include "logging.h"
#include "utils.h"

Director::Director() {
  BUGZ_LOG(warning) << "Director::Director()";

  // active = false;
  game = 0;  // not in a game
  galaxy.reset();

  // do everything proxy_deactivate does ...
  // proxy_deactivate();
  active = false;
  // reset everything back to good state
  talk_direct = true;
  show_client = true;
  count = 0;

  /*
  Setup StringFunc for SL_parser:
  Construct these once, rather then every single time we need them.
  */
  SF_cimline = [this](const std::string &s) { this->SL_cimline(s); };
  SF_sectorline = [this](const std::string &s) { this->SL_sectorline(s); };
  SF_portline = [this](const std::string &s) { this->SL_portline(s); };
  SF_warpline = [this](const std::string &s) { this->SL_warpline(s); };

  build_menu();
}

Director::~Director() { BUGZ_LOG(warning) << "Director::~Director()"; }

void Director::client_input(const std::string &input) {
  // If we're already active, don't try to activate.

  if (chain) {
    chain->client_input(input);
    return;
  }

  if (active) {
    if (input == "Q" || input == "q") proxy_deactivate();
    return;
  } else if (input == "\x1b" || input == "~") {
    std::string &prompt = current_prompt;
    BUGZ_LOG(trace) << "CI: ACTIVATE prompt shows: [" << prompt << "]";

    if (prompt == "Selection (? for menu): ") {
      to_client(
          "\n\rThere's not much we can do here.  Activate in-game at a "
          "Command prompt.\n\r");
      to_client(current_raw_prompt);
      return;
    }

    // easter-eggs:

    if (prompt == "Enter your choice: ") {
      to_client(
          "\n\r\x1b[1;36mI'd choose \x1b[1;37m`T`\x1b[1;36m, but "
          "that's how I was coded.\n\r");
      to_client(current_raw_prompt);
      return;
    }

    // easter-egg
    if (prompt == "[Pause]") {
      to_client(" \x1b[1;36mPAWS\x1b[0m\n\r");
      to_client(current_raw_prompt);
      return;
    }

    if (prompt == "Planet command (?=help) [D] ") {
      // future:  Activate at planet menu ?
      return;
    }
    //
    // The command prompt that we're looking for:
    //
    // "Command [TL=00:00:00]:[242] (?=Help)? : "
    // the time, and the sector number vary...

    if (startswith(prompt, "Command [")) {
      // if (prompt.substr(0, 9) == "Command [") {
      //  int len = prompt.length();
      if (endswith(prompt, "] (?=Help)? : ")) {
        // if (prompt.substr(len - 14) == "] (?=Help)? : ") {
        proxy_activate();
        return;
      }
    }
  }
  // Ok...
  if (talk_direct) to_server(input);

  /*
  if (emit_client_input)
    emit_client_input(line);
  */
}

void Director::server_line(const std::string &line,
                           const std::string &raw_line) {
  // check for if we entered game/left game

  if (line.find("TradeWars Game Server   ") != std::string::npos) {
    to_client("\rTradeWars Proxy v2++ READY (~ or ESC to activate)\n\r");
    /*
    There's a delay here when I save the game data.
    I've moved it futher down.  Hide it at a prompt, so it isn't so noticeable.
     */
    // reset "active game" -- we're at the TWGS main menu
  }

  if (line.find("Selection (? for menu): ") != std::string::npos) {
    char ch = line[line.length() - 1];
    if (ch >= 'A' && ch < 'Q') {
      if ((game) && (game != ch)) {
        galaxy.save();
      }
      game = ch;
      BUGZ_LOG(warning) << "GAME " << game << " activated!";
      // TODO:  Load game data
      galaxy.game = game;
      galaxy.username = username;
      galaxy.load();
    }
    // not needed (handled by above Game Server check).
    if (ch == 'Q') {
      if (game) {
        // TODO:  Save galaxy data
        galaxy.save();
      }
      game = 0;
      galaxy.reset();
    }
  }

  if (game) {
    // in-game parsing here.

    /*
      ____                             _     _
     / ___|  ___ _ ____   _____ _ __  | |   (_)_ __   ___
     \___ \ / _ \ '__\ \ / / _ \ '__| | |   | | '_ \ / _ \
      ___) |  __/ |   \ V /  __/ |    | |___| | | | |  __/
     |____/ \___|_|    \_/ \___|_|    |_____|_|_| |_|\___|

      ____                _
     |  _ \ __ _ _ __ ___(_)_ __   __ _
     | |_) / _` | '__/ __| | '_ \ / _` |
     |  __/ (_| | |  \__ \ | | | | (_| |
     |_|   \__,_|_|  |___/_|_| |_|\__, |
                                  |___/

   This is where all of the server lines are gleaned for all the
   information that we can get out of them.

    // When activating the computer
    SP: [Command [TL=00:00:00]:[926] (?=Help)? : ]
    Sector: 926
    CI: [c]
    SL: [Command [TL=00:00:00]:[926] (?=Help)? : C]
    SL: [<Computer>]
    SL: []
    SL: [<Computer activated>]
    SL: []
    SP: [Computer command [TL=00:00:00]:[926] (?=Help)? ]

    // deactivating the computer
    SL: [Computer command [TL=00:00:00]:[926] (?=Help)? Q]
    SL: []
    SL: [<Computer deactivated>]
    SL: []
    */

    if (startswith(line, " Items     Status  Trading % of max OnBoard"))
      SL_parser = SF_portline;
    if (startswith(line, "Sector  : ")) SL_parser = SF_sectorline;
    if (line == ": ") SL_parser = SF_cimline;
  }

  if (SL_parser) {
    SL_parser(line);
  }

  /*
  if (emit_server_line) {
    emit_server_line(line);
  }
   */
  if (chain) {
    chain->server_line(line, raw_line);
  }
}

void Director::server_prompt(const std::string &prompt,
                             const std::string &raw_prompt) {
  current_prompt = prompt;
  current_raw_prompt = raw_prompt;

  if (game) {
    if (prompt == "Selection (? for menu): ") {
      galaxy.save();
      game = 0;
      galaxy.reset();
    }

    // in-game parsing here.
    if (startswith(prompt, "Command [") && endswith(prompt, "] (?=Help)? : ")) {
      std::string sector_text;
      size_t before, after;
      before = prompt.find("]:[") + 3;
      after = prompt.find("] (?=Help)");
      sector_text = prompt.substr(before, after - before);
      current_sector = stoi(sector_text);
      BUGZ_LOG(fatal) << "Sector: " << sector_text;
    }
  }

  /*
  if (emit_server_prompt)
    emit_server_prompt(prompt);
   */
  if (chain) chain->server_prompt(prompt);
}

void Director::build_menu(void) {
  main_menu = std::make_shared<MenuDispatch>(*this);
  MenuDispatch *md = static_cast<MenuDispatch *>(&(*main_menu));
  md->menu_box_color = "\x1b[1;33;44m";
  md->menu_text_color = "\x1b[1;37;44m";
  md->menu_title = "Proxy Menu";
  md->menu_options_color = "\x1b[1;36;40m";

  md->menu_prompt =
      "\x1b[0;31;40m\xdb\xb2\xb1\xb0 \x1b[31;40mRED "
      "\x1b[32;40mGREEN\x1b[30;42m\xdb\xb2\xb1\xb0 \x1b[0m : ";
  md->lazy = true;
  md->menu = {{"C", "Configure"},
              {"D", "Display Report"},
              {"E", "Export Data/Save"},
              {"I", "Information"},
              {"P", "Port CIM"},
              {"W", "Warp CIM"},
              {"T", "Trading Report"},
              {"S", "Scripts"},
              {"X", "eXit"}};
  md->setNotify([this]() { this->menu_choice(); });

  cim = std::make_shared<CIMDispatch>(*this);
  cim->setNotify([this]() { this->cim_done(); });
}

void Director::proxy_activate(void) {
  active = true;   // yes, set keep-alive timer.
  to_server(" ");  // start keep-alive timer.

  // set other values we need
  talk_direct = false;
  show_client = false;
  /*
  Wait a minute .. this might be confusing.
  Shouldn't I send them the current prompt?
  Just in case we abort in the middle of something?!?
  */
  old_prompt = current_prompt;
  old_raw_prompt = current_raw_prompt;
  to_client("\x1b[0m\n\r");

  /*
  ╔══════════════════════════════╗
  ║    TradeWars Proxy Active    ║
  ╚══════════════════════════════╝
     -=>
  */

  Boxes box(30, 1, true);
  box.boxcolor = "\x1b[1;33;44m";
  box.textcolor = "\x1b[1;33;44m";
  to_client(box.top());
  std::string output = "    TradeWars Proxy \x1b[5mActive\x1b[0;1;33;44m    ";
  to_client(box.row(output));
  to_client(box.bottom());

  chain = main_menu;
  main_menu->activate();
  /*
    // Using InputDispatch  -- and see have_input
    std::shared_ptr<Dispatch> readline = std::make_shared<InputDispatch>(*this);
    chain = readline;
    InputDispatch *id = static_cast<InputDispatch *>(&(*readline));
    id->prompt = "\x1b[0m    \x1b[1;33;44m-=>\x1b[0m \x1b[1;37;44m";
    id->max_length = 15;
    id->setNotify([this]() { this->have_input(); });
    readline->activate();
  */
}

void Director::menu_choice(void) {
  MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*chain));
  if (md) {
    if (md->input.empty()) {
      to_client("Menu aborted.\n\r");
      proxy_deactivate();
      return;
    } else {
      switch (md->input[0]) {
        case 'C':  // configure
          break;
        case 'D':  // display report
          break;
        case 'E':  // Export Data/Save
          to_client("Saving...");
          galaxy.save();
          to_client("\rSaved....\n\r");
          break;
        case 'I':  // Information
          information();
          break;
        case 'P':  // Port CIM
          // Since we're adding/updating, we don't lose our
          // type 0 ports.  Type 9 stays at 9.
          chain = cim;
          to_server("^RQ");
          to_client("Port CIM Report\n\r");
          chain->activate();
          return;
          break;
        case 'W':  // Warp CIM
          chain = cim;
          to_server("^IQ");
          to_client("Warp CIM Report\n\r");
          chain->activate();
          return;
          break;
        case 'T':  // Trading Report
          break;
        case 'S':  // Scripts
          break;
        case 'X':  // Exit
          proxy_deactivate();
          return;
      }
      /*
      std::string text = str(
          boost::format("Back from Menu [%1%] was selected.\n\r") % md->input);
      to_client(text);
      */

      md->activate();
    }
  }
}

void Director::have_input(void) {
  ++count;
  InputDispatch *id = dynamic_cast<InputDispatch *>(&(*chain));
  if (id) {
    std::string output =
        str(boost::format("Your Input (%2%): [%1%]\n\r") % id->input % count);
    to_client("\x1b[0m");
    to_client(output);
  } else {
    proxy_deactivate();
    return;
  }
  if (count > 3) {
    proxy_deactivate();
  } else {
    chain->activate();
  }
}

void Director::cim_done(void) {
  BUGZ_LOG(info) << "CIM done";
  chain = main_menu;
  main_menu->activate();
}

void Director::information(void) {
  std::string output;
  to_client("I currently know the following:\n\r");
  output = str(
      boost::format("Ports: %1%, Sectors: %2%, Config: %3%, Meta: %4%\n\r") %
      galaxy.ports.size() % galaxy.warps.size() % galaxy.config.size() %
      galaxy.meta.size());
  to_client(output);
}

void Director::proxy_deactivate(void) {
  active = false;
  // reset everything back to good state
  talk_direct = true;
  show_client = true;
  chain.reset();
  to_client("\n\r");
  to_client(current_raw_prompt);
}

/*

Server Line Parsing Routines

 */

void Director::SL_cimline(const std::string &line) {
  if (line == ": ENDINTERROG") {
    SL_parser = nullptr;
    return;
  }
  if (line == ": ") {
    // do I need to do anything special here for this?
    // Maybe -- We would save special ports that don't show up
    // (StarDock/Special) before. We don't know (at this point) if this is warps
    // or ports.
    return;
  }
  if (line.empty()) {
    SL_parser = nullptr;
    return;
  }

  // parse cimline
  // size_t pos = line.find('%');
  // std::string work = line;

  // if (pos == line.npos) {
  if (in(line, "%")) {
    // portcim
    struct port p = parse_portcim(line);
    if (p.sector == 0)
      BUGZ_LOG(fatal) << "portcim:  FAIL [" << line << "]";
    else
      BUGZ_LOG(trace) << "portcim: " << p;
    galaxy.add_port(p);
  } else {
    // warpcim
    // BUGZ_LOG(fatal) << "warpcim: [" << line << "]";
    auto warps = split(line);
    sector_warps sw;
    for (auto const &w : warps) {
      if (sw.sector == 0) {
        sw.sector = stoi(w);
      } else {
        sw.add(stoi(w));
      }
    }
    BUGZ_LOG(trace) << "warpcim: " << sw;
    galaxy.add_warp(sw);
  }
}

void Director::SL_thiefline(const std::string &line) {
  size_t pos = line.find("Suddenly you're Busted!");
  bool busted = pos != line.npos;
  if (busted) {
    BUGZ_LOG(fatal) << "set bust";
    SL_parser = nullptr;
  } else {
    pos = line.find("(You realize the guards saw you last time!)");
    if (pos != line.npos) SL_parser = nullptr;
  }

  // Are those the two ways to exit from this state?
}
void Director::SL_sectorline(const std::string &line) {
  BUGZ_LOG(fatal) << "sectorline: [" << line << "]";
  if (line.empty()) {
    SL_parser = nullptr;
  } else {
    /*
    sectorline: [Sector  : 926 in The Federation.]
    sectorline: [Beacon  : FedSpace, FedLaw Enforced]
    sectorline: [Ports   : Stargate Alpha I, Class 9 (Special) (StarDock)]
    sectorline: [Traders : Civilian phil, w/ 30 ftrs,]
    sectorline: [           in Star Stomper (Sverdlov Merchant Cruiser)]
    sectorline: [Warps to Sector(s) :  70 - 441 - 575 - 600 - 629 - 711]
    sectorline: [Warps to Sector(s) :  70 - (475) - 569]
    */
    if (in(line, "Sector  :")) {
      current_sector = stoi(line.substr(10));
      BUGZ_LOG(warning) << "SECTOR: " << current_sector;
    }
    if (in(line, "Ports   :")) {
      std::string port_class;
      size_t pos = line.find(", Class ");
      if (pos != std::string::npos) {
        pos += 8;
        int class_ = stoi(line.substr(pos));
        BUGZ_LOG(fatal) << "PORT: " << class_;
        galaxy.add_port(current_sector, class_);
      }
    }
    if (in(line, "Warps to Sector(s) :")) {
      std::string temp = line.substr(20);
      replace(temp, " - ", " ");
      // unexplored sectors ()
      // Should I track these?
      replace(temp, "(", "");
      replace(temp, ")", "");
      sector_warps sw;
      auto warps = split(temp);
      sw.sector = current_sector;
      for (auto const &w : warps) {
        sw.add(stoi(w));
      }
      BUGZ_LOG(fatal) << "WARPS: " << sw;
      galaxy.add_warp(sw);
    }
  }
}

void Director::SL_portline(const std::string &line) {
  if (line.empty()) {
    SL_parser = nullptr;
    return;
  }
  /*
  SL: [ Items     Status  Trading % of max OnBoard]
  SL: [ -----     ------  ------- -------- -------]
  SL: [Fuel Ore   Buying    3000    100%       0]
  SL: [Organics   Buying    3000    100%       0]
  SL: [Equipment  Buying    3000    100%       0]
  SL: []
  SL: [Commerce report for: 03:51:56 PM Mon Oct 24, 2033     You can buy:]
  SL: [A  Cargo holds     :    650 credits / next hold                0]
  SL: [B  Fighters        :    233 credits per fighter               75]
  SL: [C  Shield Points   :    116 credits per point                100]
  SL: []
   */
  BUGZ_LOG(info) << "portline : " << line;
  if (in(line, "%")) {
    // size_t pos = line.find('%');
    // if (pos != line.npos) {
    // Ok, this is a valid portline
    std::string work = line;
    replace(work, "Fuel Ore", "Fuel");
    auto parts = split(work);
    BUGZ_LOG(fatal) << "portline split:";
    for (auto const p : parts) {
      BUGZ_LOG(fatal) << p;
    }
    // BUGZ_LOG(fatal) << "portline split : [" << parts << "]";
  }
}

void Director::SL_warpline(const std::string &line) {
  if (line.empty()) {
    SL_parser = nullptr;
    return;
  }

  // process warp line
  BUGZ_LOG(fatal) << "warpline: [" << line << "]";
}