/*

https://viewsourcecode.org/snaptoken/kilo/04.aTextViewer.html

step 149

bugs:

  - rainbow brackets can be a bit.... dynamic.....
  - prompt does not show cursor
  - mouse wheel scroll is bound to keeping cursor on screen
  - delete doesn't not merge lines
  
Features in queue:

  - block hilighting
    proper hilighting for comment blocks, strings
  - copy, cut and paste
    the extravogance!
  - undo
    wtf, you trying to kill me?
  - replace (as in search/replace)
  - split view
    oooh CMON NOW....
  
Manual:

  - compile with gcc *.c
  - To get the number of a terminal run:    tty
  - open a spare terminal and get its tty  (for debug stream)
  - in a deifferent terminal run with:  ./a.out foo.txt  2>/dev/pts/13
    (or whatever terminal number you got)
    

  


  ctrl-q   quit
  ctrl-s   save
  ctrl-f   find
  ctrl-n   new
  ctrl-o   open   
  
  alt-0    insert main() function
  alt-1    insert an if block
  alt-2    insert an else blocl
  alt-3    insert a for loop
  alt-4    insert a while loop  
  
  F3       find
    (in find)
    F3 find next
    shift-F3 find previous
    esc    abort
    enter  exit find with cursor position on found.
 

*/

#define _GNU_SOURCE     // asprintf

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <termios.h>    // raw mode
#include <stdlib.h>     // raw mode
#include <ctype.h>      // keypress
#include <errno.h>      // die
#include <string.h>     // writes
#include <sys/ioctl.h>  // getwindowsize
#include <sys/types.h>  // file openingg
#include <time.h>       // status message
#include <stdarg.h>     // status message
#include <fcntl.h>      // save

#include "danStuff.h"
#include "buffedit.h"


#define  ANSI_Cls       "\e[2J"
#define  ANSI_Home      "\e[H"
#define  ANSI_Hide      "\e[?25l"
#define  ANSI_Show      "\e[?25h"
#define  ANSI_ClrLine   "\e[K"
#define  ANSI_INV       "\e[7m"
#define  ANSI_NORM      "\e[m"

// mode 2 mouse events
#define  ANSI_MouseOn   "\e[?1000h"
#define  ANSI_MouseOff  "\e[?1000l"

// another fine grey is 8
#define  ANSI_GreyF     "\e[38;5;237m"
#define  ANSI_RedF      "\e[31m"
#define  ANSI_YellowF   "\e[33m"
#define  ANSI_BlueF     "\e[34m"
#define  ANSI_BrtBlueF  "\e[38;5;19m"
#define  ANSI_MagentaF  "\e[35m"
#define  ANSI_CyanF     "\e[36m"
#define  ANSI_GreenF    "\e[32m"
#define  ANSI_BrownF    "\e[38;5;52m"
#define  ANSI_OrangeF   "\e[38;5;166m"
#define  ANSI_PurpleF   "\e[38;5;54m"
#define  ANSI_LTGreyF   "\e[38;5;245m"
#define  ANSI_ColorRST  "\e[0m"




#define Min(X,Y) ((X) < (Y) ? (X) : (Y))
#define Max(X,Y) ((X) > (Y) ? (X) : (Y))

enum editorKey {
  BS_KEY      = 0x7F,
  ARROW_LEFT  = 1000,
  ARROW_RIGHT,
  ARROW_UP,
  ARROW_DOWN,
  PAGE_UP,
  PAGE_DOWN,
  HOME_KEY,
  END_KEY,
  DEL_KEY,
  
  MOUSE_EVENT,  
  TIMER_EVENT,
  
  CTRL_Q,
  CTRL_F,
  CTRL_S,
  CTRL_N,
  CTRL_O,
  CTRL_L, 
  
  ALT_0,
  ALT_1,
  ALT_2,
  ALT_3,
  ALT_4,
  
  KEY_F3,
  KEY_SHIFT_F3,
  
  FIND_RESET,
  END_LIST_KEY
  
};

enum editorAttributes {
  HL_NORMAL = 20,
  HL_MATCH,
  
  HL_DIGIT,
  HL_STRING,
  HL_COMMENT,
  HL_KEYWORD,
  HL_TYPE,
  HL_DIRECTIVE,
  
  HL_RB1,
  HL_RB2,
  HL_RB3,
  HL_RB4,
  HL_RB5,
  HL_RB6,
  HL_RB7,
  HL_RB8,
    
  END_LIST_ATTRIB
};

int BRACKETRAINBOW[] = {  HL_RB1,  HL_RB2,  HL_RB3,  HL_RB4,  HL_RB5,  HL_RB6,  HL_RB7,  HL_RB8 };

typedef struct Console_s {  
  int screenrows;
  int screencols;
  struct termios orig_termios;
} Console_t;


typedef struct erow {
  char *chars;
  char *render;
  char *attrib;
} erow;

typedef struct Editor_s {
   char *   filename;
   char *   tmpMessage;
   time_t   messageExpire;
   int      windowrows, windowcols;
   uint32_t cx, cy;
   int      rx;
   int      rowoff;
   int      coloff;
   int      numrows;
   erow    *row;
   uint8_t  dirty;
} Editor_t;


typedef struct substTable_s {
  char from;
  char * to;
} substTable_t;

typedef struct mouseEvent_s {
  unsigned char buttons;
  unsigned char X;
  unsigned char Y;
} mouseEvent_t;



substTable_t    SUBLIST[]    = { {'\t', "  "}, /*{'h', "hello_world"},   {'r', "Rue mohr" },*/ { 0,""} };  // I officially hate tabs more now.

