#include "helper_functions.h"
#include "../libs/unicode_width/unicode_width.h"
#include "editor.h"
#include "node.h"
#include "snake.h"
#include <grapheme.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

void die(const char *s) {
  disable_raw_mode();
  printf("he :   %s\n", s);
  fflush(stdout);
  exit(EXIT_FAILURE);
  usleep(1000000000);
}

size_t clamp_to_visible(BufferData *e, int row, int dir) {
  if (row < 0)
    return 0;
  if ((size_t)row >= e->line_count)
    return e->line_count - 1;
  while (row >= 0 && (size_t)row < e->line_count && line_is_hidden(e, row)) {
    row += dir;
  }
  if (row < 0)
    return 0;
  if ((size_t)row >= e->line_count)
    return e->line_count - 1;
  return row;
}

uint32_t fnv1a_32(const char *data, size_t len) {
  uint32_t hash = 2166136261u;
  for (size_t i = 0; i < len; i++) {
    hash ^= (uint8_t)data[i];
    hash *= 16777619u;
  }
  return hash;
}

int physical_to_visible(BufferData *e, int prow) {
  int vrow = 0;
  for (int i = 0; i <= prow && i < (int)e->line_count; i++) {
    if (!line_is_hidden(e, i))
      vrow++;
  }
  return vrow - 1;
}

int visible_to_physical(BufferData *e, int vrow) {
  int vcount = 0;
  for (int i = 0; i < (int)e->line_count; i++) {
    if (!line_is_hidden(e, i)) {
      if (vcount == vrow)
        return i;
      vcount++;
    }
  }
  return e->line_count ? (int)e->line_count - 1 : 0;
}

int visible_count(BufferData *e) {
  int count = 0;
  for (size_t i = 0; i < e->line_count; i++)
    if (!line_is_hidden(e, i))
      count++;
  return count;
}

_Bool line_is_hidden(BufferData *e, size_t idx) {
  for (size_t i = 0; i < e->fold_count; i++) {
    if (e->folds[i].folded && idx > e->folds[i].start && idx <= e->folds[i].end)
      return true;
  }
  return false;
}

_Bool line_is_start(BufferData *e, size_t idx) {
  for (size_t i = 0; i < e->fold_count; i++) {
    if (e->folds[i].folded && idx == e->folds[i].start)
      return true;
  }
  return false;
}

