#include "images.h"
#include "lastseen.h"
#include "render.h"
#include "terminal.h"
#include "utils.h"
#include <iomanip>
// #include <regex.h>
#include <sstream>
#include <string.h>
#include <string>
#include <vector>

#include "charman.h"
#include "zf_log.h"

#include <unistd.h> // write

extern struct console_details console;

#define BSIZE 512

/*
 * harry_idle_event(fd)
 *
 * User is idle, let's let them know we're here.  HAHAHA!
 *
 */
void harry_idle_event(int fd) {
  // Make something happen
  std::ostringstream buffer;
  int r;
  // This is no where near finished, BUT!
  // Do not put any ^ codes in these -- the strlen() would be wrong.
  const char *phrases[] = {"Hahaha",    "Snicker, snicker", "Boo!",
                           "MeOW",      "I see U",          "Arrooo!",
                           "Ahh-wooo!", "Aaaooo!"};

  static LastSeen last_seen_harry_event(2);

  do {
    r = randint((sizeof(phrases) / sizeof(char *)));
  } while (last_seen_harry_event.seen_before(r));

  int color = randint(15) + 1;
  // %02d = std::setfill('0') << std::setw(2) << (int)

  buffer << "^S2^C" << std::setfill('0') << std::setw(2) << color << phrases[r]
         << "^P2^CR^D" << std::setw(2) << strlen(phrases[r]);

  /*
  slen = snprintf(buffer, sizeof(buffer), "^S2^C%02d%s^P2^CR^D%02d", color, cp,
                  (int)strlen(cp));
  if (slen >= sizeof(buffer)) {
    ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer));
    buffer[0] = 0;
  }
  */

  std::string str = buffer.str();
  ZF_LOGD("harry_event: render(%d, \"%s\")", fd, str.c_str());
  render(fd, str);
}

void init_harry() {
  // init_have_seen(last_seen_harry_event, MAX_HARRY_EVENT_DUPS);
  // ZF_LOGD("init => %d %d", last_seen_harry_event[0],
  // last_seen_harry_event[1]);
  console_init(&console);
}

// char words[] = "[a-zA-Z]+( [a-zA-Z]+)+";