char          * KEYWORDS     = " switch case default if else do while for break continue return ";
char          * DIRECTIVES   = " #include #define #ifdef #ifndef #endif ";
char          * TYPES        = " int char unsigned float double long uint8_t uint16_t uint32_t int8_t int16_t int32_t const enum struct union static typedef void ";
char          * SEPCHARS     = " \t,.+-/*=~%&|^!?()<>[]{}'\";:";

int  editorOpen             ( Editor_t * E, char *filename);
void editorSave             ( Editor_t * E );
void editorFind             ( Editor_t * E );
void editorNew              ( Editor_t * E );
void editorCmdOpen          ( Editor_t * E );

void enableRawMode          ( ) ;
void disableRawMode         ( ) ;
void die                    ( const char *s);
int  editorReadKey          ( Editor_t * E );
void editorProcessKeypress  ( Editor_t * E );
void editorRefreshScreen    ( Editor_t * E );
void writes                 ( char * s);
int  getWindowSize          ( int *rows, int *cols);
void initEditor             ( Editor_t * E );
int  astrcat                ( char ** dst,  const char * src);
void editorDrawRows         ( Editor_t * E, char ** ab);
void editorMoveCursor       ( Editor_t * E, int c);
//void editorAppendRow        ( Editor_t * E, char *s, size_t len);
void editorInsertRow        ( Editor_t * E,int at, char *s, size_t len) ;
void editorScroll           ( Editor_t * E );
void editorUpdateRow        ( Editor_t * E, erow *row);
void editorDrawStatusBar    ( Editor_t * E, char **ab);
void editorSetStatusMessage ( Editor_t * E, const char *fmt, ...);
//int  astrcat                ( char ** dst,  const char *src, ...);
void editorRowInsertChar    ( Editor_t * E, erow *row, unsigned int at, int c);
void editorInsertChar       ( Editor_t * E, int c);
char *editorRowsToString    ( Editor_t * E );
void editorRowDelChar       ( Editor_t * E, erow *row, int at);
void editorFreeRow          ( Editor_t * E, erow *row ) ;
void editorDelRow           ( Editor_t * E, int at ) ;
void editorRowAppendString  ( Editor_t * E, erow *row, char *s );
void editorInsertNewline    ( Editor_t * E );
char *editorPrompt          ( Editor_t * E,  char *prompt, char *hint, char *dfValue, void (*callback)(Editor_t * E, char *, int)) ;
int  promptProcessKeypress  ( Editor_t * E, buffEdProc_t * this);
void editorFindCallback     ( Editor_t * E, char *query, int key ) ;
int  escKeyLookup           ( char *s );
unsigned int extCharLookup  ( unsigned char c );
char * editorAttribToESC    ( int a );
char * genAttribs           ( Editor_t * E, erow *row) ;
void editorRowInsertString  ( Editor_t * E, erow *row, unsigned int at, char *s);


Console_t    C;
mouseEvent_t M;  // back in my day, 1 letter was all we had for variable names.

#define CTRL_KEY(k) ((k) & 0x1f)


char * INSERT_MAIN[]  = { "#include <stdio.h>", "  ", "int main(int argc, char *argv[]) { ", "  ", "  return 0; ", "}",  "" };
char * INSERT_IF[]    = { "if (  ) {", "  ", "}",                                            "" };
char * INSERT_ELSE[]  = { "else {", "  ", "}",                                               "" };
char * INSERT_FOR[]   = { "for ( ; ; ) {", "  ", "}",                                        "" };
char * INSERT_WHILE[] = { "while( ) {", "  ", "}",                                           "" };




int main(int argc, char *argv[]) {
    
  Editor_t  E;  
    
  enableRawMode() ;
  initEditor(&E);
  
  if (argc >= 2)  editorOpen( &E, argv[1] );
  
  editorSetStatusMessage(&E, "Quit: Ctrl-Q, Save: Ctrl-S, Find: F3 ");
  
  while (1) {
    editorRefreshScreen(&E);
    editorProcessKeypress(&E);
  }
  
  return 0;
}

// -----------------------------------------------------------------

void editorInsertFlow(Editor_t * E, int c) {
   char * line;
   char * t;
   char * pad;
   int i;
   
   pad = calloc(1, E->cx+1); memset(pad, ' ', E->cx);      
   
   switch (c) {
    case ALT_0:  // main loop      
      for(i = 0, line = INSERT_MAIN[0]; *(line = INSERT_MAIN[i]) ; i++) {
        editorInsertRow(E, E->cy+i, line, strlen(line)); 
      }
      E->cy+=3;
      E->cx += 2;
    break;
   
    case ALT_1: // if block   
      editorRowInsertString(E, &E->row[E->cy], E->cx, INSERT_IF[0]);
      for(i = 1, line = INSERT_IF[1]; *(line = INSERT_IF[i]) ; i++) {
        asprintf(&t, "%s%s", pad, line);
        editorInsertRow(E, E->cy+i, t, strlen(t)); 
        free(t);
      }
      E->cx += 5;
    break;
    
    case ALT_2: // else block   
      editorRowInsertString(E, &E->row[E->cy], E->cx, INSERT_ELSE[0]); 
      for(i = 1, line = INSERT_ELSE[1]; *(line = INSERT_ELSE[i]) ; i++) {
        asprintf(&t, "%s%s", pad, line);
        editorInsertRow(E, E->cy+i, t, strlen(t)); 
        free(t);
      }
      E->cy++ ;
      E->cx += 2;   
      
    break;
    
    case ALT_3: // for block
      editorRowInsertString(E, &E->row[E->cy], E->cx, INSERT_FOR[0]);
      for(i = 1, line = INSERT_FOR[1]; *(line = INSERT_FOR[i]) ; i++) {
        asprintf(&t, "%s%s", pad, line);
        editorInsertRow(E, E->cy+i, t, strlen(t)); 
        free(t);
      }
      E->cx += 5;   
        
    break;
    
    case ALT_4: // while block
      editorRowInsertString(E, &E->row[E->cy], E->cx, INSERT_WHILE[0]);
      for(i = 1, line = INSERT_WHILE[1]; *(line = INSERT_WHILE[i]) ; i++) {
        asprintf(&t, "%s%s", pad, line);
        editorInsertRow(E, E->cy+i, t, strlen(t)); 
        free(t);
      }
      E->cx += 6; 
      
    break;
   
   }
   
   free(pad);

}

