#include "director.h"

#include <boost/format.hpp>
#include <cctype>

#include "ansicolor.h"
#include "boxes.h"
#include "galaxy.h"
#include "logging.h"
#include "scripts.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); };
  SF_infoline = [this](const std::string &s) { this->SL_infoline(s); };
  SF_densityline = [this](const std::string &s) { this->SL_densityline(s); };
  SF_computer_portline = [this](const std::string &s) {
    this->SL_computer_portline(s);
  };
  SF_planetline = [this](const std::string &s) { this->SL_planetline(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) << "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: ") {
      ANSIColor c1(COLOR::CYAN, ATTR::BOLD);
      ANSIColor c2(COLOR::WHITE, ATTR::BOLD);

      to_client(std::string("\n\r") + c1() + "I'd choose " + c2() + "`T`" +
                c1() + ", but that's how I was coded.\n\r");
      //          "\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]") {
      ANSIColor c1(COLOR::CYAN, ATTR::BOLD);
      to_client(std::string(" ") + c1() + "PAWS" + reset() + "\n\r");
      to_client(current_raw_prompt);
      return;
    }

    if (at_planet_prompt(prompt)) {
      // future:  If activated at planet menu, activate the planet upgrade
      // script!  (Maybe).  If I can figure out what planet it is/and where.
      to_client("\n\r\x1b[0mFUTURE:  Activate the planet upgrade script.\n\r");
      to_client(current_raw_prompt);
      return;
    }

    if (at_command_prompt(prompt)) {
      proxy_activate();
      return;
    }
  }
  // Ok...
  if (talk_direct) to_server(input);
}

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) {
    // Inject our proxy activation message
    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();

      // YAML loaded, set sensible default config values (if missing).
      if (!galaxy.config["display_lines"]) {
        galaxy.config["display_lines"] = 20;
      }

      galaxy.meta["help"]["display_lines"] =
          "Number of report lines to display";

      if (!galaxy.config["burnt_percent"]) {
        galaxy.config["burnt_percent"] = 40;
      }

      galaxy.meta["help"]["burnt_percent"] =
          "Don't display ports in report below this percent";
    }
    // 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 (line == "<Port>") {
      SL_parser = SF_portline;
    }

    if (line ==
            "                           Corporate Planet Scan                  "
            "            " ||
        line ==
            "                           Personal Planet Scan                   "
            "           ") {
      SL_parser = SF_planetline;
    }

    /*
    if (startswith(line, " Items     Status  Trading % of max OnBoard"))
      SL_parser = SF_portline;
    */
    if (endswith(line, "Relative Density Scan")) {
      galaxy.dscan.reset(current_sector);
      SL_parser = SF_densityline;
    }
    if (startswith(line, "Sector  : ")) SL_parser = SF_sectorline;
    if (line == ": ") SL_parser = SF_cimline;
    if (line == "<Info>") SL_parser = SF_infoline;
    if (startswith(line, "What sector is the port in? [")) {
      // Computer Port Report
      // SL: [What sector is the port in? [611] 4]
      size_t pos = line.rfind(' ');

      computer_port_sector = stoi(line.substr(pos));
      SL_parser = SF_computer_portline;
    }
  }

  if (SL_parser) {
    SL_parser(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)? : ")) {
    if (at_command_prompt(prompt)) {
      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(info) << "current_sector = " << current_sector;
    }
  }

  /*
  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));
  ANSIColor bcolor(COLOR::YELLOW, COLOR::BLUE, ATTR::BOLD);
  ANSIColor tcolor(COLOR::WHITE, COLOR::BLUE, ATTR::BOLD);
  ANSIColor mocolor(COLOR::CYAN, ATTR::BOLD);
  md->menu_box_color = bcolor();   // "\x1b[1;33;44m";
  md->menu_text_color = tcolor();  // "\x1b[1;37;44m";
  md->menu_title = "Proxy Menu";
  md->menu_options_color = mocolor();  // "\x1b[1;36;40m";

  ANSIColor by{1, 33};
  ANSIColor cyan{36};
  ANSIColor bg{1, 32};
  std::string prompt = by() + "M" + cyan() + "ain " + by() + "P" + cyan() +
                       "roxy " + bg() + "=>" + reset() + " ";

  md->menu_prompt = prompt;  // "Main Proxy => ";

  // "\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 (same as D)"},
              {"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());

  if (galaxy.meta["port_CIM"]) {
    time_t last_port_cim_report = galaxy.meta["port_CIM"].as<int>();
    int seconds_ago = time_t_now() - last_port_cim_report;
    int minutes_ago = seconds_ago / 60;
    BUGZ_LOG(fatal) << "port_CIM was " << minutes_ago << " minutes ago.";

    if (minutes_ago >= 60 ) {
      float hours_ago = minutes_ago / 60.0;
      std::string message = str(boost::format("Warning: Last Port CIM Refresh was %1$.2f% hours ago.\n\r") % hours_ago);
      to_client(message);
    }
  } else {
    BUGZ_LOG(fatal) << "no meta port_CIM value seen.";
    for( auto const &d : galaxy.meta) {
      BUGZ_LOG(fatal) << d.first << " : " << d.second;
    }
  }

  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
          config_edit();
          return;
          break;
        case 'D':
        case 'T':  // display trading report
        {
          auto pptv = galaxy.find_best_trades();
          std::string output;
          galaxy.sort_port_pair_type(pptv);

          int max_display = 20;
          if (galaxy.config["display_lines"])
            max_display = galaxy.config["display_lines"].as<int>();
          else
            galaxy.config["display_lines"] = max_display;

          if ((max_display <= 0) || (max_display > 255)) {
            max_display = 255;
            galaxy.config["display_lines"] = 255;
          }
          const int per_line = 5;
          int count = 0;
          int line = 0;
          std::string display_line;

          ANSIColor by{1, 33};  // yellow
          ANSIColor cyan{36};   // cyan
          ANSIColor bg{1, 32};  // bright green
          ANSIColor bb{1, 34};  // bright blue

          for (auto const &ppt : pptv) {
            output =
                str(boost::format("%1%%2$5d%3%:%4%%5$-5d%3%(%6%%7$d%3%) ") %
                    by() % ppt.s1 % cyan() % bg() % ppt.s2 % bb() % ppt.type);
            display_line.append(output);

            ++count;
            if (count == per_line) {
              count = 0;
              display_line.append("\n\r");
              to_client(display_line);
              display_line.clear();
              ++line;
            }

            if (line == max_display) break;
          }

          if (count != 0) {
            display_line.append("\n\r");
            to_client(display_line);
            display_line.clear();
          }

          // We got < 5 lines, and max_display is > 5.  Offer suggestion:
          if ((line < 5) && (max_display > 5)) {
            // suggestion:
            to_client(
                "HINT: For more lines, try reducing the burnt_percent?\n\r");
          }
        } 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.
        galaxy.meta["port_CIM"] = (int)time_t_now();
        chain = cim;
        to_server("^RQ");
        {
          std::string text = str(boost::format("Port CIM Report (%1%)\n\r") %
                                 galaxy.ports.size());
          // to_client("Port CIM Report\n\r");
          to_client(text);
        }
        chain->activate();
        return;
        break;
      case 'W': // Warp CIM
        chain = cim;
        to_server("^IQ");
        {
          std::string text = str(boost::format("Warp CIM Report (%1%)\n\r") %
                                 galaxy.warps.size());
          // to_client("Warp CIM Report\n\r");
          to_client(text);
        }
        chain->activate();
        return;
        break;
      // case 'T':  // Trading Report
      //  break;
      case 'S': // Scripts
      {
        init_scripts_menu();
        chain = scripts_menu;
        chain->activate();
        return;
      } 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();
    }
  }
}

MenuDispatch *Director::init_scripts_menu(void) {
  MenuDispatch *md;
  if (scripts_menu) {
    md = dynamic_cast<MenuDispatch *>(&(*scripts_menu));
    return md;
  } else {
    scripts_menu = std::make_shared<MenuDispatch>(*this);
    md = static_cast<MenuDispatch *>(&(*scripts_menu));
    md->menu_box_color = "\x1b[0;32;40m";
    md->menu_text_color = "\x1b[1;32;40m";
    md->menu_title = "Scripts Menu";
    md->menu_options_color = "\x1b[1;32;40m";
    md->lazy = false;

    ANSIColor by{1, 33};
    ANSIColor cyan{36};
    ANSIColor bg{1, 32};
    std::string prompt = by() + "S" + cyan() + "cript " + by() + "M" + cyan() +
                         "enu " + bg() + "=>" + reset() + " ";

    md->menu_prompt = prompt;  // " SCRIPT : ";
    md->menu = {{"!", "Terror"},
                {"T", "Trade"},
                {"S", "Safe Move"},
                {"C", "Closest Trade"},
                {"N", "Nearest Unexplored"},
                {"V", "Voyager Explorer"},
                {"U", "Upgrade Planet Pants"},
                {"X", "Exit Scripts"}};
    md->setNotify([this]() { this->scripts_done(); });
    return md;
  }
}

void Director::scripts_done(void) {
  // Was script selected?  If so, run it!
  // otherwise, back to the menu we go...
  MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*scripts_menu));
  if (md) {
    if (md->input.empty()) {
      to_client("Scripts aborted.\n\r");
      scripts_menu.reset();
      proxy_deactivate();
      return;
    } else {
      switch (md->input[0]) {
        case 'T':  // Trade
        {
          script = std::make_shared<TraderDispatch>(*this);
          TraderDispatch *ts = static_cast<TraderDispatch *>(&((*script)));
          ts->setNotify([this]() { this->proxy_deactivate(); });

          // Locate best trades
          auto found = galaxy.find_trades(current_sector, false);
          if (found.empty()) {
            to_client(
                "No Trades found.  Port burnt (CONFIG: lower burnt_percent?) "
                "or no ports around.\n\r");
            proxy_deactivate();
            return;
          }
          // sort first?
          galaxy.sort_port_pair_type(found);

          BUGZ_LOG(fatal) << "Found " << found.size() << " possible trade(s).";
          BUGZ_LOG(fatal) << "Best: " << found[0].s1 << "," << found[0].s2
                          << " : " << found[0].type;

          // Set parameters
          ts->port[0] = found[0].s1;
          ts->port[1] = found[0].s2;
          ts->type = found[0].type;
          ts->trades = found[0].trades;
          chain = script;
          chain->activate();
          return;
        } break;
        case 'S': {
          script = std::make_shared<MoveDispatch>(*this);
          MoveDispatch *md = static_cast<MoveDispatch *>(&((*script)));
          md->setNotify([this]() { this->proxy_deactivate(); });
          md->move_to = 1;
          chain = script;
          chain->activate();
          return;
        } break;
        case '!': {
          script = std::make_shared<ScriptTerror>(*this);
          ScriptTerror *st = static_cast<ScriptTerror *>(&(*script));
          st->setNotify([this]() {
            script.reset();
            this->proxy_deactivate();
          });
          chain = script;
          chain->activate();
          return;
        } break;
        // }
        case 'C': {
          auto best = galaxy.find_closest(current_sector);
          if (best.type != 0) {
            std::string text =
                str(boost::format("Best/Closest: %1% with %2% & %3%\n\r") %
                    best.type % best.s1 % best.s2);
            to_client(text);
          } else {
            to_client("I don't see any best trades.\n\r");
          }
        } break;
        case 'N': {
          sector_type s = galaxy.find_nearest_unexplored(current_sector);
          if (s != 0) {
            std::string text = str(boost::format("Sector: %1%\n\r") % s);
            to_client(text);
          } else {
            to_client("I don't see any unexplored.\n\r");
          }
        } break;
        case 'V': {
          script = std::make_shared<ScriptVoyager>(*this);
          ScriptVoyager *sv = static_cast<ScriptVoyager *>(&(*script));
          sv->setNotify([this]() {
            script.reset();
            this->proxy_deactivate();
          });
          chain = script;
          chain->activate();
          return;
        } break;
        case 'Q':
          chain = main_menu;
          main_menu->activate();
          return;
          break;
      }
    }
  }

  proxy_activate();

  // And to end scripts, we do .. what exactly?
  // DEBUG:  Ok, why does everything work OK if I reset the scripts_menu
  // here?? probably do want to destroy scripts here.  ;)
  // scripts_menu.reset();
  // proxy_deactivate();
}

/**
 * @brief Setup Config Input
 *
 * @return DispatchInput*
 */
