// todo esc handling slightly broken

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

#include <assert.h>
#include <ctype.h>
//#include "trie.h"

int keydebug = FALSE;

// to do - flushinp() to cancel type-ahead when there is an ecce error reported
// (and then wait for the user to see it. (Enter?) (Issue ^G too?)

// current serious bug: entering an ecce command of all spaces (or
// any equivalent ignorable noise characters) causes a lock-up.  probably
// looping looking for more data and getting an EOF it doesn't handle

// entering a number to repeat the last command unfortunately repeats
// the last immediate-mode command too, because things like cursor-right
// are implemented as callbacks to ecce commands.  Fix by recording
// the last entered ecce command, and re-issuing it on a number.
// need to take care re 'hidden' ecce commands contained in keystroke
// macro expansions.

char *expand[256] = {
#include "expand.h"
};

static int default_highlighting = TRUE;

void inverted_screen(void)
{
  default_highlighting = !default_highlighting;
}

void highlight(void)
{
  if (!default_highlighting) standend(); else standout();
}

void normal(void)
{
  if (default_highlighting) standend(); else standout();
}

typedef struct virtual_screen_line {
  char *sp; // pointer to next byte to be displayed on screen
  char *st; // partial text of multi-char expansion from
            // previous line.
} virtual_screen_line;

void getsize(char *fname, int *MAX_BUF_BYTES, int *MAX_DISPLAY_LINES)
{
  FILE *tmp = fopen(fname, "rw");
  *MAX_BUF_BYTES = -1; *MAX_DISPLAY_LINES = -1;
  if (tmp == NULL) return;
  fseek(tmp, 0L, SEEK_END);
  *MAX_BUF_BYTES = (int)ftell(tmp);
  fclose(tmp);
  *MAX_BUF_BYTES = (*MAX_BUF_BYTES + 1024*256) * 3;
  *MAX_DISPLAY_LINES = *MAX_BUF_BYTES / 8; /* approx, worst case eg for dict files */
}

// virtual screen variables
virtual_screen_line *l;

// ECCE variables
char *a;
char *fbeg, *lbeg, *pp, *fp, *lend, *fend;
extern char ecce_err[256];
extern void ecinner(char *command);
void ecinnerf(char *format, int num) // HACK
{
  char command[256];
  sprintf(command, format, num);
  ecinner(command);
}

// display (sliding cursor window) variables
int FIRST_DISPLAY_LINE = 0;
int LAST_DISPLAY_LINE = 23;
static int preferred_display_line = 0;
int DISPLAY_LEFTMOST_COLUMN = 0;
int DISPLAY_RIGHTMOST_COLUMN = 79;

void allocatebuffer(int MAX_BUF_BYTES, int MAX_DISPLAY_LINES)
{
  a = malloc(MAX_BUF_BYTES);
  l = malloc(sizeof(virtual_screen_line)*MAX_DISPLAY_LINES);
}

int lpend = 0; // temp hacks...
int filesize = 0;
void loadfile(char *fname, int MAX_BUF_BYTES)
{
  FILE *tmp = fopen(fname, "r");
  int c;
  a[0] = '\n';
  lbeg = pp = fbeg = &a[1];
  fp = fend = &a[MAX_BUF_BYTES-1];
  *fend = '\n';
  for (;;) {
    c = fgetc(tmp);
    if (c == EOF) break;
    *pp++ = c;
    filesize += 1;
  }
  fclose(tmp);
  while (pp != fbeg) {
    *--fp = *--pp;
  }
  lend = fp;
  while (*lend != '\n') lend += 1;
}