void editorNew ( Editor_t * E ) {

  int i;

  free(E->filename); E->filename = NULL;
  E->cx = 0, E->cy = 0;
  E->coloff = 0; E->rowoff = 0;
  for(i = 0;i < E->numrows; i++  )  editorFreeRow(E, &E->row[i]);
  E->numrows = 0;
  
}

void editorCmdOpen ( Editor_t * E ) {
  char *filename;

  filename = editorPrompt(E, "Open: ", "filename.ext", "", NULL);
  if (filename == NULL) {; // user abort
    editorSetStatusMessage(E, "Canceled.");
    return;
  }
  editorNew(E);
  if (editorOpen( E, filename ) == 0)  E->messageExpire = time(NULL)-1; // get bar to reset if open was ok.
}




char * editorAttribToESC(int a) {
  switch (a) {
    case HL_DIGIT:     return ANSI_RedF;
    case HL_MATCH:     return ANSI_BlueF;
    case HL_STRING:    return ANSI_MagentaF;
    case HL_COMMENT:   return ANSI_CyanF;
    case HL_KEYWORD:   return ANSI_GreenF;
    case HL_TYPE:      return ANSI_YellowF;
    case HL_DIRECTIVE: return ANSI_BlueF;
    
    case HL_RB1:       return ANSI_BrownF;
    case HL_RB2:       return ANSI_RedF;
    case HL_RB3:       return ANSI_OrangeF;
    case HL_RB4:       return ANSI_YellowF;
    case HL_RB5:       return ANSI_GreenF;
    case HL_RB6:       return ANSI_BlueF;
    case HL_RB7:       return ANSI_PurpleF;
    case HL_RB8:       return ANSI_LTGreyF;    
        
 //   default:           return ANSI_ColorRST;
    default:           return ANSI_BrtBlueF;
  }
}

// per line syntax hilighting.
char * genAttribs(Editor_t * E, erow *row) {
   char          *tmpAttr;             // prehilighting
   char          *toks;                 // tokenizing copy
   char          *tok;
   char          *ptok;                 // padded token
   char          *p;                   // char pointer for walking the input
   char           src = 0, rrc = 0;
   static char    crc = 0;                // rainbow counters!
   int            x;                   // horizontal tracking
      
  tmpAttr     = strdup(row->chars);                 // tempate out as large as the file data for line
  memset(tmpAttr, HL_NORMAL, strlen(row->chars));   // default to normal hilight.
  
  toks        = strdup(row->chars);                 // copy for tokenizing
      
  // whole word hilighting
  tok = strtok(toks, SEPCHARS);  
  while (tok != NULL) { 
     
    asprintf( &ptok, " %s ", tok); // make a padded version of the token       
    
    if (0) {
    } else if (strstr(KEYWORDS, ptok) != NULL) {
      memset(&tmpAttr[tok-toks], HL_KEYWORD, strlen(tok));            //  hilight word
    } else if (strstr(DIRECTIVES, ptok) != NULL) {
      memset(&tmpAttr[tok-toks], HL_DIRECTIVE, strlen(tok));          //  hilight word
    } else if (strstr(TYPES, ptok) != NULL) {
      memset(&tmpAttr[tok-toks], HL_TYPE, strlen(tok));               //  hilight word
    } 
                
    free(ptok);
    tok = strtok(NULL, SEPCHARS);
  }
       
  free(toks);  // at this point the result is in tmpAttr    
                  
  for (p = row->chars, tok = tmpAttr; *p; p++, x++, tok++ ) {          // character hilighting
  
    if (0) {
    } else if (*p == '(') {
      *tok = BRACKETRAINBOW[rrc];   rrc = (rrc+1)&7;
    } else if (*p == ')') {
      rrc = (rrc==0)?7:(rrc-1);  *tok = BRACKETRAINBOW[rrc];    
          
    } else if (*p == '[') {
      *tok = BRACKETRAINBOW[src];   src = (src+1)&7;
    } else if (*p == ']') {
      src = (src==0)?7:(src-1);  *tok = BRACKETRAINBOW[src];                        
      
    } else if (*p == '{') {
      *tok = BRACKETRAINBOW[crc];   crc = (crc+1)&7;
    } else if (*p == '}') {
      crc = (crc==0)?7:(crc-1);  *tok = BRACKETRAINBOW[crc];         
      
    } 
   }
   
   if ((toks = strstr(row->chars, "//")) != NULL) {
     memset(&tmpAttr[toks - row->chars], HL_COMMENT, strlen(row->chars) - (toks - row->chars) );
   }  
   
    
   return tmpAttr;
}


