#include "dispatchers.h"

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

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

Dispatch::Dispatch(Director &d) : director{d} {};
Dispatch::~Dispatch(){};

void Dispatch::to_server(const std::string &send) { director.to_server(send); }
void Dispatch::to_client(const std::string &send) { director.to_client(send); }
const std::string &Dispatch::get_prompt(void) {
  return director.current_prompt;
}

void Dispatch::setNotify(notifyFunc nf) { notify_ = nf; }

void Dispatch::notify(void) {
  if (director.post) {
    director.post(notify_);
  }
}

void Dispatch::chain_client_input(const std::string &input) {
  if (chain) {
    chain->chain_client_input(input);
  } else {
    client_input(input);
  }
}

void Dispatch::chain_server_line(const std::string &line,
                                 const std::string &raw_line) {
  if (chain) {
    chain->chain_server_line(line, raw_line);
  } else {
    server_line(line, raw_line);
  }
}

void Dispatch::chain_server_prompt(const std::string &prompt) {
  if (chain) {
    chain->chain_server_prompt(prompt);
  } else {
    server_prompt(prompt);
  }
}

void Dispatch::server_line(const std::string &line,
                           const std::string &raw_line) {}
void Dispatch::server_prompt(const std::string &prompt) {}
void Dispatch::client_input(const std::string &input) {}

MainDispatch::MainDispatch(Director &d) : Dispatch{d}, id{d}, md{d} {
  BUGZ_LOG(warning) << "MainDispatch()";
}

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

void MainDispatch::activate(void) {
  count = 0;
  old_prompt = get_prompt();

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

  Boxes box(30, 1, true);
  box.boxcolor = "\x1b[1;33;44m";
  box.textcolor = "\x1b[1;33;44m";
  to_client("\n\r");
  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());

  // to_client("\n\r\x1b[1;34mWELCOME!  You are now in the proxy zone...\n\r");
  id.prompt = "\x1b[0m    \x1b[1;33;44m-=>\x1b[0m \x1b[1;37;44m";
  id.max_length = 15;
  id.setNotify([this]() { this->have_input(); });
  id.activate();
}

void MainDispatch::have_input(void) {
  ++count;
  std::string output =
      str(boost::format("Your Input (%2%): [%1%]\n\r") % id.input % count);
  to_client("\x1b[0m");
  to_client(output);
  if (id.input == "?") {
    // Maybe?  Maybe not.
  }

  if (id.input == "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 = " --==>> ";

    // bold white to white ---  black to green
    // md.menu_prompt = "\x1b[1;37;47m\xdb\xb2\xb1\xb0\x1b[0;30;47m RED GREEN
    // \x1b[30;42m\xdb\xb2\xb1\xb0\x1b[0m : ";

    // md.menu_prompt = "\x1b[0;31;47m\xdb\xb2\xb1\xb0\x1b[0;30;47m RED
    // GREEN\x1b[37;42m\xdb\xb2\xb1\xb0\x1b[0m : ";
    const char *CP437_GRADIENT = "\xdb\xb2\xb1\xb0 ";  // 100, 75, 50, 25, 0

    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 = {{"A", "Apple"}, {"B", "Blue"}, {"R", "Rabbit"}, {"Z", "ZOOO!"}};
    md.setNotify([this]() { this->menu_choice(); });
    md.activate();
    return;
  }

  if (id.input == "menu2") {
    md.lazy = false;
    md.setNotify([this]() { this->menu_choice(); });
    md.activate();
    return;
  }
  if (id.input.empty()) {
    // if (count >= 5) {
    auto lines = Boxes::alert(" Returning you to the game... ", "",
                              "\x1b[1;32m", 30, 1, true);
    // I'm not setting the box color, so the last color bleeds over.
    to_client("\x1b[0m");
    for (auto line : lines) {
      to_client(line);
    };
    to_client("Returning you to the game...\n\r");
    deactivate();
  } else {
    // prompt it again, sam.
    id.setNotify([this]() { this->have_input(); });
    id.activate();
  }
}

void MainDispatch::menu_choice(void) {
  if (md.input.empty()) {
    to_client("Menu abort.\n\r");
  } else {
    std::string text = "Back from menu [";
    text.append(md.input);
    text.append("] was your selection.\n\r");
    to_client(text);
  }

  id.max_length = 15;
  id.setNotify([this]() { this->have_input(); });
  id.activate();
}

void MainDispatch::deactivate(void) {
  // Since we're the main thing there --
  // sess->show_client = true;
  // sess->talk_direct = true;

  notify();
}

void MainDispatch::server_line(const std::string &line,
                               const std::string &raw_line) {
  BUGZ_LOG(info) << "MDSL: " << line;
  to_client("SL: ");
  to_client(line);
  to_client("\n\r");
}

void MainDispatch::server_prompt(const std::string &prompt) {
  BUGZ_LOG(info) << "MDSP: " << prompt;
}

