#include <stdio.h>
#include <string.h>

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>

#include "vterm.h"

#define DEFINE_INLINES
#include "../src/utf8.h" // fill_utf8

#define streq(a,b) (!strcmp(a,b))

static VTerm *vt;
static VTermScreen *vts;

static int cols;
static int rows;

static enum {
  FORMAT_PLAIN,
  FORMAT_SGR,
} format = FORMAT_PLAIN;

static int col2index(VTermColor target)
{
  int index;

  for(index = 0; index < 256; index++) {
    VTermColor col;
    vterm_state_get_palette_color(NULL, index, &col);
    if(col.red == target.red && col.green == target.green && col.blue == target.blue)
      return index;
  }
  return -1;
}

static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell)
{
  switch(format) {
    case FORMAT_PLAIN:
      break;
    case FORMAT_SGR:
      {
        // If all 7 attributes change, that means 7 SGRs max
        // Each colour could consume up to 3
        int sgr[7 + 2*3]; int sgri = 0;

        if(!prevcell->attrs.bold && cell->attrs.bold)
          sgr[sgri++] = 1;
        if(prevcell->attrs.bold && !cell->attrs.bold)
          sgr[sgri++] = 22;

        if(!prevcell->attrs.underline && cell->attrs.underline)
          sgr[sgri++] = 4;
        if(prevcell->attrs.underline && !cell->attrs.underline)
          sgr[sgri++] = 24;

        if(!prevcell->attrs.italic && cell->attrs.italic)
          sgr[sgri++] = 3;
        if(prevcell->attrs.italic && !cell->attrs.italic)
          sgr[sgri++] = 23;

        if(!prevcell->attrs.blink && cell->attrs.blink)
          sgr[sgri++] = 5;
        if(prevcell->attrs.blink && !cell->attrs.blink)
          sgr[sgri++] = 25;

        if(!prevcell->attrs.reverse && cell->attrs.reverse)
          sgr[sgri++] = 7;
        if(prevcell->attrs.reverse && !cell->attrs.reverse)
          sgr[sgri++] = 27;

        if(!prevcell->attrs.strike && cell->attrs.strike)
          sgr[sgri++] = 9;
        if(prevcell->attrs.strike && !cell->attrs.strike)
          sgr[sgri++] = 29;

        if(!prevcell->attrs.font && cell->attrs.font)
          sgr[sgri++] = 10 + cell->attrs.font;
        if(prevcell->attrs.font && !cell->attrs.font)
          sgr[sgri++] = 10;

        if(prevcell->fg.red   != cell->fg.red   ||
            prevcell->fg.green != cell->fg.green ||
            prevcell->fg.blue  != cell->fg.blue) {
          int index = col2index(cell->fg);
          if(index == -1)
            sgr[sgri++] = 39;
          else if(index < 8)
            sgr[sgri++] = 30 + index;
          else if(index < 16)
            sgr[sgri++] = 90 + (index - 8);
          else {
            sgr[sgri++] = 38;
            sgr[sgri++] = 5 | CSI_ARG_FLAG_MORE;
            sgr[sgri++] = index | CSI_ARG_FLAG_MORE;
          }
        }

        if(prevcell->bg.red   != cell->bg.red   ||
            prevcell->bg.green != cell->bg.green ||
            prevcell->bg.blue  != cell->bg.blue) {
          int index = col2index(cell->bg);
          if(index == -1)
            sgr[sgri++] = 49;
          else if(index < 8)
            sgr[sgri++] = 40 + index;
          else if(index < 16)
            sgr[sgri++] = 100 + (index - 8);
          else {
            sgr[sgri++] = 48;
            sgr[sgri++] = 5 | CSI_ARG_FLAG_MORE;
            sgr[sgri++] = index | CSI_ARG_FLAG_MORE;
          }
        }

        if(!sgri)
          break;

        printf("\x1b[");
	{
	  int i;
	  for(i = 0; i < sgri; i++)
	    printf(!i               ? "%d" :
		CSI_ARG_HAS_MORE(sgr[i]) ? ":%d" :
		";%d",
		CSI_ARG(sgr[i]));
	}
        printf("m");
      }
      break;
  }

  {
    int i;
    for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
      char bytes[6];
      bytes[fill_utf8(cell->chars[i], bytes)] = 0;
      printf("%s", bytes);
    }
  }
}

static void dump_eol(const VTermScreenCell *prevcell)
{
  switch(format) {
    case FORMAT_PLAIN:
      break;
    case FORMAT_SGR:
      if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic ||
         prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike ||
         prevcell->attrs.font)
        printf("\x1b[m");
      break;
  }

  printf("\n");
}

void dump_row(int row)
{
  VTermPos pos;
  VTermScreenCell prevcell;
  pos.row = row;
  pos.col = 0;
  memset(&prevcell, 0, sizeof(prevcell));
  vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);

  while(pos.col < cols) {
    VTermScreenCell cell;
    vterm_screen_get_cell(vts, pos, &cell);

    dump_cell(&cell, &prevcell);

    pos.col += cell.width;
    prevcell = cell;
  }

  dump_eol(&prevcell);
}

static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
{
  VTermScreenCell prevcell;
  int col;

  memset(&prevcell, 0, sizeof(prevcell));
  vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);

  for(col = 0; col < cols; col++) {
    dump_cell(cells + col, &prevcell);
    prevcell = cells[col];
  }

  dump_eol(&prevcell);

  return 1;
}

static int screen_resize(int new_rows, int new_cols, void *user)
{
  rows = new_rows;
  cols = new_cols;
  return 1;
}

static VTermScreenCallbacks cb_screen = {
  NULL, // damage
  NULL, // moverect
  NULL, // movecursor
  NULL, // settermprop
  NULL, // bell
  &screen_resize, // resize
  &screen_sb_pushline, // sb_pushline
  NULL, // popline
};

int main(int argc, char *argv[])
{
  int opt;
  const char *file;
  int fd;
  int len;
  char buffer[1024];
  int row;

  rows = 25;
  cols = 80;

  while((opt = getopt(argc, argv, "f:l:c:")) != -1) {
    switch(opt) {
      case 'f':
        if(streq(optarg, "plain"))
          format = FORMAT_PLAIN;
        else if(streq(optarg, "sgr"))
          format = FORMAT_SGR;
        else {
          fprintf(stderr, "Unrecognised format '%s'\n", optarg);
          exit(1);
        }
        break;

      case 'l':
        rows = atoi(optarg);
        if(!rows)
          rows = 25;
        break;

      case 'c':
        cols = atoi(optarg);
        if(!cols)
          cols = 80;
        break;
    }
  }

  file = argv[optind++];
  fd = open(file, O_RDONLY);
  if(fd == -1) {
    fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
    exit(1);
  }

  vt = vterm_new(rows, cols);
  vterm_set_utf8(vt, TRUE);

  vts = vterm_obtain_screen(vt);
  vterm_screen_set_callbacks(vts, &cb_screen, NULL);

  vterm_screen_reset(vts, 1);

  while((len = read(fd, buffer, sizeof(buffer))) > 0) {
    vterm_input_write(vt, buffer, len);
  }

  for(row = 0; row < rows; row++) {
    dump_row(row);
  }

  close(fd);

  vterm_free(vt);

  return 0;
}