// translate chars buffer to render buffer. then update the hilighting
void editorUpdateRow(Editor_t * E, erow *row) {
  char          *p;                   // char pointer for walking the input
  char          *tmp = strdup(" ");   // dummy string for inserting characters.
  char          *tmp2;                // temp string for attribute fills
  char          *tmpAttr;             // prehilighting
  int            x;                   // horizontal tracking
  substTable_t  *sp;                  // substitution pointer
  
  char          *tok;

  x     = 0;
  E->rx = 0;
  free(row->render);                             // the docs say that free(NULL) is OK
  free(row->attrib);
  row->render = strdup("");                      // we might not add anything else.   
  row->attrib = strdup("");   
  
  tmpAttr  =  genAttribs(E, row);
        
  for (p = row->chars, tok = tmpAttr; *p; p++, x++, tok++ ) {          
          
    for(sp = SUBLIST; sp->from; sp++)            // check the character against all the substitution characters
      if (sp->from == *p) {                      // if you find a hit
        astrcat(&row->render, sp->to );          // append the translation to the output string
        astrcat(&row->attrib, memset(tmp2 = calloc(strlen(sp->to)+2, 1), *tok, strlen(sp->to) ) );
        free(tmp2);
        if (x < E->cx) E->rx += strlen(sp->to);  // if were behind the cursor, bump the cursor position
        break;                                   // stop substituting for this character.
      }
  
    if (!sp->from) {                             // if you hit the end of the list with no substitution
      if (x < E->cx) E->rx ++;                   // if behind cursor, bump it forward
      tmp[0] = *p;                               // and append the character to the result
      astrcat(&row->render, tmp );
      tmp[0] = *tok;
      astrcat(&row->attrib, tmp );
    }
                            
  }
  
  if (x < E->cx) E->rx += (E->cx - x);           // if the cursor is beyond the end of the string, bump it out accordingly
  
  free(tmpAttr);
  
}




void editorFindCallback(Editor_t * E, char *query, int c) {

  int i;
  char *match;
  erow *row;
  
  static int    n_match = 1;  // if you have more than 32768 matches, I feel for you
         int    hit;

  if (0) {
 // } else if ( c == CTRL_KEY('f') ) { // find next
  } else if ( c == KEY_F3 ) {       // find next
    n_match++;
  } else if ( c == FIND_RESET ) {
    n_match = 1;
    return;
  } else if ( c == KEY_SHIFT_F3 ) { // find prev
    if (n_match > 1) n_match--;
    else {                               // beep to say we cant go earlier than the first match
      putchar( '\a'); fflush(stdout);
      return;
    }
  } else if (!isprint(c)) {         // ignore control stuff
    return;
  }

  for (i = 0, hit = 0; i < E->numrows; i++) {
    row = &E->row[i];
    match = strstr(row->chars, query); // why are we using render instead of chars???
    if (match) {
      hit++;
      if (hit == n_match) {
        E->cy = i;                                   // we position cx,cy,  editorUpdateRow()  adjusts cx into rx, which is where the cursor is actually put.
        E->cx = match - row->chars + strlen(query) ; // pointers of offset.
        E->rowoff = E->numrows;
        break;
      }
    }       
  }
  
  if (!(i < E->numrows)) {  // if no match was found, beep!    
    if (n_match > 1) n_match--; // last rematch didn't work    
    putchar( '\a'); fflush(stdout);
  }
  
}

/*

repeat F3 for next find
up/down arrows for search history
shift-F3 for reverse find

*/
void editorFind( Editor_t * E ) {

  char * rv;
  int cx, cy, coloff, rowoff;
  
  cx = E->cx; cy = E->cy; coloff = E->coloff; rowoff = E->rowoff;

  if (rv = editorPrompt(E, "Search (F3 Next/shift-F3 Back/Esc/Enter): ", "word", "", editorFindCallback )) {
    free(rv);
  } else { // user cancel, restore position.
    E->cx = cx; E->cy = cy; E->coloff = coloff; E->rowoff = rowoff;
  }      
  E->messageExpire = time(NULL)-1; // get bar to reset
}



//  return 1 means continue, 0 means normal result, -1 means cancel
int promptProcessKeypress( Editor_t * E, buffEdProc_t * this) {
  int c; 
  
  switch (c = editorReadKey(E)) {
    
    case '\r':
    case '\n':
      return 0; // return valid completion
    break;
    
   // case CTRL_KEY('l'):
    case CTRL_L:
    case '\e':
      return -1; // return abort
    break;
      
    case '\b':
    case BS_KEY:
      buffEdTakeStroke(  this , EV_BACKSP,  ' ');   return 1;
    break;
    
    case DEL_KEY:
      buffEdTakeStroke(  this , EV_DELETE,  ' ');    return 1;  
    break;      
    
    case HOME_KEY: 
      buffEdTakeStroke( this , EV_CRS_START,  ' ');  return 1;
    break;  
      
    case END_KEY: 
      buffEdTakeStroke(  this , EV_CRS_END,  ' ');   return 1;
    break;   
           
    case ARROW_LEFT:
      buffEdTakeStroke(  this , EV_CRS_BCK,  ' ');   return 1;
    break;
    
    case ARROW_RIGHT:
      buffEdTakeStroke(  this , EV_CRS_FWD,  ' ');   return 1;
    break;
    
    default:
      if (isprint(c)) {
        buffEdTakeStroke( this , EV_INSERT,  c);
      } else {
        fprintf(stderr, "pass -> %04X\n", c);  
      }
      return c;
    break;
     
  }
  
}


