#include "main.h"
#include "constants.h"
#include "editor.h"
#include "helper_functions.h"
#include "highlighters.h"
#include "lsp_handler.h"
#include "node.h"
#include "snake.h"
#include <fcntl.h>
#include <libgen.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <termios.h>
#include <unistd.h>

uint8_t mode = NORMAL;
pthread_mutex_t screen_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
volatile int buffer_dirty = 0;

int read_input(char *buf, size_t buflen) {
  size_t i = 0;
  int n;
  n = read(STDIN_FILENO, &buf[i], 1);
  if (n <= 0)
    return -1;
  i++;
  if (buf[0] == '\x1b') {
    while (i < buflen - 1) {
      n = read(STDIN_FILENO, &buf[i], 1);
      if (n <= 0)
        break;
      i++;
    }
  }
  buf[i] = '\0';
  return i;
}

void capture_mouse(char *buf, KeyEvent *ret) {
  uint8_t byte = buf[3];
  ret->mouse.modifier = (byte >> 3) & 0x03;
  uint8_t aa = (byte >> 5) & 0x03;
  uint8_t cc = byte & 0x03;
  ret->mouse.x = buf[4] - 32;
  ret->mouse.y = buf[5] - 32;
  if (aa == 1 && cc == 3) {
    ret->mouse.state = RELEASE;
    ret->mouse.button = NONE_BTN;
  } else if (aa == 1) {
    ret->mouse.state = PRESS;
    ret->mouse.button = cc;
  } else if (aa == 2) {
    ret->mouse.state = DRAG;
    ret->mouse.button = cc;
  } else if (aa == 3) {
    ret->mouse.state = SCROLL;
    ret->mouse.button = cc;
  } else {
    ret->mouse.state = RELEASE;
    ret->mouse.button = NONE_BTN;
  }
}

void read_key(KeyEvent *ret) {
  char buf[7];
  int n = read_input(buf, sizeof(buf));
  if (n <= 0) {
    ret->key_type = KEY_NONE;
    ret->c = '\0';
    return;
  }
  if (n == 1) {
    ret->key_type = KEY_CHAR;
    ret->c = buf[0];
  } else if (buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') {
    ret->key_type = KEY_MOUSE;
    capture_mouse(buf, ret);
  } else {
    ret->key_type = KEY_SPECIAL;
    if (buf[0] == '\x1b' && buf[1] == '[') {
      _Bool using_modifiers = buf[3] == ';';
      int pos;
      if (!using_modifiers) {
        pos = 2;
        ret->special.modifier = 0;
      } else {
        pos = 4;
        switch (buf[3]) {
        case '2':
          ret->special.modifier = SHIFT;
          break;
        case '3':
          ret->special.modifier = ALT;
          break;
        case '5':
          ret->special.modifier = CNTRL;
          break;
        case '7':
          ret->special.modifier = CNTRL_ALT;
          break;
        default:
          ret->special.modifier = 0;
          break;
        }
      }
      switch (buf[pos]) {
      case 'A':
        ret->special.key = KEY_UP;
        break;
      case 'B':
        ret->special.key = KEY_DOWN;
        break;
      case 'C':
        ret->special.key = KEY_RIGHT;
        break;
      case 'D':
        ret->special.key = KEY_LEFT;
        break;
      case '3':
        ret->special.key = KEY_DELETE;
        break;
      default:
        ret->special.key = 99;
        break;
      }
    }
  }
}