Coords filter_visible_lines(BufferData *e, VirtLine ***out_virt,
                            Line ***out_lines, size_t *out_count) {
  Coords ret = {0, 0};
  if (!e || !out_virt || !out_count)
    return ret;
  Line **new_lines = malloc(e->line_count * sizeof(Line *));
  VirtLine **new_virt = malloc(e->line_count * sizeof(VirtLine *));
  if (!new_lines || !new_virt) {
    free(new_lines);
    free(new_virt);
    return ret;
  }
  bool *line_hidden = calloc(e->line_count, sizeof(bool));
  bool *line_is_fold_start = calloc(e->line_count, sizeof(bool));
  size_t *fold_line_count = calloc(e->line_count, sizeof(size_t));
  if (!line_hidden || !line_is_fold_start || !fold_line_count) {
    free(new_lines);
    free(new_virt);
    free(line_hidden);
    free(line_is_fold_start);
    free(fold_line_count);
    return ret;
  }
  for (size_t i = 0; i < e->fold_count; i++) {
    if (e->folds[i].folded) {
      if (e->folds[i].start < e->line_count) {
        line_is_fold_start[e->folds[i].start] = true;
        size_t fold_lines = 0;
        if (e->folds[i].end < e->line_count &&
            e->folds[i].end > e->folds[i].start) {
          fold_lines = e->folds[i].end - e->folds[i].start;
        }
        fold_line_count[e->folds[i].start] = fold_lines;
      }
      for (size_t j = e->folds[i].start + 1;
           j <= e->folds[i].end && j < e->line_count; j++) {
        line_hidden[j] = true;
      }
    }
  }
  size_t count = 0;
  for (size_t i = 0; i < e->line_count; i++) {
    if (i == e->cursor_row)
      ret.row = count;
    if (!line_hidden[i]) {
      new_lines[count] = e->lines[i];
      VirtLine *orig_vl = e->virtual_lines[i];
      if (orig_vl && line_is_fold_start[i]) {
        Line *n_l = calloc(1, sizeof(Line));
        VirtLine *vl_copy = calloc(1, sizeof(VirtLine));
        vl_copy->lines = calloc(1, sizeof(Line *));
        int folded_lines = fold_line_count[i];
        char buf[32];
        if (folded_lines == -1)
          buf[0] = '\0';
        else
          snprintf(buf, sizeof(buf), " folded %d lines", folded_lines + 1);
        int len = strlen(buf);
        n_l->clusters =
            calloc(e->lines[i]->cluster_count + len + 2, sizeof(Cluster));
        for (size_t j = 0; j < e->lines[i]->cluster_count; j++) {
          n_l->clusters[j].utf8 = calloc(1, sizeof(char));
          n_l->clusters[j].utf8[0] = '\0';
          n_l->clusters[j].width = 1;
        }
        n_l->clusters[e->lines[i]->cluster_count].utf8 =
            calloc(2, sizeof(char));
        n_l->clusters[e->lines[i]->cluster_count].utf8[0] = ' ';
        n_l->clusters[e->lines[i]->cluster_count].utf8[1] = '\0';
        n_l->clusters[e->lines[i]->cluster_count].width = 1;
        const char *symbol_utf8 = "󰁂";
        n_l->clusters[e->lines[i]->cluster_count + 1].utf8 =
            malloc(strlen(symbol_utf8) + 1);
        strcpy(n_l->clusters[e->lines[i]->cluster_count + 1].utf8, symbol_utf8);
        n_l->clusters[e->lines[i]->cluster_count + 1].width = 1;
        for (int j = 0; j < len; j++) {
          n_l->clusters[e->lines[i]->cluster_count + j + 2].utf8 =
              calloc(2, sizeof(char));
          n_l->clusters[e->lines[i]->cluster_count + j + 2].utf8[0] = buf[j];
          n_l->clusters[e->lines[i]->cluster_count + j + 2].utf8[1] = '\0';
          n_l->clusters[e->lines[i]->cluster_count + j + 2].width = 1;
        }
        n_l->cluster_count = e->lines[i]->cluster_count + len + 2;
        n_l->highlight_count = 1;
        n_l->highlights = calloc(1, sizeof(Highlight));
        n_l->highlights[0].start_col = e->lines[i]->cluster_count;
        n_l->highlights[0].end_col = e->lines[i]->cluster_count + len + 2;
        n_l->highlights[0].fg = (Color *)colors + RED;
        vl_copy->lines[0] = n_l;
        vl_copy->line_count = 1;
        vl_copy->is_tmp = true;
        new_virt[count] = vl_copy;
      } else {
        new_virt[count] = orig_vl;
      }
      count++;
    }
  }
  free(line_hidden);
  free(line_is_fold_start);
  free(fold_line_count);
  new_lines = realloc(new_lines, count * sizeof(Line *));
  new_virt = realloc(new_virt, count * sizeof(VirtLine *));
  *out_lines = new_lines;
  *out_virt = new_virt;
  *out_count = count;
  ret.col = e->cursor_col;
  return ret;
}

void get_terminal_size(int *rows, int *cols) {
  struct winsize w;
  ioctl(0, TIOCGWINSZ, &w);
  *rows = w.ws_row;
  *cols = w.ws_col;
}

char *line_to_string(Line *line) {
  if (!line || line->cluster_count == 0)
    return strdup("");
  size_t total_len = 0;
  for (size_t i = 0; i < line->cluster_count; i++) {
    total_len += strlen(line->clusters[i].utf8);
  }
  char *result = malloc(total_len + 1);
  if (!result)
    return NULL;
  char *p = result;
  for (size_t i = 0; i < line->cluster_count; i++) {
    size_t len = strlen(line->clusters[i].utf8);
    memcpy(p, line->clusters[i].utf8, len);
    p += len;
  }
  *p = '\0';
  return result;
}

char *lines_to_string(BufferData *e) {
  if (!e || e->line_count == 0)
    return strdup("");
  size_t total_len = 0;
  for (size_t i = 0; i < e->line_count; i++) {
    for (size_t j = 0; j < e->lines[i]->cluster_count; j++)
      total_len += strlen(e->lines[i]->clusters[j].utf8);
    total_len += 1;
  }
  char *result = malloc(total_len + 1);
  if (!result)
    return NULL;
  char *p = result;
  for (size_t i = 0; i < e->line_count; i++) {
    Line *line = e->lines[i];
    for (size_t j = 0; j < line->cluster_count; j++) {
      size_t len = strlen(line->clusters[j].utf8);
      memcpy(p, line->clusters[j].utf8, len);
      p += len;
    }
    *p++ = '\n';
  }
  *p = '\0';
  return result;
}