char *editorPrompt(Editor_t * E,  char *prompt, char *hint, char *dfValue, void (*callback)(Editor_t * E, char *, int)) {

  char * rend = NULL;
  buffEdProc_t  el;  
  int d;          
  int c;
            
  buffEdInit(&el);  
  free(el.buff); // dont need default buffer.
  el.buff = strdup(dfValue);
  buffEdTakeStroke( &el , EV_CRS_END,  ' ');  
  
  for(;;) {
  
    d = strlen(hint) - strlen(el.buff);
      // render status line
    astrcat  ( &rend,  prompt);
    astrcat  ( &rend,  el.buff);    
    if (d > 0) {
      astrcat ( &rend, ANSI_GreyF );
      astrcat ( &rend, &hint[strlen(el.buff)]);
      astrcat ( &rend, ANSI_ColorRST );
    }
    
    editorSetStatusMessage(E, rend);  //
    free(rend); rend = NULL;
   
    editorRefreshScreen( E ) ;
   
    c =  promptProcessKeypress(E, &el);
    
    switch  ( c ) {   
      case   1:                   break; 
      case   0: return el.buff;   break;  // result
      case  -1: return NULL;      break;  // user canceled     
      default : if (callback) callback(E, el.buff, c);          
    }
    
  }
  
  return NULL;

}




void editorSave(Editor_t * E) {  
  char *buf ;
  int   fd  ;
                    
  if (E->filename == NULL)  {
    E->filename = editorPrompt(E, "Save as: ", "filename.ext", "untitled.txt", NULL);    
    if (E->filename == NULL) {; // user abort
      editorSetStatusMessage(E, "Canceled.");
      return;
    }
  }   
    
  if ((fd = open(E->filename, O_RDWR | O_CREAT, 0644)) == -1) return editorSetStatusMessage(E, "Can't save! I/O error: %s", strerror(errno));
  
  if ( ftruncate(fd, 0) != -1) {  
    buf = editorRowsToString(E);  
    write(fd, buf, strlen(buf));
    free(buf);
    editorSetStatusMessage(E, "Saved.");
    E->dirty = 0;
  } else {
    editorSetStatusMessage(E, "Can't save! I/O error: %s", strerror(errno));
  }
  close(fd);  
  
}


void editorInsertNewline( Editor_t * E ) {
  erow *row;

  if (E->cx == 0) {
    editorInsertRow(E, E->cy, "", 0);
  } else {
    row = &E->row[E->cy];
    editorInsertRow(E, E->cy + 1, &row->chars[E->cx], strlen(row->chars) - E->cx); // strlen(row->chars)
    row = &E->row[E->cy];
    row->chars[E->cx] = '\0';  // !!!???!!! after a redraw with asprintf, I think this is freed
    editorUpdateRow(E, row);
  }
  E->cy++;
  E->cx = 0;
}


/* ============================================================================================= */
void editorInsertRow(Editor_t * E, int at, char *s, size_t len) {

  if (at < 0 || at > E->numrows) return;
  E->row = realloc(E->row, sizeof(erow) * (E->numrows + 1)); 
  memmove(&E->row[at + 1], &E->row[at], sizeof(erow) * (E->numrows - at));
   
  asprintf( &E->row[at].chars, "%.*s", len, s);
  
  E->row[at].render = NULL; // pre-init
  E->row[at].attrib = NULL;
  editorUpdateRow( E, &E->row[at]);
    
  E->numrows++; 
  E->dirty = 1;   
  
}

void editorFreeRow(Editor_t * E, erow *row) {
  free(row->render);
  free(row->attrib);
  free(row->chars);
}

/* ============================================================================================= */

void editorRowAppendString(Editor_t * E, erow *row, char *s) {  
  astrcat(&row->chars,  s);  
  editorUpdateRow(E, row);
  E->dirty++;
}

void editorDelRow(Editor_t * E, int at) {
  if (at < 0 || at >= E->numrows) return;
  editorFreeRow(E, &E->row[at]);
  memmove(&E->row[at], &E->row[at + 1], sizeof(erow) * (E->numrows - at - 1));
  E->numrows--;
  E->dirty++;
}




void editorDelChar(Editor_t * E) {
  erow *row;

  if (E->cy == E->numrows) return;
  if ((E->cx == 0) && (E->cy == 0)) return;
  
  row = &E->row[E->cy];
  if (E->cx > 0) {
    editorRowDelChar(E, row, E->cx - 1);
    E->cx--;
  } else {
    E->cx = strlen(E->row[E->cy - 1].chars);
    editorRowAppendString(E, &E->row[E->cy - 1], row->chars);
    editorDelRow(E, E->cy);
    E->cy--;
  
  }
  
  
}


void editorRowDelChar(Editor_t * E, erow *row, int at) {
  if (at < 0 || at >= strlen(row->chars)) return;
  memmove(&row->chars[at], &row->chars[at + 1], strlen(row->chars) - at);
  editorUpdateRow(E, row);
  E->dirty = 1;
}



char *editorRowsToString(Editor_t * E) {

  int i;
  char *buf = NULL;
  
  for (i = 0; i < E->numrows; i++) {
     astrcat( &buf,  E->row[i].chars); // horrid efficiency here
     astrcat( &buf,  "\n");
  }
  return buf;
}