InputDispatch *Director::init_config_input(void) {
  InputDispatch *id;
  if (config_input) {
    // Yes, it has been setup before.
    id = dynamic_cast<InputDispatch *>(&(*config_input));

    ANSIColor by{1, 33};
    ANSIColor cyan{36};
    ANSIColor bg{1, 32};
    std::string prompt =
        by() + "C" + cyan() + "onfig " + bg() + "=>" + reset() + " ";
    id->prompt = prompt;  // "Config => ";
    id->numeric = true;
    id->max_length = 3;
    config_item.clear();
    return id;
  } else {
    // set it up
    config_input = std::make_shared<InputDispatch>(*this);
    id = static_cast<InputDispatch *>(&(*config_input));
    ANSIColor by{1, 33};
    ANSIColor cyan{36};
    ANSIColor bg{1, 32};
    std::string prompt =
        by() + "C" + cyan() + "onfig " + bg() + "=>" + reset() + " ";
    id->prompt = prompt;  // "Config => ";
    id->numeric = true;
    id->max_length = 3;
    id->setNotify([this]() { this->config_have_input(); });
    config_item.clear();
    return id;
  }
}

void Director::config_edit(void) {
  // display current config
  std::string menu_box_color = "\x1b[1;33;44m";
  std::string menu_text_color = "\x1b[1;37;44m";
  auto abox = Boxes::alert("   Configuration:   ", menu_box_color,
                           menu_text_color, 20, 1, true);
  for (auto line : abox) {
    to_client(line);
  }
  // to_client("Configuration:\n\r");
  int item = 1;
  ANSIColor number(COLOR::CYAN);
  ANSIColor key(COLOR::GREEN, ATTR::BOLD);
  ANSIColor value(COLOR::BLUE, ATTR::BOLD);

  for (auto const &cfg : galaxy.config) {
    std::string output =
        str(boost::format("%1%%2$2d %3%%4$20s: %5%%6$s%7%\n\r") % number() %
            item % key() % cfg.first % value() % cfg.second % reset());
    to_client(output);
    ++item;
  }
  std::string message =
      number() + "Enter number to edit, " + key() + "blank to exit.\n\r";
  // to_client("Enter number to edit, blank to exit.\n\r");
  to_client(message);

  // setup call to config_input:
  InputDispatch *id = init_config_input();
  chain = config_input;
  id->activate();

  // to return to the menu:
  // MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*chain));
  // md->activate();
}

