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

// lower bound is *always* inclusive, upper bound is always exclusive.               haha
// (i.e. all ranges are semi-open intervals)                                 X
// (i.e. all ranges are semi-open intervals)                                  Y

char *a, *fbeg, *fend, *lbeg, *lend, *fp, *pp;
// SIGNIFICANCE OF FILE POINTERS:
// [NL] O N E NL T W . . . O NL N E X T NL . . NL L A S T NL [NL]
//      !        !   !     !  !                                !
//      T        L   P     F  L                                F
//      O        B   P     P  E                                E
//      P        E            N                                N
//               G            D                                D

int first_line = 0;
int max_line;
int max_lines = 50000;
int first_screen_line = 0;
int last_screen_line = 24;
static int screen_line;
int screen_leftmost_column = 1;
int screen_rightmost_column = 80;
int screen_max_col = 80; /* blech. +1. */
int cursor;

char *sp[24], *ep[24]; /* screen-line start and screen-line end */
char screen[200/*hack (for screen_max_col?)*/][50000/*max_lines*/]; /* wrapped lines exceed raw lines */

void update_display(int cursor_line, int cursor_col) // try to put the cursor line on this screen line
{
  int x, y, line;
  move(0, 0);
  line = first_line;
  for (y = first_screen_line; y < last_screen_line; y++) {
    for (x = screen_leftmost_column-1; x < screen_rightmost_column; x++) {
      move(y, x);
      addch(screen[x-screen_leftmost_column+1][line]);
    }
    line += 1;
  }
  move(cursor_line, cursor_col);
}

int trailhack(int col, int line)
{
  int i;
  if (screen[0][line+1] == '+') return(0!=0);
  for (i = col; i < screen_max_col; i++)
    if (screen[i][line] != ' ') return(0!=0);
  return(0==0);
}

int trimhack(int line, int actual)
{
  if (screen[0][line+1] == '+' || (screen[actual][line] != ' ')) return(actual);
  while (actual > 0 && screen[actual][line] == ' ') actual -= 1;
  if (actual == 0) {
    actual += 1;
  } else if (screen[actual+1][line] != ' ') {
    actual += 2; /* HACK! for 79-col rows */
  } else actual += 1;
  return(actual);
}

void execute(int command)
{
  static int need_refresh = (0==0);

  if (command == '\e' && getch() == ERR) {
    move(0, 0); clear(); refresh(); endwin();
    exit(0);
// to add: HOME, END (start/end of file)
  } else if (command == KEY_DOWN || command == '\r') {
    if (screen_line == last_screen_line-1) {
      first_line += 1;
      if (first_line > max_line-(last_screen_line-1)+first_screen_line) first_line = max_line-(last_screen_line-1)+first_screen_line;
    } else {
      screen_line += 1;
    }
    need_refresh = (0==0);
  } else if (command == KEY_NPAGE || command == ' ' /* "more" clone? */) {
    first_line += (last_screen_line-first_screen_line-1);
    if (first_line > max_line-(last_screen_line-1)+first_screen_line) first_line = max_line-(last_screen_line-1)+first_screen_line;
    need_refresh = (0==0);
  } else if (command == KEY_UP) {
    if (screen_line == first_screen_line) {
      first_line -= 1; if (first_line < 0) first_line = 0;
    } else {
      screen_line -= 1;
    }
    need_refresh = (0==0);
  } else if (command == KEY_PPAGE) {
    if (screen_line+(last_screen_line-first_screen_line-1) >= first_screen_line) {
      first_line -= (last_screen_line-first_screen_line-1);
      if (first_line < 0) first_line = 0;
    } else {
      screen_line -= (last_screen_line-first_screen_line-1);
    }
    need_refresh = (0==0);
  } else if (command == KEY_LEFT) {
    cursor -= 1;
    if (cursor < screen_leftmost_column) {
      if (screen_line == first_screen_line && first_line == 0) {
        // Can't
        cursor += 1;
      } else {
        if (screen_line == first_screen_line) {
          first_line -= 1; if (first_line < 0) first_line = 0;
        } else {
          screen_line -= 1;
        }
        cursor = trimhack(first_line+(screen_line-first_screen_line), screen_rightmost_column-1);
        need_refresh = (0==0);
      }
    } else need_refresh = (0==0);
  } else if (command == KEY_RIGHT) {
    cursor += 1;
    if ((cursor >= screen_rightmost_column) || (trailhack(cursor-1, first_line+screen_line-first_screen_line))) {
      if (screen_line == (last_screen_line-1) && first_line == max_line) {
        /* bug on wrap at end of file */
        cursor -= 1;
      } else {
        cursor = screen_leftmost_column;
        if (screen_line == (last_screen_line-1)) {
          first_line += 1; if (first_line > max_line) first_line = max_line;
        } else {
          screen_line += 1;
        }
        need_refresh = (0==0);
      }
    } else need_refresh = (0==0);
  } else if (command == ERR) {
    // update only when there is no more type-ahead
    if (need_refresh) update_display(screen_line, cursor);
    need_refresh = (0!=0);
  }
}

int main(int argc, char **argv)
{
  int command;
  int c;
  char *s;
  int line, col;

  void add(int c)
  {
    screen[col++][line] = c;
    if (col == screen_max_col) {
      col = 1; line++;
      if (line == max_lines) {line--; pp--; } /* exclusive */
      screen[0][line] = '+';
    }
  }

  if (argc == 1) {
    fprintf(stderr, "syntax: %s filename\n", (s=strrchr(argv[0], '/')) == NULL ? argv[0] : ++s);
    exit(1);
  }

  initscr(); cbreak(); noecho();

//       Most programs would additionally use the sequence:

  nonl();
  intrflush(stdscr, FALSE);
  keypad(stdscr, TRUE);
  nodelay(stdscr, TRUE);

  fbeg = a = malloc(64*1024);
  fp = fend = fbeg + 64*1024;
  *fbeg++ = '\n';
  pp = fbeg;
  // load up a file
  freopen(argv[1], "rb", stdin);
  {
    char *endp = "**END**";
    for (line = 0; line < max_lines; line++)
      for (col = 0; col < screen_max_col; col++)
        screen[col][line] = ' ';
    col = 1; line = 0;
    for (;;) {
      c = fgetc(stdin);
      if (c == EOF) c = *endp++;
      if (c == '\0') break;
      if (pp != fend) *pp++ = c;
      if (c == '\n') {
        line++; col=1;
        if (line == max_lines) line--;
      } else {
        if ((c&127) < 32 || c == 127) {
          char *hex = "0123456789ABCDEF";
          add('<');
          add(hex[(c>>4)&15]);
          add(hex[c&15]);
          add('>');
        } else {
          add(c);
        }
      }
    }
    max_line = line;
  }
  *pp = '\n';
  lend = lbeg = fbeg;
  while (*lend != '\n') lend++;
  freopen("/dev/tty", "rb", stdin);
  screen_line = first_screen_line;
  cursor = screen_leftmost_column;
  for (;;) {
    command = getch();
    execute(command);
    refresh();
  }
  exit(0);
  return(0);
}
