#include <ctype.h>
#include <regex>
#include <string>
#include <vector>

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

void CharMan::validate(void) {
  // Control buffer debugging output.
#ifndef NO_BUFFER_DEBUG
  bool valid = true;
  ZF_LOGE("validate: text_offsets %d", (int)text_offsets.size());
  ZF_LOGE("validate work size %d, buffer size %d, text size %d",
          (int)work.size(), (int)buffer.size(), (int)text.size());

  for (int x = 0; x < (int)text_offsets.size(); ++x) {
    int offset = text_offsets[x];
    ZF_LOGE("%d : %d", x, offset);

    if (offset >= 0) {
      if (text[x] != work[offset]) {
        ZF_LOGE("validate: %d off %d [%c != %c]", x, offset, text[x],
                work[offset]);
        valid = false;
      }
      if ((text[x] != ' ') && (text[x] != buffer[offset])) {
        ZF_LOGE("validate: %d off %d [%c != %c]", x, offset, text[x],
                buffer[offset]);
        valid = false;
      }
    }
  }
  if (!valid) {
    ZF_LOGE("* NOT VALID*  Somethings hosed.");
    diagnostics();
  }
#endif
}

void CharMan::diagnostics(void) {
  // Control buffer debugging output.
#ifndef NO_BUFFER_DEBUG
  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:");

  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();
#endif
}

void CharMan::regular_expressions(void) {
  static std::regex words("[a-zA-Z'-]+([ ]{1,2}[a-zA-Z&'-]+)+");
  // I need position and length.
  pos_len.clear();

  for (auto it =
           std::sregex_iterator(this->text.begin(), this->text.end(), words);
       it != std::sregex_iterator(); ++it) {

    int pos = it->position(), len = it->length();
    ZF_LOGD("pos %d len %d (%s)", pos, len,
            this->text.substr(pos, len).c_str());
    if (len > 4)
      pos_len.push_back(std::make_pair(it->position(), it->length()));
  }
}

char CharMan::get(int pos) { return this->text[pos]; }

void CharMan::set(int pos, char ch) {
  this->text[pos] = ch;
  int idx = this->text_offsets[pos];
  if (idx >= 0) {
    this->buffer[idx] = ch;
    this->work[idx] = ch;
  }
}

void CharMan::insert(int pos, std::string str) {
  int len = str.size();
  // What happens if pos is at the end of the buffer?
  int idx;
  if (pos == (int)text_offsets.size()) {
    // Ok, this is at the very end of the buffer, which is beyond what the
    // vector is holding.
    idx = this->text_offsets[pos - 1] + 1;
    ZF_LOGE("Use %d for idx", idx);
  } else
    idx = this->text_offsets[pos];

  // ZF_LOGE("insert( POS %d, LEN %d, IDX %d, %s)", pos, len, idx, str.c_str());
  // diagnostics();
  std::string blank(len, ' ');
  // Don't insert into text.
  // Insert blank into work.
  // Update indexes >= idx

  if (idx >= 0) {
    this->buffer.insert(idx, str);
    this->work.insert(idx, blank);

    // UPDATE indexes!
    for (auto it = std::begin(this->text_offsets);
         it != std::end(this->text_offsets); ++it) {
      if (*it >= idx) {
        *it += len;
      }
    }
    // ZF_LOGE("Indexes updated... check your work");
    // diagnostics();
  }
}

int CharMan::word_mangler(std::pair<int, int> pos_len) {
  int pos = pos_len.first;
  int state = randrange(-1, 1);
  int count = 0;

  for (int p = 0; p < pos_len.second; ++p) {
    char c = this->get(pos + p);
    if (randint(pos_len.second) == p)
      break;
    switch (state) {
    case -1:
      if (islower(c)) {
        count++;
        this->set(pos + p, toupper(c));
      }
      break;
    case 0:
      if (islower(c)) {
        count++;
        this->set(pos + p, toupper(c));
      } else {
        if (isupper(c)) {
          count++;
          this->set(pos + p, tolower(c));
        }
      }
      break;
    case 1:
      if (isupper(c)) {
        count++;
        this->set(pos + p, tolower(c));
      }
      break;
    }
  }
  return count;
}

// What is the max number of characters I should wrangle?
#define MAX_TRANSPOSE 2

int CharMan::word_wrangler(std::pair<int, int> pos_len) {
  int count = 0;
  int p;
  int len;

  if (pos_len.second < 4)
    return 0;

  p = randint(pos_len.second - 4) + 2;
  for (len = 0; len < MAX_TRANSPOSE; ++len) {
    if (!isalpha(this->get(pos_len.first + p + len)))
      break;
  }

  ZF_LOGD("Wrangler: %d, %d", p, len);

  if (len >= 2) {
    for (int x = 0; x < len / 2; x++) {
      char ch = this->get(pos_len.first + p + x);
      this->set(pos_len.first + p + x,
                this->get(pos_len.first + p + len - 1 - x));
      this->set(pos_len.first + p + len - 1 - x, ch);
    }
    count++;
  }
  return count;
}

/*
  Display up to certain point.
  Print some characters slowly.  Delay.
 */
int CharMan::word_tangler(std::pair<int, int> pos_len) {
  int p;
  int len;

  std::string part = this->text.substr(pos_len.first, pos_len.second);
  ZF_LOGE("tangler [%s]", logrepr(part.c_str()));

  if (pos_len.second < 4)
    return 0;

  /* p = randint(pos_len.second - 4);
  len = randint(pos_len.second - p); */
  p = pos_len.first;
  len = pos_len.second;

  if (len >= 2) {
    ZF_LOGD("Tangler: %d, %d", p, len);
    this->validate();
    std::ostringstream buffer;
    std::string tangle;
    int r = 1; // randint(2) + 1;

    buffer << "^P1^R" << r;
    r = randint(4) + 1;
    buffer << "^S" << r;
    tangle = buffer.str();
    std::string reset = "^R0^S0";
    ZF_LOGD("insert reset %s", reset.c_str());
    this->insert(p + len, reset);
    // this->validate();
    ZF_LOGD("insert tangle %s", tangle.c_str());
    this->insert(p, tangle);
    // this->validate();
    return 1;
  }
  return 0;
}

CharMan::CharMan(std::string &buffer, std::string &work, std::string &text,
                 std::vector<int> &text_offsets)
    : buffer(buffer), work(work), text(text), text_offsets(text_offsets) {

  this->mangle_count = 0;
  this->mangle_chars = 0;
  this->need_render = 0;

  this->level = harry_level();
  if (!this->level)
    return;

  // validate();
  regular_expressions();
  ZF_LOGD("Found %d word groups", (int)pos_len.size());
  if (pos_len.size() > 0) {
    for (int i = 0; i < (int)pos_len.size(); ++i) {
      int active = 0;
      // if (random_activate((level + 1) / 2)) { // 8
      if (random_activate(level * 11)) { // level = 4, so it's 44
        int c = word_mangler(pos_len[i]);
        if (c) {
          active = 1;
          this->mangle_count++;
          this->mangle_chars += c;
        }
      }

      // if (random_activate((level + 1) / 2)) { // 4
      if (random_activate(level * 11)) { // level = 4, so it's 44
        if (word_wrangler(pos_len[i])) {
          this->mangle_count++;
          active = 1;
        }
      }

      if (!active && random_activate(level)) { // level = 4, so it's 4
        if (word_tangler(pos_len[i])) {
          this->need_render = 1;
        }
      }
    }
  }
};

CharMan::~CharMan() {
  ZF_LOGD("~CharMan");
  // validate();
}