int mangle_clrscr(std::string &buffer, std::string &work, size_t pos) {
  static int ANSI_CLS_count = 0;
  ZF_LOGI("seen: ANSI_CLS");
  ANSI_CLS_count++;

  if (ANSI_CLS_count > 1) {
    // get the restore color value
    struct console_details temp_console;
    memcpy(&temp_console, &console, sizeof(console));
    console_receive(&temp_console, buffer.substr(0, pos));
    std::string restore_color;
    restore_color.assign(color_restore(&temp_console));

    if (random_activate(3)) {
      std::ostringstream display;
      int needs_cls = 0;

      struct image {
        const char **lines;
        int size;
        int cls;
        int width; // height = size
      } images[] = {{ghost, sizeof(ghost) / sizeof(char *), 1, 0},
                    {ghead, sizeof(ghead) / sizeof(char *), 1, 0},
                    {wolf, sizeof(wolf) / sizeof(char *), 1, 0},
                    {panther, sizeof(panther) / sizeof(char *), 1, 0},
                    {bat, sizeof(bat) / sizeof(char *), 1, 0},
                    {icu, sizeof(icu) / sizeof(char *), 0, 20},
                    {skull, sizeof(skull) / sizeof(char *), 0, 19},
                    {skullblink, sizeof(skullblink) / sizeof(char *), 0, 19}};

      static LastSeen last_files(2);
      int r;

      do {
        r = randint((sizeof(images) / sizeof(image)));
      } while (last_files.seen_before(r));

      std::string fgoto;

      if (!images[r].cls) {
        int x = 0, y = 0;

        x = randint(79 - images[r].width) + 1;
        y = randint(24 - images[r].size) + 1;

        display << "^f" << std::setfill('0') << std::setw(2) << x
                << std::setw(2) << y << "\x1b[1;1H";
        fgoto = display.str();
        // reset display
        display.str(std::string());
        display.clear();

        // render image, home cursor
        // slen = snprintf(fgoto, sizeof(fgoto), "^f%02d%02d\x1b[1;1H", x, y);
      } else {
        fgoto.assign("^F");
      }

      needs_cls = images[r].cls;

      // I get what's happening.  Mystic moves cursor to home, CLS, cursor
      // home. When we get here, we're ALWAYS at the top of the screen...
      // Hence our bat isn't displayed at the end of the screen.

      // This is before the actual CLS, so we CLS before displaying our files.
      // I tried a ^P2 before doing this .. but I'd rather have the picture up
      // right away I think.

      // Ok, yes, there's no filename being sent.  :P
      render_image(images[r].lines, images[r].size);

      display << (needs_cls ? "\x1b[2J" : "") << fgoto << restore_color
              << "^P3";

      // slen = snprintf(display, sizeof(display), "%s%s%s^P3",
      //                 needs_cls ? "\x1b[2J" : "", fgoto, restore_color);

      std::string display_output = display.str();

      ZF_LOGI("mangle(ANSI_CLS): %d file inserted %s", r,
              repr(display_output.c_str()));

      // Move the buffer so there's room for the display string.
      buffer.insert(pos, display_output);
      work.insert(pos, std::string(display_output.size(), ' '));
      return 1; // need_render = 1;
    } else {
      if (random_activate(4)) {
        int r;
        std::ostringstream display;

        const char *phrasing[] = {
            "^R1Haha^P1ha^P1ha", "Poof!", "Got U", "Anyone there?",
            "^R1Knock, ^P1Knock",
            /*
            This picks random color and position -- then
            homes cursor and changes to another color.  (This can be seen.)
             */
            "^G0101^C07^S9Segmentation fault (core dumped)^P2"};
        static LastSeen last_phrasing(2);

        ZF_LOGI("mangle(ANSI_CLS)");

        do {
          r = randint(sizeof(phrasing) / sizeof(char *));
        } while (last_phrasing.seen_before(r));

        int color = randint(14) + 1;
        int x = randint(30) + 1;
        int y = randint(15) + 1;

        /*
        Don't have it pause there before moving the cursor.

        Move the cursor, get the color changed, THEN pause.
        Then act all crazy.

        NOTE:  Make sure if you use any ^R Render effects, turn them off
        before trying to display the restore_color.  :P   ^R0 Also, make
        sure you re-home the cursor ^G0101 because that's where they are
        expecting the cursor to be!  (At least it's how Mystic does it.)

        HOME, CLS, HOME, ...  Not sure what others do there.  We'll see.
        */

        if (strncmp(phrasing[r], "^G", 2) == 0) {
          display << "^S3^P1" << phrasing[r] << "^S0^R0" << restore_color
                  << "^P1^G0101";
          // This starts with a GOTO, so don't use our random position
          // slen = snprintf(display, sizeof(display),
          // "^S3^P1%s^S0^R0%s^P1^G0101",
          //                 phrasing[r], restore_color);
        } else {
          display << "^G" << std::setw(2) << std::setfill('0') << x
                  << std::setw(2) << y << "^S3^C" << std::setw(2) << color
                  << "^P1" << phrasing[r] << "^S0^R0" << restore_color
                  << "^P1^G0101";
          // slen = snprintf(display, sizeof(display),
          //                "^G%02d%02d^S3^C%02d^P1%s^S0^R0%s^P1^G0101", x, y,
          //                color, phrasing[r], restore_color);
        };

        std::string display_output = display.str();

        // sprintf(display, "^P1^S3^C%02d%s^S0^R0%s^P1", color, phrasing[r],
        // restore_color);
        // Added debug statement so we can identify what was sent... color,
        // number picked and what that is
        ZF_LOGD("mangle(ANSI_CLS): Inserted color=%02d r=%d phrase='%s'", color,
                r, phrasing[r]);
        ZF_LOGI("mangle(ANSI_CLS): %d %s", r, repr(display_output.c_str()));

        // Move the buffer so there's room for the display string.
        buffer.insert(pos, display_output);
        work.insert(pos, std::string(display_output.size(), ' '));
        return 1; // need_render = 1;
      }
    }
  }
  return 0;
}