void generate_screen_line_start_pointers(char *pp, int *this_line, int *this_col)
{
  // at the moment this is only used for finding the screen coords of the
  // cursor, but the main reason for it is for scrolling upwards.  Eventually
  // it won't be invoked more than it needs to be.  This is just a placeholder.
  int c, col;
  char *p;
  p = fbeg;
  lpend = 0;
  l[lpend].sp = p;
  l[lpend].st = "";
  col = 0;
  for (;;) {
    if (p == pp) {
      p = fp;
      *this_col = col;
      *this_line = lpend;
    }
    if (p == fend) break;
    c = (*p++)&255;
    if (c == '\t') {
      int spaces = ((col+8)&(~7))-col; // at least 1
      if (col+spaces > DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) { // wrap
        char *eight = "        ";
        int wrapped_part = (col+spaces)-(DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN); // 1..8
        int this_part = (DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN)-col;
        col += this_part;
        l[lpend+1].st = &eight[8-(wrapped_part-1)];
      } else col += spaces;
    } else if (c != '\n') { // STILL DOES IT???
      if (col+strlen(expand[c]) > DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) { // wrap
        int extra = col+strlen(expand[c]) - (DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN);
        col = DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN;
        l[lpend+1].st = expand[c]+strlen(expand[c])-extra;
      } else {
        col += 1; /* or add width of expansion of c */
        l[lpend+1].st = "";  // may be able to fold this with the case above
      }
    }
    if (c == '\n') { // AHA!  Oops - should be in the display routine, not the scanner!!!
      // NEED TO PAD WITH SPACES TO EOL IF DOING ICL7502-STYLE HIGHLIGHTING
      l[++lpend].sp = p;
      // safety coding:
      if (l[lpend].st == NULL) l[lpend].st = "";
      l[lpend+1].st = "";
      // if we run out of lines, do a realloc() ...
      col = 0;
    } else if (col == DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) {
      l[++lpend].sp = p;
      // safety coding:
      if (l[lpend].st == NULL) l[lpend].st = "";
      l[lpend+1].st = "";
      // if we run out of lines, do a realloc() ...
      col = 0;
    }
  }
}

void update_display(char *pp, int pref_display_line)
{
  int c, x, y, line, this_virtual_screen_line;
  int first_virtual_screen_line,
      cursor_col = 0;

  if (pref_display_line > LAST_DISPLAY_LINE) pref_display_line = LAST_DISPLAY_LINE;
  if (pref_display_line < FIRST_DISPLAY_LINE) pref_display_line = FIRST_DISPLAY_LINE;

  generate_screen_line_start_pointers(pp, &this_virtual_screen_line, &cursor_col); // SIDE-EFFECT: sets lpend

  first_virtual_screen_line = this_virtual_screen_line-pref_display_line+FIRST_DISPLAY_LINE;
  if (first_virtual_screen_line < 0) first_virtual_screen_line = 0;
  if (first_virtual_screen_line > lpend) first_virtual_screen_line = lpend;

  if (first_virtual_screen_line+LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE > lpend) {
    first_virtual_screen_line = lpend - (LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE);
    pref_display_line = LAST_DISPLAY_LINE;
  }
  if (first_virtual_screen_line < 0) first_virtual_screen_line = 0;

  if (this_virtual_screen_line-first_virtual_screen_line < LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE) {
    // requested line too far down page, so move it up
    pref_display_line = first_virtual_screen_line+FIRST_DISPLAY_LINE; // tie to top
  }

  for (y = first_virtual_screen_line; y <= first_virtual_screen_line+LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE; y++) {
    int eol = FALSE;

      char *p;
      int c, col = 0;

      move(y-first_virtual_screen_line+FIRST_DISPLAY_LINE, DISPLAY_LEFTMOST_COLUMN);

      // Output wrapped part of .st string first, if present ...
      p = l[y].st;
      if (p == NULL) break; // coding error
      while (*p != '\0') {
        addch(*p); p += 1; col += 1;
      }
      p = l[y].sp;
      if (p == NULL) break; // coding error
      for (;;) {
        if (p == pp) {
          p = fp;
          cursor_col = DISPLAY_LEFTMOST_COLUMN+col;
          pref_display_line = y-first_virtual_screen_line;
          highlight();
        }
        if (p == fend) {
          normal();
          clrtobot();
          break;
        }
        c = (*p++)&255;
        if (c == '\t') {
          int spaces = ((col+8)&(~7))-col; // at least 1
          if (col+spaces > DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) { // wrap
            int wrapped_part = (col+spaces)-(DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN); // 1..8
            int this_part = (DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN)-col;
            col += this_part;
// DOESN'T INVERSE!            highlight(); clrtoeol(); normal();
            while (this_part > 0) {
              addch(' '); this_part -= 1;
            }
            break;
          } else {
            col += spaces;
// DOESN'T INVERSE!            highlight(); clrtoeol(); normal();
            while (spaces > 0) {
              addch(' '); spaces -= 1;
            }
//            move(y, DISPLAY_LEFTMOST_COLUMN+col); // is this now redundant?
          }
        } else if (c != '\n') {
          char *exp = expand[c];
          while (*exp != '\0') {
            addch(*exp++);
            col += 1;
             if (col == DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) break;
          }
        }
        if (c == '\n' || (col == DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN)) {
          clrtoeol(); normal();
          col = 0; // output wrapped string here?
          break;
        }
      }
  }
  move(pref_display_line, cursor_col);
}