/*** input ***/
void editorProcessKeypress(Editor_t * E) {
  int c; 
  int times;
  
  switch (c = editorReadKey(E)) {
  
   // case CTRL_KEY('s'):
    case CTRL_S:       // save
      editorSave(E);
    break;

    case CTRL_N:        // new
      editorNew(E);
    break;

    case CTRL_O:        // open
      editorCmdOpen(E);
    break;
  
    case '\r':
      editorInsertNewline(E);
    break;
    
    case '\b':
    case DEL_KEY:
    case BS_KEY:
      if (c == DEL_KEY) editorMoveCursor(E, ARROW_RIGHT); // bug at end of line
      editorDelChar(E);
    break;
    
    case CTRL_F:  // find
    case KEY_F3:
      editorFindCallback(E, NULL, FIND_RESET);
      editorFind(E);
    break;
  
    case CTRL_Q:   // quit
      writes(ANSI_Cls);
      writes(ANSI_Home);
      exit(0);
    break;
    
    case HOME_KEY:  E->cx = 0;                  break;  
      
    case END_KEY:   E->cx = strlen(E->row[E->cy].chars);   break;   
     
    case PAGE_UP:
    case PAGE_DOWN:      
        for ( times = E->windowrows; times--; )
          editorMoveCursor(E, c == PAGE_UP ? ARROW_UP : ARROW_DOWN);      
    break;    
    
    case ARROW_LEFT:
    case ARROW_RIGHT:
    case ARROW_UP:
    case ARROW_DOWN:
      editorMoveCursor(E, c);
    break;
    
    case ALT_0:
    case ALT_1:
    case ALT_2:
    case ALT_3:
    case ALT_4:
      editorInsertFlow(E, c);
    break;    
    
    case MOUSE_EVENT:
      fprintf(stderr, "Mouse Event: button: %X X:%d Y:%d\n", M.buttons, M.X, M.Y);
    
      if (0) {
      } else if (M.buttons == 0x60) { if (E->rowoff) E->rowoff--; // editorMoveCursor(E, ARROW_UP); // scroll up
      } else if (M.buttons == 0x61) { E->rowoff++; // editorMoveCursor(E, ARROW_DOWN); // scroll down
      }
    break;
    
    case TIMER_EVENT:  // used to trip screen redraw
    break;
    
    default:
      if (isprint(c) || /* tab */(c == 9)) editorInsertChar(E,c);    
      else fprintf(stderr, "hu? -> %X\n", c);  
    break;
        
  }
  
}

void editorInsertChar(Editor_t * E, int c) {
  
  while (E->cy >= E->numrows)     editorInsertRow(E, E->numrows, "", 0);
 
  editorRowInsertChar(E, &E->row[E->cy], E->cx, c);
  E->cx++;
}


void editorRowInsertChar(Editor_t * E, erow *row, unsigned int at, int c) {

  char * t;

  if (at < strlen(row->chars) ) {    // if cursor is within existing text
    row->chars = realloc(row->chars, strlen(row->chars) + 2);
    memmove(&row->chars[at + 1], &row->chars[at], strlen(row->chars) - at + 1);
    row->chars[at] = c;
  } else {                           // if cursor is beyond existing text
    asprintf(&t, "% *c", at-strlen(row->chars)+1 ,c);
    astrcat ( &row->chars,  t);
    free(t);
  }
  
  E->dirty = 1;
  editorUpdateRow(E, row);
}


void editorRowInsertString(Editor_t * E, erow *row, unsigned int at, char *s) {

  char * t;

  if (at < strlen(row->chars) ) {    // if cursor is within existing text
    row->chars = realloc(row->chars, strlen(row->chars) + strlen(s) + 1);
    memmove(&row->chars[at + strlen(s)], &row->chars[at], strlen(row->chars) - at + 1);
    memcpy(&row->chars[at], s, strlen(s));
  } else {                           // if cursor is beyond existing text
    asprintf(&t, "% *s", at-strlen(row->chars)+1 ,s);
    astrcat ( &row->chars,  t);
    free(t);
  }
  
  E->dirty = 1;
  editorUpdateRow(E, row);
}



void initEditor(Editor_t * E) {

  if (getWindowSize(&C.screenrows, &C.screencols) == -1) die("getWindowSize");

  E->filename      = NULL;
  E->windowrows    = C.screenrows-1;
  E->windowcols    = C.screencols;
  E->cx            = 0;
  E->cy            = 0;
  E->rx            = 0;
  E->rowoff        = 0;
  E->dirty         = 0;
  E->coloff        = 0;
  E->numrows       = 0;
  E->row           = NULL;  
  E->tmpMessage    = NULL;
  E->messageExpire = 0;
      
}



//======================================================================================================

void editorSetStatusMessage(Editor_t * E, const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  
  free(E->tmpMessage);
  asprintf(&E->tmpMessage, fmt, ap);
  
  va_end(ap);
  E->messageExpire = time(NULL) + 3; // you have 3 seconds to realize the clock is about to roll over.
  
}

void editorDrawStatusBar(Editor_t * E, char **ab) {
  char * fill;
  int l;
  char *info1, *info2;
  int srcStart, destStart, len;
  
  astrcat(ab, ANSI_ColorRST);
  astrcat(ab, ANSI_INV);
  
  fill = malloc(E->windowcols+1);
  memset(fill, ' ', E->windowcols); fill[E->windowcols] = 0;
  
  l = asprintf(&info1, "%d,%d", E->cx + 1, E->cy + 1); 
  memcpy(fill, info1, l);
  free(info1);

    asprintf(&info2, "%c%.20s - %d lines",E->dirty?'*':' ', E->filename ? E->filename : "Untitled", E->numrows);    
    l = asprintf(&info1, "%.*s", E->windowcols-1, info2);

    free(info2);

    destStart = Max(0, E->windowcols - 1 - l);
    len       = Min(l ,E->windowcols - 1) ;
    srcStart  = l - len;

    memcpy(&fill[destStart], &info1[srcStart], len);
    free(info1);
      
  astrcat(ab, fill);
  free(fill);

  astrcat(ab, ANSI_NORM);
}