void Director::config_have_input(void) {
  InputDispatch *id = dynamic_cast<InputDispatch *>(&(*config_input));

  if (config_item.empty()) {
    // This is a config menu selection
    if (id->input.empty()) {
      // We're done here.  Return to menu.
      chain = main_menu;
      MenuDispatch *md = dynamic_cast<MenuDispatch *>(&(*chain));
      md->activate();
      // destroy the input?  yes.
      config_input.reset();
      return;
    } else {
      int item = sstoi(id->input);

      if ((item < 1) || (item > (int)galaxy.config.size())) {
        // selection out of range - redisplay config menu
        to_client("What?  I didn't see that item.\n\r");
        config_edit();
        return;
      } else {
        int pos = 1;
        const YAML::Node &config = galaxy.config;
        for (auto const &c : config) {
          if (pos == item) {
            // got it!
            ANSIColor key(COLOR::GREEN, ATTR::BOLD);
            ANSIColor value(COLOR::BLUE, ATTR::BOLD);

            config_item = c.first.as<std::string>();
            std::string output =
                str(boost::format("%1%%2% : %3%%4%\n\r") % key() % config_item %
                    value() % galaxy.meta["help"][config_item]);
            to_client(output);
            id->max_length = 30;
            id->numeric = false;

            ANSIColor by{1, 33};
            ANSIColor cyan{36};
            ANSIColor bg{1, 32};
            std::string prompt =
                by() + "C" + cyan() + "hange to " + bg() + "=>" + reset() + " ";
            id->prompt = prompt;
            id->activate();
            return;
          };
          ++pos;
        }
        to_client("What?  I didn't find that item?\n\r");
        config_edit();
        return;
      }
    }
  } else {
    // This is a config item edit
    if (id->input.empty()) {
      to_client("No change.\n\r");
      config_edit();
      return;
    } else {
      BUGZ_LOG(fatal) << "Config EDIT: " << config_item << " = " << id->input;
      galaxy.config[config_item] = id->input;
      config_edit();
      return;
    }
  }
}

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]

    What can we get from Traders : line?  Can we get if they are hostile
    to us?  We need to respond to "is powering up weapons" ... We can
    react faster then a person can!
    "phil is powering up weapons systems!"

    Also the auto-attack ones Ferrengi -- we need to auto-respond Retreat.

    */
    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;
      // what if there is only one warp?
      for (auto const &w : warps) {
        sw.add(stoi(w));
      }
      BUGZ_LOG(fatal) << "WARPS: " << sw;
      galaxy.add_warp(sw);
    }
  }
}