int learn = FALSE;
int waiting_for_key = FALSE;

#include "execute.h"

#include "screen.h"

int main(int argc, char **argv)
{
  struct NODE *base = NULL;
  int *macro_trigger = NULL;
  int *expansion = NULL;
  int macro_trigger_length = 0;
  int expansion_length = 0;
  int macro_trigger_max_length = 0;
  int expansion_max_length = 0;
  int command = -127, lastcommand, count = 0;
  int assigning = FALSE, insequence = FALSE;

  int execute(int i)
  {
    void execute_primitive(int i) {
       if (learn) {
          assert(expansion != NULL);
          if (expansion_length == expansion_max_length) {
            expansion = realloc(expansion, sizeof(int) * (expansion_max_length *= 2));
          }
          assert(expansion_max_length > expansion_length);

          expansion[expansion_length++] = i;

          if (keydebug) fprintf(stderr, "execute+remember %d (%d)\r\n", i, expansion_length);
       }
       screen_edit(i);
    }

    if (command == ('L'&31)) {
      if (learn == FALSE) {
        // assign current sequence to the next key
        if (keydebug) fprintf(stderr, "start recording\r\n");
        if (expansion == NULL) {
          expansion = malloc(sizeof(int) * (expansion_max_length=1));
	}
        assert(expansion_max_length >= 1);
        expansion_length = 0; // should already be 0
        learn = TRUE;
        update_required = TRUE;
        //inverted_screen();
      } else {
        if (keydebug) fprintf(stderr, "end recording, wait for keypress\r\n");
        if (keydebug) {int i;
        fprintf(stderr, "macro expansion sequence:");
        for (i=0; i<expansion_length; i++) fprintf(stderr, " %d", expansion[i]);
        fprintf(stderr, "\r\n");
	}
        learn = FALSE;
        update_required = TRUE;
        waiting_for_key = TRUE;
      }
    } else if (waiting_for_key) {
      static int firsttime = 0;
      if (command == ERR) {
        // ignore...
        if (firsttime == 0) redraw_all(); firsttime = 1;
      } else {
        firsttime = 0;
        // record one more char for the execute sequence
        if (keydebug) fprintf(stderr, "macro trigger start %d\r\n", command);
        if (macro_trigger == NULL) {
          macro_trigger = malloc(sizeof(int) * (macro_trigger_max_length=1));
	}
        assert(macro_trigger_max_length >= 1);
        macro_trigger_length = 0;
        macro_trigger[macro_trigger_length++] = command;
        assigning = TRUE;
        waiting_for_key = FALSE;
        update_required = TRUE;
      }
    } else if (assigning) {
      if (command == ERR) {
        // sequence is complete
        if (keydebug) {int i;
        fprintf(stderr, "macro trigger sequence:");
        for (i=0; i<macro_trigger_length; i++) fprintf(stderr, " %d", macro_trigger[i]);
        fprintf(stderr, "\r\n");
	}
        base = add_sequence(base, macro_trigger, macro_trigger_length, expansion, expansion_length);
        macro_trigger_length = 0;
        expansion_length = 0;
        if (keydebug) fprintf(stderr, "macro trigger done\r\n");
        assigning = FALSE;
      } else {
        // record one more char for the execute sequence
        if (keydebug) fprintf(stderr, "macro trigger key %d\r\n", command);
        assert(macro_trigger != NULL);
        if (macro_trigger_length == macro_trigger_max_length) {
          macro_trigger = realloc(macro_trigger, sizeof(int) * (macro_trigger_max_length *= 2));
	}
        assert(macro_trigger_max_length > macro_trigger_length);
        macro_trigger[macro_trigger_length++] = command;
      }
    } else {
      // execute incoming keys, possibly multi-byte sequence
      if (command == ERR) {
//        if (lastcommand != ERR) fprintf(stderr, "idle\r\n"); // could trap *typed* <esc>[ etc here?
          screen_edit(ERR);
          if (lastcommand == ('C'&31)) return(FALSE);
      } else {
       static struct NODE *current_node = NULL; 
       {
          if (insequence) {
            // does this continue a sequence???
            struct NODE *each;
            int found = FALSE;
            // does this start a sequence???
            each = current_node;
            while (each != NULL) {
              if (each->c == command) {
                found = TRUE;
                current_node = each;
                break;
	      }
              each = each->sibling;
	    }
            if (found) {
              if (each->is_macro) {
                // alpha version - execute each->expansion ... final version, only if no children
                if (keydebug) fprintf(stderr, "execute stored sequence ending in %d\r\n", command);
                {int i;
                for (i = 0; i < each->expansion_length; i++) {
                  if (keydebug) fprintf(stderr, "recursively execute %d\r\n", each->expansion[i]);
                  execute_primitive(each->expansion[i]);
		}
		}
                current_node = NULL;
                insequence = FALSE;
	      } else {
                current_node = each->child; // still in the same expansion
                insequence = TRUE;
	      }
	    } else {
              // the first few bytes look like the start of a sequence but it turns out it wasn't.
              // if we had an allowed sequence in the lead-up to this byte then execute it first,
              // but since we don't do that yet, just execute the individual keys one by one
              // as if they were not part of a macro.
              if (keydebug) if (current_node != NULL) fprintf(stderr, "execute stored sequence up to %d\r\n", command);
              insequence = FALSE;
              current_node = NULL;
              if (keydebug) fprintf(stderr, "execute %d\r\n", command); // just go round again???
              execute_primitive(command);
	    }
	  } else {
            struct NODE *each;
            int found = FALSE;
            // does this start a sequence???
            each = base;
            while (each != NULL) {
              if (each->c == command) {
                found = TRUE;
                current_node = each;
                break;
              }
              each = each->sibling;
            }
            if (found) {
              if (each->is_macro) {
                // alpha version - execute each->expansion ... final version, only if no children
                if (keydebug) fprintf(stderr, "execute stored sequence ending in %d\r\n", command);
                {int i;
                for (i = 0; i < each->expansion_length; i++) {
                  if (keydebug) fprintf(stderr, "recursively execute %d\r\n", each->expansion[i]);
                  execute_primitive(each->expansion[i]);
		}
		}
                current_node = NULL;
                insequence = FALSE;
	      } else {
                current_node = each->child;
                insequence = TRUE;
	      }
	    } else {
              if (keydebug) if (current_node != NULL) fprintf(stderr, "execute stored sequence %d\r\n", command);
              insequence = FALSE;
              current_node = NULL;
              if (keydebug) fprintf(stderr, "execute %d\r\n", command); // just go round again???
              execute_primitive(command);
	    }
	  }
	}
      }
    }
  }


  int MAX_BUF_BYTES, MAX_DISPLAY_LINES;
  int i, this_line;
  char *p;
  if (argc == 111) {
    char *s;
    fprintf(stderr, "syntax: %s filename\n",
                    (s=strrchr(argv[0], '/')) == NULL ? argv[0] : ++s);
    exit(1);
  }
  getsize(argc == 1 ? "tabtest.txt" : argv[1], &MAX_BUF_BYTES, &MAX_DISPLAY_LINES);
  if (MAX_BUF_BYTES < 0) {
    char *s;
    fprintf(stderr, "%s: cannot edit %s\n",
                    (s=strrchr(argv[0], '/')) == NULL ? argv[0] : ++s, argc == 1 ? "tabtest.txt" : argv[1]);
    exit(1);
  }

  load_database(&base);
  initscr(); raw();/*cbreak();*/ noecho(); nonl();
  intrflush(stdscr, FALSE); keypad(stdscr, TRUE); nodelay(stdscr, TRUE);
  LAST_DISPLAY_LINE = LINES-1;
  DISPLAY_RIGHTMOST_COLUMN = COLS;

  allocatebuffer(MAX_BUF_BYTES, MAX_DISPLAY_LINES);

  loadfile(argc == 1 ? "tabtest.txt" : argv[1], MAX_BUF_BYTES);

  init_globals(); ecinner("%e");

  for (;;) {
    lastcommand = command;
    command = getch();
    if (!execute(command)) break;
  }

  dump_database(base);
  free_buffers();
  sleep(2); move(0, 0); clear(); refresh(); sleep(1); endwin();

  exit(0);
}