#ifdef NEVERMORE
void MainDispatch::client_input(const std::string &input) {
  // I don't care what the old prompt looked liked at this point.
  BUGZ_LOG(warning) << "Got: " << input;  //  << " prompt=" << get_prompt();

  // Serious problem when the input = "\x1b" ESC.  The output gets gummed/locked
  // up.
  if (input == "\x1b") {
    return;
  }

  ++count;
  std::string output = str(boost::format("MSG %1%: [%2%]\n\r") % count % input);
  to_client(output);
  if (count >= 5) {
    to_client("And we're outta here!\n\r");
    deactivate();
  }
}
#endif

InputDispatch::InputDispatch(Director &d) : Dispatch(d) {
  BUGZ_LOG(warning) << "InputDispatch()";
}

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

void InputDispatch::activate(void) {
  input.clear();
  to_client(prompt);
}

void InputDispatch::deactivate(void) { notify(); }

void InputDispatch::server_line(const std::string &line,
                                const std::string &raw_line) {
  if (line.empty()) return;

  std::string temp = repr(raw_line);
  BUGZ_LOG(fatal) << "Input:SL(" << temp << ")";

  if (startswith(line, "Command [TL=")) {
    return;
  }

  /*
  temp = raw_line;
  clean_string(temp);
  BUGZ_LOG(fatal) << "InputDispatch::server_line(" << temp << ")";
  */
  temp = prompt;
  ansi_clean(temp);
  size_t total = temp.length() + input.length();

  to_client("\x1b[0m");  // reset colors
  while (total > 0) {
    to_client("\b \b");
    --total;
  }

  // Lines line "\[[1A\[[1;36mphil \[[0;32mwarps into the sector.\[[0m"
  temp = raw_line;
  replace(temp, "\x1b[1A", "");
  // replace(temp, "\x1[2J", "");
  to_client(temp);
  to_client("\n\r");
  // Doesn't matter if it is one or two calls.
  temp = prompt;
  temp.append(input);
  to_client(temp);
  // to_client(prompt);
  // to_client(input);
}

// void InputDispatch::server_prompt(const std::string &prompt) {}

void InputDispatch::client_input(const std::string &cinput) {
  // BUGZ_LOG(info) << "InputDispatch::client_input(" << cinput << ")";
  for (const char ch : cinput) {
    if (isprint(ch)) {
      // Ok!
      if (input.length() < max_length) {
        to_client(std::string(1, ch));
        input += ch;
      }
    } else if ((ch == '\b') || (ch == 0x7f)) {
      // Backspace or rubout
      if (input.length() > 0) {
        to_client("\b \b");
        input.erase(input.size() - 1);
      }
    } else if (ch == '\r') {
      // Ok, we're done!
      BUGZ_LOG(info) << "InputDispatch done: " << input;
      to_client("\x1b[0m\n\r");
      deactivate();
    }
  }
}

/**
 * Menu Dispatch
 *
 * Two types of menus:
 * lazy:  display the menu name and show prompt. ? shows menu.
 * non-lazy:  displays menu + prompts.
 *
 *
 */

MenuDispatch::MenuDispatch(Director &d) : Dispatch(d) {
  BUGZ_LOG(warning) << "MenuDispatch()";
}

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

void MenuDispatch::activate(void) {
  calculate_widths();
  input.clear();

  BUGZ_LOG(warning) << "MenuDispatch::activate() " << max_width << ", "
                    << max_option_width;

  if (lazy)
    menubox();
  else
    help();
  to_client(menu_prompt);
}

void MenuDispatch::deactivate(void) { notify(); }

void MenuDispatch::help(void) {
  size_t max = max_width;
  Boxes mbox(max, 1, true);
  mbox.boxcolor = menu_box_color;

  if (lazy) {
    // just the menu
    mbox.textcolor = menu_options_color;
    to_client(mbox.top());
    for (auto const menu_item : menu) {
      std::string text = " ";
      text.append(menu_item.first);
      text.append(" - ");
      text.append(menu_item.second);
      while (text.length() < max) text.append(1, ' ');
      to_client(mbox.row(text));
    }
    to_client(mbox.bottom());
  } else {
    // full menu
    mbox.textcolor = menu_text_color;
    to_client(mbox.top());
    std::string title = centered(max, menu_title);
    BUGZ_LOG(debug) << "help max=" << max << " [" << title << "]";

    to_client(mbox.row(title));
    to_client(mbox.middle());
    mbox.textcolor = menu_options_color;
    for (auto const menu_item : menu) {
      std::string text = " ";
      text.append(menu_item.first);
      text.append(" - ");
      text.append(menu_item.second);
      while (text.length() < max) text.append(1, ' ');
      to_client(mbox.row(text));
    }
    to_client(mbox.bottom());
  }
}

std::string MenuDispatch::centered(int length, const std::string &s) {
  std::string text = s;
  size_t leftovers = length - text.length();
  int count = leftovers / 2;
  if (count > 0) {
    text.insert(0, count, ' ');
    text.append(count, ' ');
  }

  if (leftovers % 1 == 1) text.append(1, ' ');
  return text;
}