void Director::SL_densityline(const std::string &line) {
  BUGZ_LOG(fatal) << "densityline: [" << line << "]";
  if (line.empty()) {
    SL_parser = nullptr;
    return;
  }

  /*
  // Ensure this really is a density scan and not something else
  if (!in(line, "Sector") || !in(line, "Warps") || !in(line, "NavHaz") ||
      !in(line, "Anom")) {
    BUGZ_LOG(fatal) << "densityline: Invalid line.";
    SL_parser = nullptr;
    return;
  }
  if (not galaxy.meta["density"]) {
    galaxy.meta["density"] = YAML::Node();
  }
  */

  /*
   0         1   2                3  4     5 6    7      8     9     10   11 12
  "Sector    55  ==>              0  Warps : 4    NavHaz :     0%    Anom : No"
  "Sector ( 223) ==>              0  Warps : 3    NavHaz :     0%    Anom : No"
  */
  if (in(line, "==>")) {
    std::string work = line;
    replace(work, ":", "");
    bool known = !in(work, "(");
    replace(work, "(", "");
    replace(work, ")", "");
    replace(work, "%", "");
    auto dense = split(work);
    // Parse our data

    sector_type sector = std::stoi(dense.at(1));
    uint16_t density = std::stoi(dense.at(3));
    uint16_t warps = std::stoi(dense.at(5));
    uint16_t navhaz = std::stoi(dense.at(7));
    bool anom = in(dense.at(9), "Yes");

    struct density d = {sector, density, warps, navhaz, anom, known};
    galaxy.dscan.add_scan(d);

    // Commit data
    BUGZ_LOG(warning) << "densityline: {sector=" << sector
                      << " density=" << density << " warps=" << warps
                      << " navhaz=" << navhaz << " anom=" << anom
                      << " known=" << known << "}";
    /*
    if (galaxy.meta["density"][sector]) {
      galaxy.meta["density"][sector] = YAML::Node();
    }
    */
    galaxy.meta["density"][sector]["density"] = density;
    galaxy.meta["density"][sector]["warps"] = warps;
    galaxy.meta["density"][sector]["navhaz"] = navhaz;
    galaxy.meta["density"][sector]["anom"] = anom;
    galaxy.meta["density"][sector]["known"] = known;
    // Add a check to see if density is greater than 500
    // Add datetime stamp
  }
}