void *clipboard_get_thread(void *arg) {
  (void)arg;
  FILE *fp = popen("xclip -selection clipboard -o", "r");
  if (!fp)
    return NULL;
  size_t capacity = 1024;
  size_t len = 0;
  char *buf = malloc(capacity);
  if (!buf) {
    pclose(fp);
    return NULL;
  }
  int c;
  while ((c = fgetc(fp)) != EOF) {
    if (len + 1 >= capacity) {
      capacity *= 2;
      char *tmp = realloc(buf, capacity);
      if (!tmp) {
        free(buf);
        pclose(fp);
        return NULL;
      }
      buf = tmp;
    }
    buf[len++] = (char)c;
  }
  buf[len] = '\0';
  pclose(fp);
  pthread_mutex_lock(&buffer_mutex);
  Coords coords = insert_text(root.Root.focused, buf,
                              root.Root.focused->Buffer.data->cursor_row,
                              root.Root.focused->Buffer.data->cursor_col);
  set_cursor(root.Root.focused, coords.row, coords.col);
  buffer_dirty = 1;
  pthread_mutex_unlock(&buffer_mutex);
  free(buf);
  return NULL;
}

void *clipboard_get_thread_replace(void *arg) {
  (void)arg;
  FILE *fp = popen("xclip -selection clipboard -o", "r");
  if (!fp)
    return NULL;
  size_t capacity = 1024;
  size_t len = 0;
  char *buf = malloc(capacity);
  if (!buf) {
    pclose(fp);
    return NULL;
  }
  int c;
  while ((c = fgetc(fp)) != EOF) {
    if (len + 1 >= capacity) {
      capacity *= 2;
      char *tmp = realloc(buf, capacity);
      if (!tmp) {
        free(buf);
        pclose(fp);
        return NULL;
      }
      buf = tmp;
    }
    buf[len++] = (char)c;
  }
  buf[len] = '\0';
  pclose(fp);
  pthread_mutex_lock(&buffer_mutex);
  Coords coords;
  BufferData *e = root.Root.focused->Buffer.data;
  int start_row = e->selection_start_row;
  int start_col = e->selection_start_col;
  int end_row = e->cursor_row;
  int end_col = e->cursor_col;
  if (start_row > end_row || (start_row == end_row && start_col > end_col)) {
    int tmp_row = start_row, tmp_col = start_col;
    start_row = end_row;
    start_col = end_col;
    end_row = tmp_row;
    end_col = tmp_col;
  }
  end_col += 1;
  coords = replace_text(root.Root.focused, buf, start_row, start_col, end_row,
                        end_col);
  set_cursor(root.Root.focused, coords.row, coords.col);
  end_selection(root.Root.focused);
  mode = NORMAL;
  buffer_dirty = 1;
  pthread_mutex_unlock(&buffer_mutex);
  free(buf);
  return NULL;
}

void *clipboard_set_thread(void *text) {
  text = (char *)text;
  FILE *fp = popen("xclip -selection clipboard", "w");
  if (!fp)
    return NULL;
  fputs(text, fp);
  pclose(fp);
  free(text);
  return NULL;
}