char *read_file(const char *filename) {
  FILE *fp = fopen(filename, "rb");
  if (!fp) {
    perror("fopen");
    return NULL;
  }
  fseek(fp, 0, SEEK_END);
  long size = ftell(fp);
  rewind(fp);
  char *buffer = malloc(size + 1);
  if (!buffer) {
    perror("malloc");
    fclose(fp);
    return NULL;
  }
  size_t read = fread(buffer, 1, size, fp);
  buffer[read] = '\0';
  fclose(fp);
  return buffer;
}

int file_type(const char *filename) {
  char command[256];
  sprintf(command, "file -b --mime-type %s", filename);
  FILE *fp = popen(command, "r");
  if (!fp)
    return 0;
  char buf[256];
  if (!fgets(buf, sizeof(buf), fp))
    return 0;
  pclose(fp);
  if (strcmp(buf, "text/x-ruby\n") == 0)
    return RUBY;
  if (strcmp(buf, "text/x-c\n") == 0)
    return C_LANG;
  return UNKNOWN;
}

int display_width(const char *str) {
  if (!str)
    return 0;
  if (!strlen(str))
    return 0;
  if (str[0] == '\t')
    return 4;
  unicode_width_state_t state;
  unicode_width_init(&state);
  int width = 0;
  for (size_t j = 0; j < strlen(str); j++) {
    unsigned char c = str[j];
    if (c < 128) {
      int char_width = unicode_width_process(&state, c);
      if (char_width > 0)
        width += char_width;
    } else {
      uint_least32_t cp;
      size_t bytes = grapheme_decode_utf8(str + j, strlen(str) - j, &cp);
      if (bytes > 1) {
        int char_width = unicode_width_process(&state, cp);
        if (char_width > 0)
          width += char_width;
        j += bytes - 1;
      }
    }
  }

  return width;
}

Line split_graphemes(const char *input) {
  Line line = {0};
  if (!input)
    return line;
  size_t count = 0;
  size_t off = 0, ret;
  while (input[off] != '\0') {
    ret = grapheme_next_character_break_utf8(input + off, SIZE_MAX);
    if (ret == 0)
      break;
    off += ret;
    count++;
  }
  if (count == 0)
    return line;
  line.clusters = calloc(count, sizeof(Cluster));
  if (!line.clusters)
    return line;
  line.cluster_count = count;
  off = 0;
  for (size_t i = 0; i < count; i++) {
    ret = grapheme_next_character_break_utf8(input + off, SIZE_MAX);
    if (ret == 0)
      break;
    line.clusters[i].utf8 = calloc(ret + 1, sizeof(char));
    if (!line.clusters[i].utf8) {
      for (size_t j = 0; j < i; j++)
        free(line.clusters[j].utf8);
      free(line.clusters);
      line.clusters = NULL;
      line.cluster_count = 0;
      return line;
    }
    memcpy(line.clusters[i].utf8, input + off, ret);
    line.clusters[i].utf8[ret] = '\0';
    line.clusters[i].width = display_width(line.clusters[i].utf8);
    off += ret;
  }
  return line;
}

size_t grapheme_to_byte_col(Line *line, size_t col) {
  if (col > line->cluster_count)
    col = line->cluster_count;
  size_t b = 0;
  for (size_t i = 0; i < col; i++) {
    b += strlen(line->clusters[i].utf8);
  }
  return b;
}

size_t grapheme_to_byte(Line *line) {
  size_t b = 0;
  for (size_t i = 0; i < line->cluster_count; i++)
    b += strlen(line->clusters[i].utf8);
  return b;
}

size_t byte_to_grapheme(Line *line, size_t b) {
  size_t g = 0;
  for (size_t i = 0; i < line->cluster_count; i++) {
    if (b < strlen(line->clusters[i].utf8))
      return g;
    b -= strlen(line->clusters[i].utf8);
    g++;
  }
  return g;
}