void editorDrawMessageBar(Editor_t * E, char **ab) {
  char * tmp;
  astrcat(ab, ANSI_ClrLine);
  asprintf(&tmp, "%.*s", E->windowcols, E->tmpMessage);
  astrcat(ab, tmp);
  free(tmp);
}




void editorDrawRows(Editor_t * E, char ** ab) {
  int y;
  char * b;
  int filerow;
  int p;       // pointer thru render string
  int oa = -1; // old attribite
  
  for (y = 0; y < E->windowrows ; y++) {
    filerow = y + E->rowoff;
    if (filerow >= E->numrows) { // past end of file.
      astrcat(ab, "~");
    } else {
      if (E->coloff < strlen(E->row[filerow].render)) {   //  normal content  
      
      /*
        asprintf(&b, "%.*s", E->windowcols, &E->row[filerow].render[E->coloff]); 
        astrcat(ab, b);
        free(b); 
        */
       
         for (p = 0; E->row[filerow].render[p+E->coloff] && (p <(( E->windowcols) )) ; p++ ) {  //!!!???!!! ARG per character render
           if (oa != E->row[filerow].attrib[p+E->coloff]) {
             oa = E->row[filerow].attrib[p+E->coloff];
             asprintf(&b, "%s%c", editorAttribToESC( oa ), E->row[filerow].render[p+E->coloff]);
           } else {
             asprintf(&b, "%c",  E->row[filerow].render[p+E->coloff]); 
           }
           astrcat(ab, b);
           free(b);                        
         }
                                        
      } else {                                            // past end of horizontal content
        astrcat(ab, "");        
      }      
    }
    
    astrcat(ab, ANSI_ClrLine);
    if (y < E->windowrows -1)  astrcat(ab, "\r\n");
  }
}





void editorRefreshScreen(Editor_t * E) {  

   char * ab = NULL;  // screen buff
   char * temp = NULL;
   
   if (getWindowSize(&C.screenrows, &C.screencols) != -1) {
     E->windowrows    = C.screenrows-1;
     E->windowcols    = C.screencols;
   }
  
   editorScroll(E);
   
   astrcat(&ab,  ANSI_Hide);
   astrcat(&ab,  ANSI_Home);
   astrcat(&ab,  ANSI_ColorRST);
   
   if (time(NULL) > E->messageExpire) {   
     editorDrawStatusBar ( E, &ab);  
     if (E->tmpMessage) {
       free(E->tmpMessage);
       E->tmpMessage = NULL;
     }
   } else {
     editorDrawMessageBar( E, &ab);
   }
   astrcat(&ab, "\r\n");
   editorDrawRows      ( E, &ab);
   
   asprintf(&temp,"\e[%d;%dH", E->cy - E->rowoff + 2, E->rx - E->coloff + 1); // set cursor position
   astrcat(&ab,  temp);
   free(temp);
   
   astrcat(&ab,  ANSI_Show); // show cursor command
   
   writes(ab);  // write to screen
   free(ab);
}



//====================================================================================================


void editorMoveCursor(Editor_t * E, int c) {
  switch (c) {
    case ARROW_LEFT:  if (E->cx != 0)                  E->cx--;   break;
    case ARROW_RIGHT:                                  E->cx++;   break;
    case ARROW_UP:    if (E->cy != 0)                  E->cy--;   break;
    case ARROW_DOWN:                                   E->cy++;   break;
  }
}

void editorScroll(Editor_t * E) {

  if (E->cy < E->numrows) {
    editorUpdateRow( E, &E->row[E->cy]); // translate cursor x and y
  } else {
    E->rx = E->cx;
  }

  if (E->cy < E->rowoff) {
    E->rowoff = E->cy;
  }
  if (E->cy >= E->rowoff + E->windowrows) {
    E->rowoff = E->cy - E->windowrows + 1;
  }
  
  if (E->rx < E->coloff) {
    E->coloff = E->rx;
  }
  if (E->rx >= E->coloff + E->windowcols) {
    E->coloff = E->rx - E->windowcols + 1;
  }
  
}



int editorOpen(Editor_t * E, char *filename) {

  FILE    *fp      = fopen(filename, "r");
  char    *line    = NULL;
  size_t   linecap = 0;
  ssize_t  linelen;
  
  if (!fp) {
    editorSetStatusMessage(E, "Unable to open file.");
    return 1;
  }
  
  free(E->filename);
  E->filename = strdup(filename);
  
  while ((linelen = getline(&line, &linecap, fp)) != -1) {   
    editorInsertRow(E, E->numrows, line, strcspn(line, "\r\n"));  
  }
  E->dirty = 0;
  free(line);
  fclose(fp);
  return 0;
}