void Director::SL_portline(const std::string &line) {
  /*
  We take blank lines because we start at <Port>.
  Otherwise, we trigger on computer port requests.

  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);

    if (parts[0] == "Items")
      return;

    char c = tolower(parts[0][0]);
    int pos;
    char foe[4] = "foe";

    for (pos = 0; pos < 3; ++pos) {
      if (c == foe[pos])
        break;
    }
    int amount = stoi(parts[2]);
    int percent = stoi(parts[3]);

    // update port
    auto port = galaxy.ports.find(current_sector);
    if (port != galaxy.ports.end()) {
      port->second.amount[pos] = amount;
      port->second.percent[pos] = percent;
    }

    /*
    BUGZ_LOG(fatal) << "portline split:";
    for (auto const p : parts) {
      BUGZ_LOG(fatal) << p;
    }
    */

    // Here's the end:
    if (parts[0] == "Equipment")
      SL_parser = nullptr;

    // 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 << "]";
}

void Director::SL_infoline(const std::string &line) {
  static int state;

  if (line == "<Info>") {
    state = 0;
    galaxy.meta["info"] = YAML::Node();
  }
  if (line.empty()) {
    ++state;
    if (state == 2) {
      SL_parser = nullptr;

      // clear out the existing ship data
      galaxy.meta["ship"] = YAML::Node();
      // process the parsed information in meta["info"]
      if (galaxy.meta["info"]["Total Holds"]) {
        std::string work = galaxy.meta["info"]["Total Holds"].as<std::string>();
        replace(work, "Fuel Ore", "Fuel");
        auto parts = split(work, " - ");
        int total_holds = stoi(parts[0]);
        BUGZ_LOG(fatal) << "total holds: " << total_holds;
        auto contents = split(parts[1]);

        for (auto const &hold : contents) {
          auto hold_amount = split(hold, "=");
          BUGZ_LOG(fatal) << hold_amount[0] << " with " << hold_amount[1];
          std::string key = hold_amount[0];
          str_tolower(key);
          // equipment = e
          // organics = o
          // fuel = f
          // colonists = c
          // empty = empty
          if (key != "empty") {
            key = key[0];
          }
          galaxy.meta["ship"]["holds"][key] = stoi(hold_amount[1]);
        }
        galaxy.meta["ship"]["holds"]["total"] = total_holds;
      }

      if (galaxy.meta["info"]["Turns to Warp"]) {
        int warp_turns = galaxy.meta["info"]["Turns to Warp"].as<int>();
        BUGZ_LOG(fatal) << "Turns to Warp: " << warp_turns;
        galaxy.meta["ship"]["warp_turns"] = warp_turns;
      }

      if (galaxy.meta["info"]["LongRange Scan"]) {
        std::string scanner_text =
            galaxy.meta["info"]["LongRange Scan"].as<std::string>();
        char scanner = scanner_text[0];
        BUGZ_LOG(fatal) << "Scanner: " << scanner;
        galaxy.meta["ship"]["scanner"] = scanner;
      }

      // turns isn't ship specific
      if (galaxy.meta["info"]["Turns left"]) {
        // OR this could be "Unlimited" !!!
        std::string text = galaxy.meta["info"]["Turns left"].as<std::string>();
        if (text == "Unlimited") {
          galaxy.meta["turns"] = -1;
        } else {
          int turns =
              stoi(text); // galaxy.meta["info"]["Turns left"].as<int>();
          BUGZ_LOG(fatal) << "Turns left: " << turns;
          galaxy.meta["turns"] = turns;
        }
      }

      if (galaxy.meta["info"]["Current Sector"]) {
        int sector = galaxy.meta["info"]["Current Sector"].as<int>();
        BUGZ_LOG(fatal) << "Sector: " << sector;
        // it should already be sector ...
        current_sector = sector;
      }

      if (galaxy.meta["info"]["Credits"]) {
        std::string credit_text =
            galaxy.meta["info"]["Credits"].as<std::string>();
        replace(credit_text, ",", "");
        int credits = stoi(credit_text);
        galaxy.meta["credits"] = credits;
        BUGZ_LOG(fatal) << "Credits: " << credits;
      }
    }
    return;
  }

  // info to parse:
  size_t pos = line.find(" : ");
  if ((!endswith(line, " : ")) && (pos != line.npos)) {
    std::string key = line.substr(0, pos);
    // Ferrengi ships don't have date built
    std::string value = line.substr(pos + 3);
    trim(key);
    trim(value);
    galaxy.meta["info"][key] = value;
    BUGZ_LOG(fatal) << "Info: " << key << " : " << value;
  }
}