void *input_thread(void *arg) {
  UNUSED(arg);
  KeyEvent key;
  while (1) {
    usleep(1000);
    memset(&key, 0, sizeof(key));
    read_key(&key);
    pthread_mutex_lock(&buffer_mutex);
    if (key.key_type == KEY_CHAR && key.c == CTRL_KEY('q'))
      break;
    if (key.key_type == KEY_CHAR &&
        ((key.c >= 'a' && key.c <= 'z') || (key.c >= 'A' && key.c <= 'Z') ||
         (key.c >= '0' && key.c <= '9') || key.c == ' ' || key.c == '!' ||
         key.c == '@' || key.c == '#' || key.c == '$' || key.c == '%' ||
         key.c == '^' || key.c == '&' || key.c == '*' || key.c == '(' ||
         key.c == ')' || key.c == '-' || key.c == '_' || key.c == '=' ||
         key.c == '+' || key.c == '[' || key.c == ']' || key.c == '{' ||
         key.c == '}' || key.c == '\\' || key.c == '|' || key.c == ';' ||
         key.c == ':' || key.c == '\'' || key.c == '"' || key.c == ',' ||
         key.c == '.' || key.c == '<' || key.c == '>' || key.c == '/' ||
         key.c == '?' || key.c == '`' || key.c == '~')) {
      if (mode == INSERT) {
        char *c = malloc(2);
        c[0] = key.c;
        c[1] = '\0';
        Coords ret = insert_text(root.Root.focused, c,
                                 root.Root.focused->Buffer.data->cursor_row,
                                 root.Root.focused->Buffer.data->cursor_col);
        set_cursor(root.Root.focused, ret.row, ret.col);
        buffer_dirty = 1;
        free(c);
      }
    }
    if (key.key_type == KEY_CHAR && key.c == '\t') {
      if (mode == INSERT) {
        char *c = malloc(3);
        c[0] = ' ';
        c[1] = ' ';
        c[2] = '\0';
        Coords ret = insert_text(root.Root.focused, c,
                                 root.Root.focused->Buffer.data->cursor_row,
                                 root.Root.focused->Buffer.data->cursor_col);
        set_cursor(root.Root.focused, ret.row, ret.col);
        buffer_dirty = 1;
        free(c);
      }
    }
    if (key.key_type == KEY_CHAR && (key.c == '\r' || key.c == '\n')) {
      Coords ret = insert_text(root.Root.focused, "\n",
                               root.Root.focused->Buffer.data->cursor_row,
                               root.Root.focused->Buffer.data->cursor_col);
      set_cursor(root.Root.focused, ret.row, ret.col);
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_SPECIAL && key.special.key == KEY_DELETE) {
      if (mode == INSERT) {
        Coords ret = delete_text(root.Root.focused, -1,
                                 root.Root.focused->Buffer.data->cursor_row,
                                 root.Root.focused->Buffer.data->cursor_col);
        set_cursor(root.Root.focused, ret.row, ret.col);
      } else {
        move_cursor(root.Root.focused, 1, 0);
      }
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_CHAR && key.c == 'x') {
      if (mode == NORMAL) {
        Coords ret = delete_text(root.Root.focused, -1,
                                 root.Root.focused->Buffer.data->cursor_row,
                                 root.Root.focused->Buffer.data->cursor_col);
        set_cursor(root.Root.focused, ret.row, ret.col);
        buffer_dirty = 1;
      } else if (mode == SELECT) {
        Coords coords;
        BufferData *e = root.Root.focused->Buffer.data;
        int start_row = e->selection_start_row;
        int start_col = e->selection_start_col;
        int end_row = e->cursor_row;
        int end_col = e->cursor_col;
        if (start_row > end_row ||
            (start_row == end_row && start_col > end_col)) {
          int tmp_row = start_row, tmp_col = start_col;
          start_row = end_row;
          start_col = end_col;
          end_row = tmp_row;
          end_col = tmp_col;
        }
        end_col += 1;
        coords = replace_text(root.Root.focused, "", start_row, start_col,
                              end_row, end_col);
        set_cursor(root.Root.focused, coords.row, coords.col);
        end_selection(root.Root.focused);
        mode = NORMAL;
        buffer_dirty = 1;
      }
    }
    if (key.key_type == KEY_CHAR && key.c == 'c') {
      if (mode == NORMAL) {
        Coords ret = delete_text(root.Root.focused, -1,
                                 root.Root.focused->Buffer.data->cursor_row,
                                 root.Root.focused->Buffer.data->cursor_col);
        set_cursor(root.Root.focused, ret.row, ret.col);
        mode = INSERT;
        printf("\x1b[5 q");
        buffer_dirty = 1;
      } else if (mode == SELECT) {
        Coords coords;
        BufferData *e = root.Root.focused->Buffer.data;
        int start_row = e->selection_start_row;
        int start_col = e->selection_start_col;
        int end_row = e->cursor_row;
        int end_col = e->cursor_col;
        if (start_row > end_row ||
            (start_row == end_row && start_col > end_col)) {
          int tmp_row = start_row, tmp_col = start_col;
          start_row = end_row;
          start_col = end_col;
          end_row = tmp_row;
          end_col = tmp_col;
        }
        end_col += 1;
        coords = replace_text(root.Root.focused, "", start_row, start_col,
                              end_row, end_col);
        set_cursor(root.Root.focused, coords.row, coords.col);
        end_selection(root.Root.focused);
        mode = INSERT;
        printf("\x1b[5 q");
        buffer_dirty = 1;
      }
    }
    if (key.key_type == KEY_CHAR && key.c == 0x7F) {
      if (mode == INSERT) {
        Coords ret = delete_text(root.Root.focused, 1,
                                 root.Root.focused->Buffer.data->cursor_row,
                                 root.Root.focused->Buffer.data->cursor_col);
        set_cursor(root.Root.focused, ret.row, ret.col);
      } else {
        move_cursor(root.Root.focused, -1, 0);
      }
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_CHAR && key.c == 'v') {
      if (mode == NORMAL) {
        mode = SELECT;
        start_selection(root.Root.focused);
        buffer_dirty = 1;
      }
    }
    if (key.key_type == KEY_CHAR && (key.c == 'i' || key.c == 'a')) {
      if (mode == NORMAL) {
        mode = INSERT;
        printf("\x1b[5 q");
        if (key.c == 'a')
          move_cursor(root.Root.focused, 1, 0);
        buffer_dirty = 1;
      }
    }
    if (key.key_type == KEY_CHAR && key.c == KEY_ESC) {
      if (mode == SELECT)
        end_selection(root.Root.focused);
      if (mode == INSERT)
        move_cursor(root.Root.focused, -1, 0);
      printf("\x1b[2 q");
      mode = NORMAL;
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_CHAR && key.c == 'p') {
      if (mode == NORMAL) {
        pthread_t paste_thread;
        pthread_create(&paste_thread, NULL, clipboard_get_thread, NULL);
      } else if (mode == SELECT) {
        pthread_t paste_thread;
        pthread_create(&paste_thread, NULL, clipboard_get_thread_replace, NULL);
      }
    }
    if (key.key_type == KEY_CHAR && key.c == 'y') {
      if (mode == SELECT) {
        char *text = get_selection(root.Root.focused);
        pthread_t copy_thread;
        pthread_create(&copy_thread, NULL, clipboard_set_thread, text);
        end_selection(root.Root.focused);
        mode = NORMAL;
        buffer_dirty = 1;
      }
    }
    if (key.key_type == KEY_CHAR && key.c == '\'') {
      if (mode == NORMAL) {
        int lineno = root.Root.focused->Buffer.data->cursor_row;
        for (int i = 0; i < root.Root.focused->Buffer.data->fold_count; i++) {
          if (lineno == root.Root.focused->Buffer.data->folds[i].start) {
            root.Root.focused->Buffer.data->folds[i].folded =
                !root.Root.focused->Buffer.data->folds[i].folded;
            break;
          }
        }
        buffer_dirty = 1;
      }
    }
    if (key.key_type == KEY_MOUSE && key.mouse.button == LEFT_BTN &&
        key.mouse.state == PRESS) {
      if (mode == SELECT) {
        end_selection(root.Root.focused);
        mode = NORMAL;
      }
      move_cursor_to_mouse(root.Root.focused,
                           key.mouse.x - root.Root.focused->Buffer.calc_x - 1,
                           key.mouse.y - root.Root.focused->Buffer.calc_y - 1);
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_MOUSE && key.mouse.button == LEFT_BTN &&
        key.mouse.state == DRAG) {
      if (mode == NORMAL) {
        start_selection(root.Root.focused);
        mode = SELECT;
      }
      move_cursor_to_mouse(root.Root.focused,
                           key.mouse.x - root.Root.focused->Buffer.calc_x - 1,
                           key.mouse.y - root.Root.focused->Buffer.calc_y - 1);
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_MOUSE && key.mouse.state == SCROLL &&
        key.mouse.button == SCROLL_DOWN) {
      scroll_buffer(root.Root.focused, 5, 0);
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_MOUSE && key.mouse.state == SCROLL &&
        key.mouse.button == SCROLL_UP) {
      scroll_buffer(root.Root.focused, -5, 0);
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_MOUSE && key.mouse.state == SCROLL &&
        key.mouse.button == SCROLL_RIGHT) {
      scroll_buffer(root.Root.focused, 0, 5);
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_MOUSE && key.mouse.state == SCROLL &&
        key.mouse.button == SCROLL_LEFT) {
      scroll_buffer(root.Root.focused, 0, -5);
      buffer_dirty = 1;
    }
    if (key.key_type == KEY_SPECIAL && key.special.modifier == 0) {
      switch (key.special.key) {
      case KEY_UP:
        move_cursor(root.Root.focused, 0, -1);
        buffer_dirty = 1;
        break;
      case KEY_DOWN:
        move_cursor(root.Root.focused, 0, 1);
        buffer_dirty = 1;
        break;
      case KEY_LEFT:
        move_cursor(root.Root.focused, -1, 0);
        buffer_dirty = 1;
        break;
      case KEY_RIGHT:
        move_cursor(root.Root.focused, 1, 0);
        buffer_dirty = 1;
        break;
      }
    }
    if (key.key_type == KEY_CHAR && key.c == 'h') {
      if (mode == NORMAL) {
        lsp_hover(root.Root.focused, root.Root.focused->Buffer.data->cursor_row,
                  root.Root.focused->Buffer.data->cursor_col);
      }
    }
    pthread_mutex_unlock(&buffer_mutex);
  }
  return NULL;
}