int escKeyLookup (char *s) {

 // fprintf(stderr, "%s\n", (s[0]=='\e')?&s[1]:&s[0]);

  if (0) {
  } else if (!strcmp( s, "[A"))    {   return ARROW_UP;       
  } else if (!strcmp( s, "[B"))    {   return ARROW_DOWN;
  } else if (!strcmp( s, "[C"))    {   return ARROW_RIGHT;
  } else if (!strcmp( s, "[D"))    {   return ARROW_LEFT;
  } else if (!strcmp( s, "[H"))    {   return HOME_KEY;
  } else if (!strcmp( s, "[F"))    {   return END_KEY;
  } else if (!strcmp( s, "[3~"))   {   return DEL_KEY; // \e[3~
  } else if (!strcmp( s, "[1~"))   {   return HOME_KEY;
  } else if (!strcmp( s, "[7~"))   {   return HOME_KEY;
  } else if (!strcmp( s, "[4~"))   {   return END_KEY;
  } else if (!strcmp( s, "[8~"))   {   return END_KEY;          
  } else if (!strcmp( s, "[5~"))   {   return PAGE_UP;
  } else if (!strcmp( s, "[6~"))   {   return PAGE_DOWN;
  } else if (!strcmp( s, "OR"))    {   return KEY_F3;
  } else if (!strcmp( s, "[13~"))  {   return KEY_F3;
  } else if (!strcmp( s, "[[C"))   {   return KEY_F3;       // terminal // oh cmon now!
  } else if (!strcmp( s, "[25~"))  {   return KEY_SHIFT_F3;
  } else if (!strcmp( s, "[1;2R")) {   return KEY_SHIFT_F3;
  } else if (!strcmp( s, "[37~"))  {   return KEY_SHIFT_F3; // terminal
  
  } else {
    fprintf(stderr, "match fail %s\n", s ); // show what we couldn't match
    return 0;
  }
 
}

unsigned int extCharLookup (unsigned char c) {

  switch(c) {
    case CTRL_KEY('f'): return CTRL_F;
    case CTRL_KEY('q'): return CTRL_Q;
    case CTRL_KEY('l'): return CTRL_L; // I have no idea.
    case CTRL_KEY('s'): return CTRL_S;
    case CTRL_KEY('n'): return CTRL_N;
    case CTRL_KEY('o'): return CTRL_O;    
    case 0x0D:          return c;    
    case 0x0A:          return c;
    case 0x08:          return c;     //backspace
    case 0x09:          return c;     // tab!
    case 0xB0:          return ALT_0;
    case 0xB1:          return ALT_1;
    case 0xB2:          return ALT_2;
    case 0xB3:          return ALT_3;
    case 0xB4:          return ALT_4;
  
    default:           
      fprintf(stderr, "WTF key: %X\n", c);
      return 0;
  }

}


/*

<char>                                -> char
<esc>                                 -> esc
<esc> <esc>                           -> esc
<esc> <char>                          -> Alt-keypress or keycode sequence
<esc> '['                             -> Alt-[
<esc> '[' <num>
<esc> '[' (<num>) (';'<num>) '~'      -> keycode sequence, <num> defaults to 1
<esc> 'O' <char>

*/



int editorReadKey(Editor_t * E) { // this needs to be a state machine for collecting the escape seq
  int nread;
  char c[33];
  char * cp;
  
  cp = &c[0];
  
  while ((nread = read(STDIN_FILENO, cp, 1)) != 1) {       // get first character
    if ((nread == -1) && (errno != EAGAIN)) die("read");
    if (E->tmpMessage) 
      if (time(NULL) > E->messageExpire) return TIMER_EVENT;         // trip out for status bar to reset.
  } 
   
  if ((c[0] >= 0x20) && (c[0] <= 0x7F))    return c[0];     // normal character
  if  (c[0] != '\e')                       return extCharLookup(c[0]); 
  
  cp++;  
  if (read(STDIN_FILENO, cp, 1) != 1)      return '\e'; // error fallback
  
  if (*cp == '\e')                         return '\e'; // double escape
  
  if ((*cp == '[' ) || (*cp == 'O' ) )  {
    do { 
      cp++;
      if (read(STDIN_FILENO, cp, 1) != 1)  return 0; // error   
      
      if (*cp == 'M') {  // mouse event
        cp++; if (read(STDIN_FILENO, cp, 1) != 1)  return 0; // button
        cp++; if (read(STDIN_FILENO, cp, 1) != 1)  return 0; // X loc
        cp++; if (read(STDIN_FILENO, cp, 1) != 1)  return 0; // Y loc
       // fprintf(stderr, "Mouse Event: button: %X X:%d Y:%d\n", (unsigned char)c[3], (unsigned char)c[4], (unsigned char)c[5]);
        M.buttons = c[3];  M.X = c[4]; M.Y = c[5];
        return MOUSE_EVENT; // I dont know what to DO with the data yet.
      }      
      
//      fprintf(stderr, "-> %c", *cp);               
    } while( (*cp == ';') || (isdigit(*cp)) );
    cp++;
    *cp = 0;
    
    return escKeyLookup( &c[1] );
  
  }

}



int getWindowSize(int *rows, int *cols) {
  struct winsize ws;
  
  if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) || (ws.ws_col == 0)) {
    return -1;
  } else {
    *cols = ws.ws_col;
    *rows = ws.ws_row;
    return 0;
  }
}



void writes( char * s) {
  write(STDOUT_FILENO, s, strlen(s));
}


void die(const char *s) {
  
  writes(ANSI_MouseOff);
  writes(ANSI_Cls);
  writes(ANSI_Home);
    
  perror(s);
  exit(1);
}



void disableRawMode() {
   if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &C.orig_termios) == -1)
    die("tcsetattr");
    
   writes(ANSI_MouseOff); 
   writes(ANSI_ColorRST);
}



void enableRawMode() {
  struct termios raw;

  if (tcgetattr(STDIN_FILENO, &C.orig_termios) == -1) die("tcgetattr");
  atexit(disableRawMode);
  
  raw = C.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] = 1;
  
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr");
  
  writes(ANSI_MouseOn);
  
}


// ------ append buffer -------------------------




