void Director::SL_computer_portline(const std::string &line) {
  if (startswith(line, "What sector is the port in?"))
    computer_port_done = false;

  if (line == "I have no information about a port in that sector.") {
    computer_port_sector = 0;
    SL_parser = nullptr;
    return;
  }

  if (!computer_port_done) {
    if (in(line, "Fuel Ore")) computer_port_done = true;
    if (in(line, "Cargo holds")) {
      // If I want to scan the class type 0 ports:
      // computer_port_done = true;
      // otherwise:
      SL_parser = nullptr;
      return;
    }
  }

  if (computer_port_done) {
    if (line.empty()) {
      SL_parser = nullptr;
      return;
    }

    // scan for items of interest
    // SL: [Fuel Ore   Buying     810    100%       0]
    // SL: [Organics   Buying     856     57%       0]
    // SL: [Equipment  Selling    922     44%       0]
    std::string work = line;
    replace(work, "Fuel Ore", "Fuel");
    replace(work, "%", "");
    auto parts = split(work);

    char c = tolower(parts[0][0]);
    int pos;
    char foe[4] = "foe";

    for (pos = 0; pos < 3; ++pos) {
      if (c == foe[pos]) break;
    }

    int amount = stoi(parts[2]);
    int percent = stoi(parts[3]);

    // update port
    auto port = galaxy.ports.find(computer_port_sector);
    if (port != galaxy.ports.end()) {
      BUGZ_LOG(info) << "COM PORT " << computer_port_sector << " " << c << " "
                     << amount << " " << percent;
      port->second.amount[pos] = amount;
      port->second.percent[pos] = percent;
    }
  }
}