void *draw_thread(void *arg) {
  UNUSED(arg);
  while (1) {
    if (buffer_dirty) {
      buffer_dirty = 0;
      pthread_mutex_lock(&buffer_mutex);
      draw_buffer(root.Root.focused);
      pthread_mutex_unlock(&buffer_mutex);
    }
    put_screen();
    usleep(32000);
  }
  return NULL;
}

int main(int argc, char **argv) {
  start_screen();

  Node *window;
  window = create_window(0, 0, cols, rows, 0, VERTICAL);
  Node *top_block;
  top_block = create_block(window->Window.root_block_node, HORIZONTAL, 0);
  Node *editor_buffer;
  BufferData *data = init_buffer();
  char full_path[PATH_MAX];
  if (!realpath(argv[1], full_path)) {
    perror("realpath failed");
    return 1;
  }
  editor_buffer =
      create_buffer(top_block, colors + WHITE, colors + BG, 0, data);
  char *text = read_file(full_path);
  insert_text(editor_buffer, text, 0, 0);
  free(text);
  editor_buffer->Buffer.filename = full_path;
  char *folder = strdup(full_path);
  dirname(folder);
  editor_buffer->Buffer.folder = folder;
  root.Root.focused = editor_buffer;
  calculate_block(window->Window.root_block_node);
  editor_buffer->Buffer.hl_func_basic = word_highlighter;
  editor_buffer->Buffer.hl_func_syntax = tree_sitter_highlighter;
  editor_buffer->Buffer.data->lsp_change_func = lsp_change;
  data->lang = file_type(full_path);
  lsp_setup(editor_buffer, data->lang);
  draw_buffer(editor_buffer);

  pthread_t input_thread_id;
  pthread_t draw_thread_id;
  if (pthread_create(&input_thread_id, NULL, input_thread, NULL) != 0)
    die("pthread_create failed for input thread");
  if (pthread_create(&draw_thread_id, NULL, draw_thread, NULL) != 0)
    die("pthread_create failed for draw thread");

  pthread_join(input_thread_id, NULL);
  pthread_cancel(draw_thread_id);
  pthread_join(draw_thread_id, NULL);
  end_screen();
  return 0;
}