int mangle(int fd, std::string &buffer) {
  // a simple default for now.
  ZF_LOGV_MEM(buffer.data(), buffer.size(), "mangle(%d): %lu bytes", fd,
              buffer.size());

  int need_render = 0;
  int mangled = 0;
  int mangled_chars = 0;
  static std::string work;
  static size_t work_size = 0;

  work.assign(buffer);

  // This should allow us to monitor any memory allocations
  if (work.capacity() != work_size) {
    ZF_LOGD("work cap %lu -> %lu", work_size, work.capacity());
    work_size = work.capacity();
  }

  const char *ANSI_CLS = "\x1b[2J";
  size_t pos = buffer.find(ANSI_CLS);

  if (pos != std::string::npos) {
    if (mangle_clrscr(buffer, work, pos)) {
      need_render = 1;
    }
  }

  // Ok, maybe the work string was a bad idea?
  static std::string text;
  static std::vector<int> text_offsets;
  size_t stri;

  text.clear();
  text_offsets.clear();

  for (stri = 0; stri < buffer.size(); ++stri) {
    // why wasn't \x1b[?1000h   handled by console_char?
    // what happened to \x0c ?   It is there.
    termchar tc = console_char(&console, work[stri]);

    if (tc.in_ansi) {
      if (tc.ansi != START) {
        // Ok, this is something.  What is it?
        // ZF_LOGV("ANSI type %d at %lu", tc.ansi, stri);
        switch (tc.ansi) {
        case CURSOR:
        case CLEAR:
        case OTHER:
          text.append(1, '.');
          text_offsets.push_back(-1);
          break;
        case START:
        case COLOR:
          // text.append(1, ' ');
          // text_offsets.push_back(-1);
          // color changes show as nothing in the text string.
          break;
        }
      }
    } else {
      // These should never get out of sync ...
      if (text.size() != text_offsets.size()) {
        ZF_LOGE("Error: text != text_offsets %lu != %lu", text.size(),
                text_offsets.size());
      }
      text.append(1, work[stri]);
      text_offsets.push_back(stri);
    }
  }

  ZF_LOGV_MEM(buffer.data(), buffer.size(), "Buffer:");
  ZF_LOGV_MEM(work.data(), work.size(), "Work:");
  ZF_LOGV_MEM(text.data(), text.size(), "Text Buffer:");

  // Output vector contents
  std::ostringstream oss;
  int comma = 0;
  for (auto it = std::begin(text_offsets); it != std::end(text_offsets); ++it) {
    if (comma) {
      oss << ", ";
    };
    comma++;
    oss << *it;
    if (comma == 30) {
      std::string temp_output = oss.str();
      ZF_LOGV("Vector: %s", temp_output.c_str());
      // reset ostringstream
      oss.str(std::string());
      oss.clear();
      comma = 0;
    }
  }
  std::string vector_output = oss.str();
  ZF_LOGV("Vector: %s", vector_output.c_str());

  // reset oss (if we need it)
  oss.str(std::string());
  oss.clear();

  // Begin the mangle process 2.0
  {
    ZF_LOGD("CharMan");
    CharMan cm(buffer, work, text, text_offsets);
    ZF_LOGD("CharMan %d, %d chars", cm.mangle_count, cm.mangle_chars);
  };

  if (need_render) {
    render(fd, buffer);
  } else {
    write(fd, buffer.data(), buffer.size());
  }
  return need_render;
}