void MenuDispatch::menubox(void) {
  // just the menu box
  std::string title = centered(max_width, menu_title);
  /*
  int leftovers = max - menu_title.length();
  while (leftovers > 2) {
    title.insert(0, 1, " ");
    title.append(1, " ");
    leftovers -= 2;
  };
  if (leftovers == 1)
    title.append(1, " ");
  */
  BUGZ_LOG(debug) << "menubox max=" << max_width << " [" << title << "]";

  auto abox =
      Boxes::alert(title, menu_box_color, menu_text_color, max_width, 1, true);
  for (auto line : abox) {
    to_client(line);
  }
}

void MenuDispatch::calculate_widths(void) {
  max_width = menu_title.length() + 2;
  max_option_width = 0;
  for (auto key : menu) {
    size_t menu_line_length =
        1 + key.first.length() + 3 + key.second.length() + 1;
    if (menu_line_length > max_width) max_width = menu_line_length;
    if (key.first.length() > max_option_width)
      max_option_width = key.first.length();
  }
  instant = max_option_width == 1;
}

void MenuDispatch::server_line(const std::string &line,
                               const std::string &raw_line) {
  // TODO:
  // Clear prompt, display raw server line, restore prompt.
  if (line.empty()) return;

  std::string temp = repr(raw_line);
  BUGZ_LOG(fatal) << "Input:SL(" << temp << ")";

  if (startswith(line, "Command [TL=")) {
    return;
  }

  /*
  temp = raw_line;
  clean_string(temp);
  BUGZ_LOG(fatal) << "InputDispatch::server_line(" << temp << ")";
  */
  temp = menu_prompt;
  ansi_clean(temp);
  size_t total = temp.length() + input.length();

  to_client("\x1b[0m");  // reset colors
  while (total > 0) {
    to_client("\b \b");
    --total;
  }

  // Lines line "\[[1A\[[1;36mphil \[[0;32mwarps into the sector.\[[0m"
  temp = raw_line;
  replace(temp, "\x1b[1A", "");
  // replace(temp, "\x1[2J", "");
  to_client(temp);
  to_client("\n\r");
  // Doesn't matter if it is one or two calls.
  temp = menu_prompt;
  temp.append(input);
  to_client(temp);
  // to_client(prompt);
  // to_client(input);
}

void MenuDispatch::client_input(const std::string &cinput) {
  for (auto const ch : cinput) {
    // not likely that we'd have more then one,
    // but deal with it correctly.

    if (ch == '\r') {
      // enter
      if (instant) return;

      for (auto const mnu : menu) {
        if (mnu.first == input) {
          to_client("\x1b[0m\n\r");
          deactivate();
          return;
        }
      }

      // input wasn't found ?
      while (input.length() > 0) {
        to_client("\b \b");
        input.erase(input.length() - 1);
      }
      return;  // don't continue ...
    }

    if (ch == '\x1b') {
      // [ESC] - erase the input string
      while (input.length() > 0) {
        to_client("\b \b");
        input.erase(input.length() - 1);
      }

      // Exit - allow escape from menu
      deactivate();
      return;
    }

    if (ch == '\b') {
      if (input.length() > 0) {
        to_client("\b \b");
        input.erase(input.length() - 1);
      }
    }

    if (ch == '?') {
      to_client(cinput);  // display what they entered.
      to_client("\x1b[0m\n\r");
      help();
      to_client(menu_prompt);
      return;
    }

    if (isprint(ch)) {
      char c = ch;
      if (!case_sensitive) c = toupper(ch);

      // ok, it's a printable character
      if (input.length() < max_option_width) {
        // ok, there's room.
        to_client(std::string(1, c));
        input.append(1, c);
      }

      if (instant) {
        for (auto const mnu : menu) {
          if (mnu.first == input) {
            to_client("\x1b[0m\n\r");
            deactivate();
            return;
          }
        }
        // Erase last character, wasn't an option.
        to_client("\b \b");
        input.erase(input.length() - 1);
      }
    }
  }
}

CIMDispatch::CIMDispatch(Director &d) : Dispatch(d) {}

void CIMDispatch::activate(void) { count = 0; }

void CIMDispatch::deactivate(void) { notify(); }

void CIMDispatch::server_line(const std::string &line,
                              const std::string &raw_line) {
  if (!((line.empty() || startswith(line, ": ") ||
         startswith(line, "Command [")))) {
    count++;
    if (count % 100 == 0) {
      std::string message = str(boost::format("\r%1%") % count);
      to_client(message);
    }
  }
  if (line == ": ENDINTERROG") {
    std::string message = str(boost::format("\r%1%\n\r") % count);
    to_client(message);
    deactivate();
  }
}

/*
 * CoreDispatch:  This is an example class that does dispatch.
 * Copy this and make changes from there...
 */
CoreDispatch::CoreDispatch(Director &d) : Dispatch(d) {
  BUGZ_LOG(warning) << "CoreDispatch()";
}

void CoreDispatch::activate(void) {
  // save things, set things
}

void CoreDispatch::deactivate(void) {
  // restore things
  notify();
}

void CoreDispatch::server_line(const std::string &line,
                               const std::string &raw_line) {}
void CoreDispatch::server_prompt(const std::string &prompt) {}
void CoreDispatch::client_input(const std::string &input) {
  BUGZ_LOG(warning) << "Got: " << input << " prompt=" << get_prompt();
}