#include "snake.h"
#include "helper_functions.h"
#include "main.h"
#include "node.h"
#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <termios.h>
#include <unistd.h>

struct termios orig_termios;

ScreenCell *old_screen;
ScreenCell *screen;

const Color colors[10] = {
    {24, 28, 36},    // bg       → darker, deep navy
    {68, 76, 102},   // dark     → muted but distinct
    {255, 85, 85},   // red      → bright, warm red
    {255, 160, 85},  // orange   → vibrant orange
    {255, 220, 105}, // yellow   → sunny, contrasting yellow
    {102, 217, 239}, // green    → cyan-green, pops on dark bg
    {130, 170, 255}, // cyan     → bright cyan-blue
    {120, 160, 255}, // blue     → vivid but softer than cyan
    {248, 248, 242}, // white    → near-white, soft on dark bg
    {180, 135, 255}  // purple   → vibrant violet
};

int highlights_count = 0;

int rows, cols;

static void enable_raw_mode();
static void free_screen_cells(ScreenCell *buf);

void insert_screen(int row, int col, char *utf8, const Color *fg,
                   const Color *bg, uint8_t flags) {
  if (row < 0 || row >= rows || col < 0 || col >= cols)
    return;
  int idx = row * cols + col;
  pthread_mutex_lock(&screen_mutex);
  if (screen[idx].utf8)
    free(screen[idx].utf8);
  screen[idx].utf8 = strdup(utf8);
  screen[idx].fg = fg;
  screen[idx].bg = bg;
  screen[idx].flags = flags;
  pthread_mutex_unlock(&screen_mutex);
}

void put_screen() {
  static _Bool first_render = 1;
  const Color *current_fg = NULL;
  const Color *current_bg = NULL;
  _Bool current_italic = 0;
  _Bool current_bold = 0;
  _Bool current_underline = 0;
  pthread_mutex_lock(&screen_mutex);
  printf("\x1b[s\x1b[?25l");
  if (first_render) {
    printf("\x1b[2J\x1b[H");
    first_render = 0;
  }
  for (int row = 0; row < rows; row++) {
    int first_change_col = -1;
    int last_change_col = -1;
    for (int col = 0; col < cols; col++) {
      int idx = row * cols + col;
      ScreenCell *old_cell = &old_screen[idx];
      ScreenCell *new_cell = &screen[idx];
      _Bool content_changed =
          (old_cell->utf8 == NULL && new_cell->utf8 != NULL) ||
          (old_cell->utf8 != NULL && new_cell->utf8 == NULL) ||
          (old_cell->utf8 != NULL && new_cell->utf8 != NULL &&
           strcmp(old_cell->utf8, new_cell->utf8) != 0);
      _Bool style_changed =
          (old_cell->fg != new_cell->fg) || (old_cell->bg != new_cell->bg) ||
          ((old_cell->flags & 1) != (new_cell->flags & 1)) ||
          (((old_cell->flags >> 1) & 1) != ((new_cell->flags >> 1) & 1)) ||
          (((old_cell->flags >> 2) & 1) != ((new_cell->flags >> 2) & 1));
      if (content_changed || style_changed) {
        if (first_change_col == -1)
          first_change_col = col;
        last_change_col = col;
      }
    }
    if (first_change_col == -1)
      continue;
    printf("\x1b[%d;%dH", row + 1, first_change_col + 1);
    for (int col = first_change_col; col <= last_change_col; col++) {
      int idx = row * cols + col;
      ScreenCell *old_cell = &old_screen[idx];
      ScreenCell *new_cell = &screen[idx];
      if (current_fg != new_cell->fg) {
        if (new_cell->fg)
          printf("\x1b[38;2;%d;%d;%dm", new_cell->fg->r, new_cell->fg->g,
                 new_cell->fg->b);
        else
          printf("\x1b[39m");
        current_fg = new_cell->fg;
      }
      if (current_bg != new_cell->bg) {
        if (new_cell->bg)
          printf("\x1b[48;2;%d;%d;%dm", new_cell->bg->r, new_cell->bg->g,
                 new_cell->bg->b);
        else
          printf("\x1b[49m");
        current_bg = new_cell->bg;
      }
      _Bool italic = new_cell->flags & 1;
      if (italic != current_italic) {
        printf(italic ? "\x1b[3m" : "\x1b[23m");
        current_italic = italic;
      }
      _Bool bold = (new_cell->flags >> 1) & 1;
      if (bold != current_bold) {
        printf(bold ? "\x1b[1m" : "\x1b[22m");
        current_bold = bold;
      }
      _Bool underline = (new_cell->flags >> 2) & 1;
      if (underline != current_underline) {
        printf(underline ? "\x1b[4m" : "\x1b[24m");
        current_underline = underline;
      }
      if (new_cell->utf8 && new_cell->utf8[0] != '\0' &&
          new_cell->utf8[0] != '\t')
        printf("%s", new_cell->utf8);
      else if (new_cell->utf8 && new_cell->utf8[0] == '\t')
        printf("    ");
      else
        printf(" ");
      if (old_cell->utf8)
        free(old_cell->utf8);
      old_cell->utf8 = new_cell->utf8 ? strdup(new_cell->utf8) : NULL;
      old_cell->fg = new_cell->fg;
      old_cell->bg = new_cell->bg;
      old_cell->flags = new_cell->flags;
    }
  }
  pthread_mutex_unlock(&screen_mutex);
  printf("\x1b[0m");
  printf("\x1b[u\x1b[?25h");
  fflush(stdout);
}

void start_screen() {
  enable_raw_mode();
  get_terminal_size(&rows, &cols);
  old_screen = calloc(rows * cols, sizeof(ScreenCell));
  screen = calloc(rows * cols, sizeof(ScreenCell));
  for (int i = 0; i < rows * cols; i++) {
    old_screen[i].utf8 = NULL;
    screen[i].utf8 = NULL;
  }
  init_root_node();
}

static void free_screen_cells(ScreenCell *buf) {
  if (!buf)
    return;
  for (int i = 0; i < rows * cols; i++) {
    free(buf[i].utf8);
    buf[i].utf8 = NULL;
  }
}

void end_screen() {
  free_screen_cells(screen);
  free_screen_cells(old_screen);
  free(screen);
  free(old_screen);
  free_all_nodes(&root);
  disable_raw_mode();
  printf("Program exited\n");
}

void disable_raw_mode() {
  printf("\x1b[?1049l");
  printf("\x1b[?25h");
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
    die("tcsetattr");
}

static void enable_raw_mode() {
  if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
    die("tcgetattr");
  atexit(disable_raw_mode);

  struct termios raw = orig_termios;
  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  raw.c_oflag &= ~(OPOST);
  raw.c_cflag |= (CS8);
  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  raw.c_cc[VMIN] = 0;
  raw.c_cc[VTIME] = 0;

  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
    die("tcsetattr");

  printf("\x1b[?1049h");
  printf("\x1b[2 q");
  printf("\x1b[?1002h");
  printf("\x1b[?25l");
}