void Director::SL_planetline(const std::string &line) {
  if (line == "Corporate command [TL=00:00:00]:[344] (?=Help)? Q" ||
      line == "Computer command [TL=00:00:00]:[344] (?=Help)? Q") {
    SL_parser = nullptr;
    return;
  }

  if (in(line, "Class ") && (in(line, "Level ") || (endswith(line, "No Citadel")))) {
    int level;
    char c;
    sector_type sector;
    int number;
    std::string name;
    std::string work = line;
    size_t pos = work.find('#');
    std::string temp = work.substr(0, pos);
    trim(temp);
    sector = sstoi(temp);
    work = work.substr(pos + 1);
    number = sstoi(work);
    pos = work.find(' ');
    work = work.substr(pos + 1);
    trim(work);
    if (endswith(work, "No Citadel")) {
      level = 0;
    } else {
      pos = work.rfind("Level ");
      temp = work.substr(pos + 6);
      level = sstoi(temp);
    }
    pos = work.rfind("Class ");
    temp = work.substr(pos + 6);
    c = temp[0];
    work = work.substr(0, pos);
    trim(work);
    name = work;
    BUGZ_LOG(warning) << (int)sector << " # " << number << " Class " << c
                      << " Level " << level << " name: [" << name << "]";
  }

  /*
  SL: [   344   #4    Enjoy the Silence        Class M, Earth Type Level 3] SL:
  [   ---   (4M)            1T   64   25   14T   693   277         2T 10M] SL: [
  344   #5    High There!              Class L, Mountainous          Level 2]
  SL: [   ---   (5M)            2T    0    6   25T   100   112         2T 6M]
  ...
  SL: [   ---   (9M)            3T   64   31   39T   793   389         4T ---]
  */
}