/**********************************************************************\
*                                                                      *
*  editor.c -- this is a primitive graphics mode editor with word-wrap *
*  It can be used as a template for a graphics mode editor, but it is  *
*  not certified bug-free, and anybody thinking of using this code     *
*  should study it carefully, beta-test it thoroughly, and debug as    *
*  appropriate.  This code is presented here primarily for academic    *
*  purposes: it is one approach to the problem of how to write a       *
*  graphics mode text editor.                                          *
*                                                                      *
*  Use this code freely, and send suggestions for optimizations and    *
*  improvements to Diana Gruber.                                       *
*                                                                      *
\**********************************************************************/

#include "defs.h"

#define CTRL_Y      25

#define F1          59
#define F2          60
#define F9          67
#define F10         68

#define CHAR_WIDTH   8

int overstrike;
int top_row, bottom_row;

/**********************************************************************\
*                                                                      *
*  can_wrap -- is there enough room to do a word wrap?                 *
*                                                                      *
\**********************************************************************/

can_wrap(s1,s2,length)
char *s1, *s2;
int length;
{
   if (strlen(s1)+strlen(s2) < length)
      return(TRUE);
   else
      return(FALSE);
}

/**********************************************************************\
*                                                                      *
*  clear_string -- clear all or part of a row of chars                 *
*                                                                      *
\**********************************************************************/

void clear_string(x,y,len,maxx)
int x, y, len, maxx;
{
   int x1, y0;
   int color;

   if (len == 0) return;

   y0 = y - ptsize;
   x1 = x + len * CHAR_WIDTH - 1;
   if (x1 > maxx) x1 = maxx;

   color = fg_getcolor();
   fg_setcolor(background);
   fg_rect(x,x1,y0,y);
   fg_setcolor(color);
}

/**********************************************************************\
*                                                                      *
*  editor -- edit nrows lines of text                                  *
*                                                                      *
\**********************************************************************/

editor(array,minx,maxx,miny,maxy,nrows,array_len)
char *array;
int  minx, miny, maxx, maxy, nrows;
int  *array_len;
{
   register int i, n;
   int b, l;
   int x, y;
   int iy;
   unsigned char key, aux;
   static char char_string[] = {0,0};
   int maxcol;
   int row;
   char *tempstring;
   char *string[101];
   int col[101];
   int end[101];
   int color;
   int visible_rows;

   color = fg_getcolor();
   fg_setpage(visual);
   overstrike = FALSE;
   iy = miny + ptsize;

   /* determine the string size */

   maxcol = (maxx+1 - minx) / CHAR_WIDTH;

   /* allocate space for the strings and initialize all elements to 0 */

   for (n = 0; n < nrows; n++)
   {
      if ((string[n] = calloc(maxcol+3,sizeof(char))) == NULL)
         return(ERR);
      col[n] = 0;
      end[n] = 0;
   }

   tempstring = calloc(maxcol+2,sizeof(char));

   /* display whatever is in the array now */

   l = 0;
   x = minx;
   y = iy;
   top_row = 0;
   bottom_row = ((maxy - miny + 1) / (ptsize+1));
   if (bottom_row > nrows) bottom_row = nrows;

   /* recalc maxy based on ptsize, for scrolling */

   maxy = iy + 1 + (bottom_row-1) * (ptsize + 1);

   /* recalc nrows because they don't seem to come out right? */

   nrows++;

   for (row = top_row; row < bottom_row; row++)
   {
      put_string(&array[l],x,y);
      strcpy(string[row],&array[l]);     /* copy it to the temp string */
      unblank(string[row]);

      l += strlen(&array[l]) + 1;
      col[row] = strlen(string[row]);
      y += ptsize + 1;
   }

   /* if there any rows beyond the bottom row */

   for (row = bottom_row; row<nrows; row++)
   {
      strcpy(string[row],&array[l]);
      l += strlen(&array[l]) + 1;
      unblank(string[row]);
      col[row] = strlen(string[row]);
   }

   flushkey();

   row = 0;      /* starting position */
   col[row] = 0;
   x = get_x(minx,col[row]);
   y = iy;
   put_editor_cursor(x,y);

   visible_rows = bottom_row - top_row;

   while (TRUE)
   {
      fg_getkey(&key,&aux);

      /* return */

      if (key == CR)
      {
         unblank(string[row]);
         remove_editor_cursor(x,y);
         if (row < bottom_row - 1)
         {
            n = end_paragraph(row,string);
            if (n < bottom_row - 1)
            {
               for (i = n; i > row; i--)
               {
                  strcpy(string[i+1],string[i]);
                  y = get_y(iy,i+1);
                  clear_string(minx,y,maxcol,maxx);
                  put_string(string[i+1],minx,y);
               }
               strcpy(string[row+1],&string[row][col[row]]);
               string[row][col[row]] = NULL;
               x = get_x(minx,col[row]);
               y = get_y(iy,row);
               clear_string(x,y,maxcol,maxx);
               y = get_y(iy,row+1);
               clear_string(minx,y,maxcol,maxx);
               put_string(string[row+1],minx,y);
            }
            row++;
         }
         else if (bottom_row < nrows - 1)
         {
            fg_setcolor(background);
            fg_scroll(minx,maxx,(iy+1),maxy,-(ptsize+1),1);
            fg_setcolor(color);
            bottom_row++;
            top_row++;
            row++;
            y = get_y(iy,bottom_row-1);
            put_string(string[bottom_row-1],minx,y);
         }
         else
            row = top_row;

         col[row] = 0;
         x = get_x(minx,col[row]);
         y = get_y(iy,row);
         put_editor_cursor(x,y);
      }

      /* F1 -- help screen */

      else if (aux == F1)
      {
         remove_editor_cursor(x,y);
         help_screen();
         fg_setcolor(15);
         put_editor_cursor(x,y);
      }

      /* back space */

      else if (key == BS && col[row] > 0)
      {
         remove_editor_cursor(x,y);
         col[row]--;
         string[row][col[row]] = NULL; /* remove a character */

         strcpy(tempstring,string[row]);
         strcat(tempstring,&string[row][col[row]+1]);
         strcpy(string[row],tempstring);

         x = get_x(minx,col[row]);
         clear_string(x,y,maxcol,maxx);
         put_string(&string[row][col[row]],x,y);

         put_editor_cursor(x,y);
      }

      /* delete */

      else if (aux == DELETE)
      {
         if (string[row][col[row]] == NULL && row < bottom_row-1)
            /* delete at end of line */
         {
            end[row] = strlen(string[row]);
            end[row+1] = strlen(string[row+1]);

            b = first_blank(string[row+1]);
            if (end[row] + b <= maxcol)
            {
               remove_editor_cursor(x,y);

               /* see if you can wrap more than one word */

               while (end[row]+b <= maxcol && b < end[row+1])
               {
                  n = b;
                  b += first_blank(&string[row+1][b+1]) + 1;
               }
               if (end[row]+b > maxcol) b = n;

               if (string[row+1][b] == NULL) /* wrap whole line */
               {
                  strcat(string[row],string[row+1]); /* move all lines up */

                  for (i = row+1; i < bottom_row; i++)
                  {
                     strcpy(string[i],string[i+1]);
                     y = get_y(iy,i);
                     clear_string(minx,y,maxcol,maxx);
                     put_string(string[i],minx,y);
                  }
                  for (i = bottom_row; i < nrows-1; i++)
                  {
                     strcpy(string[i],string[i+1]);
                  }
                  string[nrows-1][0] = NULL;
               }
               else  /* wrap one word of several on next line */
               {
                  string[row+1][b] = NULL;
                  strcat(string[row],string[row+1]);
                  strcpy(tempstring,&string[row+1][b+1]);
                  strcpy(string[row+1],tempstring);
                  y = get_y(iy,row+1);
                  clear_string(minx,y,maxcol,maxx);
                  put_string(string[row+1],minx,y);
               }
               x = get_x(minx,col[row]);
               y = get_y(iy,row);
               put_string(&string[row][col[row]],x,y);
               put_editor_cursor(x,y);
            }
            continue;
         }
         else if (string[row][col[row]] != NULL)
         {
            string[row][col[row]] = NULL; /* remove a character */

            strcpy(tempstring,string[row]);
            strcat(tempstring,&string[row][col[row]+1]);
            strcpy(string[row],tempstring);

            clear_string(x,y,maxcol,maxx);
            put_string(&string[row][col[row]],x,y);
         }
      }

      /* line delete -- ctrl y */

      else if (key == CTRL_Y)
      {
         col[row] = 0;
         string[row][col[row]] = NULL;
         remove_editor_cursor(x,y);
         x = minx;
         clear_string(x,y,maxcol,maxx);
         put_editor_cursor(x,y);
      }

      /* left arrow */

      else if (aux == LEFT_ARROW)
      {
         if (col[row] > 0)
         {
            remove_editor_cursor(x,y);
            col[row]--;
            x = get_x(minx,col[row]);
            put_editor_cursor(x,y);
         }
         else if (row > 0)
         {
            remove_editor_cursor(x,y);
            row--;
            end[row] = strlen(string[row]);
            col[row] = MIN(end[row],maxcol-1);
            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            put_editor_cursor(x,y);
         }
      }

      /* right arrow */

      else if (aux == RIGHT_ARROW)
      {
         if (col[row] < maxcol-1)
         {
            if (string[row][col[row]] == NULL)
            {
                string[row][col[row]] = 32;
                string[row][col[row]+1] = NULL;
            }
            remove_editor_cursor(x,y);
            col[row]++;
            x = get_x(minx,col[row]);
            put_editor_cursor(x,y);
         }
         else if (row < bottom_row-1)
         {
            remove_editor_cursor(x,y);
            row++;
            col[row] = 0;
            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            put_editor_cursor(x,y);
         }
      }

      /* up arrow */

      else if (aux == UP_ARROW)
      {
         if (row > top_row)
         {
            unblank(string[row]);
            remove_editor_cursor(x,y);
            row--;
            end[row] = strlen(string[row]);
            col[row] = MIN(end[row],col[row+1]);
            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            put_editor_cursor(x,y);
         }
         else if (top_row > 0)
         {
            unblank(string[row]);
            remove_editor_cursor(x,y);

            end[row] = strlen(string[row]);
            col[row] = 0;

            bottom_row--;
            top_row--;
            row--;

            fg_setcolor(background);
            fg_scroll(minx,maxx,miny-1,maxy-ptsize-3,ptsize+1,1);
            fg_setcolor(color);

            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            put_string(string[row],minx,y);
            put_editor_cursor(x,y);
         }
      }

      /* down arrow */

      else if (aux == DOWN_ARROW)
      {
         if (row < bottom_row-1)
         {
            unblank(string[row]);
            remove_editor_cursor(x,y);
            row++;
            end[row] = strlen(string[row]);
            col[row] = MIN(end[row],col[row-1]);
            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            put_editor_cursor(x,y);
         }
         else if (bottom_row < nrows-1)
         {
            unblank(string[row]);
            remove_editor_cursor(x,y);

            bottom_row++;
            top_row++;
            row++;

            end[row] = strlen(string[row]);
            col[row] = MIN(end[row],col[row-1]);

            fg_setcolor(background);
            fg_scroll(minx,maxx,iy+1,maxy,-(ptsize+1),1);
            fg_setcolor(color);

            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            put_string(string[row],minx,y);
            put_editor_cursor(x,y);
         }

      }

      /* PgUp */

      else if (aux == PAGE_UP && row > 0)
      {
         if (top_row > visible_rows)
         {
            top_row -= visible_rows;
            bottom_row -= visible_rows;
         }
         else
         {
            top_row = 0;
            bottom_row = visible_rows;
         }

         fg_setcolor(background);
         fg_rect(minx,maxx,miny,maxy);
         fg_setcolor(color);

         for (row = top_row; row < bottom_row; row++)
         {
             y = get_y(iy,row);
             put_string(string[row],minx,y);
         }

         row = top_row;

         end[row] = strlen(string[row]);
         col[row] = 0;
         x = get_x(minx,col[row]);
         y = get_y(iy,row);
         put_editor_cursor(x,y);
      }

      /* PgDn */

      else if (aux == PAGE_DOWN)
      {
         if (bottom_row < nrows-visible_rows)
         {
            top_row += visible_rows;
            bottom_row += visible_rows;
         }
         else
         {
            top_row = nrows - visible_rows - 1;
            bottom_row = nrows - 1;
         }

         fg_setcolor(background);
         fg_rect(minx,maxx,miny,maxy);
         fg_setcolor(color);

         for (row = top_row; row < bottom_row; row++)
         {
             y = get_y(iy,row);
             put_string(string[row],minx,y);
         }

         row = top_row;

         end[row] = strlen(string[row]);
         col[row] = 0;
         x = get_x(minx,col[row]);
         y = get_y(iy,row);
         put_editor_cursor(x,y);
      }

      /* home */

      else if (aux == HOME)
      {
         remove_editor_cursor(x,y);
         col[row] = 0;
         x = get_x(minx,col[row]);
         y = get_y(iy,row);
         put_editor_cursor(x,y);
      }

      /* end */

      else if (aux == END)
      {
         remove_editor_cursor(x,y);
         unblank(string[row]);
         end[row] = strlen(string[row]);
         col[row] = MIN(end[row],maxcol-1);
         x = get_x(minx,col[row]);
         y = get_y(iy,row);
         put_editor_cursor(x,y);
      }

      /* insert toggles insert mode */

      else if (aux == INSERT)
      {
         overstrike = !overstrike;
         put_editor_cursor(x,y);
      }
      /* esc or F10 to return */

      else if (aux == F10 || key == ESC)
         break;

      /* printable character -- insert mode */

      if ((is_char(key) || key == SPACEBAR) && !overstrike)
      {
         l = 0;
         remove_editor_cursor(x,y);

         strcpy(tempstring,&string[row][col[row]]);
         string[row][col[row]] = key;
         string[row][col[row]+1] = NULL;
         strcat(string[row],tempstring);
         end[row] = strlen(string[row]);

         /* row too long, must wrap */

         if (end[row] > maxcol || col[row] == maxcol-1)
         {
            if (row == bottom_row-1 && bottom_row < nrows-1)
            {
               fg_setcolor(background);
               fg_scroll(minx,maxx,iy+1,maxy,-(ptsize+1),1);
               fg_setcolor(color);
               bottom_row++;
               top_row++;
               y = get_y(iy,row+1);
               put_string(string[row+1],minx,y);
            }

            if (row < bottom_row-1)
            {
               b = last_blank(string[row]) + 1;
               if (can_wrap(&string[row][b],string[row+1],maxcol))
               {

                  l = b - col[row];
                  word_wrap(&string[row][b],string[row+1]);
                  string[row][b] = NULL;
                  unblank(string[row]);
                  unblank(string[row+1]);

                  /* redraw the second row of text */

                  y = get_y(iy,row+1);
                  clear_string(minx,y,maxcol,maxx);
                  put_string(string[row+1],minx,y);

                  /* recalc cursor position */

                  if (col[row] >= b) /* cursor is in word that gets wrapped */
                  {
                     /* erase last word of row */

                     x = get_x(minx,b);
                     y = get_y(iy,row);
                     clear_string(x,y,maxcol,maxx);

                     row++;
                     col[row] = col[row-1] - b;
                     unblank(string[row]);
                  }
                  else /* cursor stays on current line */
                  {
                     x = get_x(minx,col[row]);
                     y = get_y(iy,row);
                     clear_string(x,y,maxcol,maxx);
                     put_string(&string[row][col[row]],x,y);
                  }
               }
               else  /* can't wrap: shorten string */
               {
                  string[row][maxcol] = NULL;
                  y = get_y(iy,row);
                  clear_string(x,y,maxcol,maxx);
                  put_string(&string[row][col[row]],x,y);
               }

               if (col[row] < maxcol-1)
                  col[row]++;
               else if (row < bottom_row)
               {
                  row++;
                  col[row] = 0;
                  unblank(string[row]);
               }

               x = get_x(minx,col[row]);
               y = get_y(iy,row);
               put_editor_cursor(x,y);
            }
            else /* row = bottom_row-1 */
            {
               string[row][maxcol] = NULL;
               clear_string(x,y,maxcol,maxx);
               y = get_y(iy,row);
               put_string(&string[row][col[row]],x,y);
               x = get_x(minx,col[row]);
               y = get_y(iy,row);
               put_editor_cursor(x,y);
            }
         }

         else  /* wrapping not necessary */

         {
            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            clear_string(x,y,maxcol,maxx);
            put_string(&string[row][col[row]],x,y);

            end[row] = strlen(string[row]);

            if (col[row] <= end[row]+1 && col[row] < maxcol-1)
                col[row]++;
            else if (row < bottom_row-1)
            {
               row++;
               col[row] = l;
            }
            else
            {
               x = get_x(minx,col[row]);
               y = get_y(iy,row);
               string[row][maxcol] = NULL;
               clear_string(x,y,maxcol,maxx);
               put_string(&string[row][col[row]],x,y);
            }
            x = get_x(minx,col[row]);
            y = get_y(iy,row);
            put_editor_cursor(x,y);
         }
      }

      /* printable character -- overstrike mode */

      else if ((is_char(key) || key == SPACEBAR) && overstrike)
      {
         remove_editor_cursor(x,y);
         string[row][col[row]] = key;
         char_string[0] = key;
         clear_string(x,y,1,maxx);
         put_string(char_string,x,y);

         if (col[row] < maxcol-1)
             col[row]++;

         /* trying to write a character past the end of a row, must wrap */

         else if (row < bottom_row-1)
         {
            b = last_blank(string[row])+1;
            l = strlen(&string[row][b]);
            unblank(string[row+1]);
            if (can_wrap(&string[row][b],string[row+1],maxcol))
            {
               word_wrap(&string[row][b],string[row+1]);
               string[row][b] = NULL;

               /* redraw the two rows of text */

               y = get_y(iy,row);
               clear_string(minx,y,maxcol,maxx);
               put_string(string[row],minx,y);

               row++;
               unblank(string[row]);

               y = get_y(iy,row);
               clear_string(minx,y,maxcol,maxx);
               put_string(string[row],minx,y);

               col[row] = l;

            }
            else
            {
               row++;
               col[row] = 0;
               unblank(string[row]);
            }
         }

         x = get_x(minx,col[row]);
         y = get_y(iy,row);
         put_editor_cursor(x,y);
      }
   }

   /* end while */

   l = 0;                       /* put all the strings in one array */
   for (n = 0; n < nrows; n++)  /* separated by nulls */
   {
      strcpy(&array[l],string[n]);
      l += strlen(string[n]) + 1;
      free (string[n]);    /* free the temporary arrays */
      array[l] = 0;
      l++;
   }

   *array_len = l;

   free(tempstring);
   return(OK);
}

/**********************************************************************\
*                                                                      *
*  end_paragraph -- find the first blank line after a line of text     *
*                                                                      *
\**********************************************************************/

end_paragraph(row,string)
char *string[60];
int row;
{
   register int n;

   for (n = row+1; n < 60; n++)
   {
      unblank(string[n]);
      if (string[n][0] == NULL)
         break;
   }
   n--;
   return(n);
}

/**********************************************************************\
*                                                                      *
* first_blank -- find the first blank space before the end of a string *
*                                                                      *
\**********************************************************************/

first_blank(s)
char *s;
{
   register int i;
   int nchar;

   nchar = strlen(s);

   i = 0;
   while (i <= nchar && is_char(s[i]))
       i++;

   /* return value of blank */

   return(i);
}

/**********************************************************************\
*                                                                      *
*  get_x -- calculate the x position based on row and column           *
*                                                                      *
\**********************************************************************/

get_x(x,col)
int x, col;
{
   return(x + col * CHAR_WIDTH);
}

/**********************************************************************\
*                                                                      *
*  get_y -- calculate the y position based on row and column           *
*                                                                      *
\**********************************************************************/

get_y(y,row)
int y, row;
{
   return(y + (row-top_row) * (ptsize+1));
}

/**********************************************************************\
*                                                                      *
*  help_screen -- display keystroke help                               *
*                                                                      *
\**********************************************************************/

void help_screen()
{
   register int i;
   unsigned char key, aux;

   static char string[13][50] = {
   " Arrows: move the cursor around",
   " Del:    delete a letter",
   " Ctrl-Y: delete the whole line",
   " Home:   go to beginning of line",
   " End:    go to end of line",
   " PgUp:   go up one screen",
   " PgDn:   go down one screen",
   " Enter:  go to the next line",
   " Ins:    insert or overstrike" ,
   " Esc:    done editing" ,
   " "
   "   press any key to exit help"
   };

   static int line [] = {46,58,70,82,94,106,118,130,142,154,166,182};

   fg_save(10,309,25,189);

   background = 2;
   fg_setcolor(2);
   fg_rect(10,309,25,189);
   fg_setcolor(15);
   draw_box(11,308,26,188);

   for (i = 0; i < 12; i++)
   {
      put_string(string[i],14,line[i]);
   }
   fg_getkey(&key,&aux);
   fg_restore(10,309,25,189);
   background = 0;
}

/**********************************************************************\
*                                                                      *
*  is_char -- is it a legitimate character that we can display?        *
*                                                                      *
\**********************************************************************/

is_char(c)
char c;
{
   if (c > 32 && c <= 126)
      return(TRUE);
   else
      return(FALSE);
}

/**********************************************************************\
*                                                                      *
*  last_blank -- find the last blank space before the end of a string  *
*                                                                      *
\**********************************************************************/

last_blank(s)
char *s;
{
   register int i;

   /* find the end of the string */

   for (i = 0; s[i]; i++)
       ;

   /* count backwards, finding the blank */

   while (i > 0 && is_char(s[i-1]))
       i--;

   i--;  /* next character back must be non-alpha */

   /* return value of blank */

   return(i);
}

/**********************************************************************\
*                                                                      *
*  put_editor_cursor -- put a cursor under where the next char goes    *
*                                                                      *
\**********************************************************************/

void put_editor_cursor(x,y)
int x, y;
{
   int color;

   color = fg_getcolor();

   if (mode06 || mode11)
      fg_setcolor(1);
   else if (overstrike)
      fg_setcolor(2);
   else
      fg_setcolor(14);

   fg_move(x,y+1);
   fg_draw(x+8,y+1);
   fg_setcolor(color);
}

/**********************************************************************\
*                                                                      *
*  remove_editor_cursor -- delete the cursor from its current position *
*                                                                      *
\**********************************************************************/

void remove_editor_cursor(x,y)
int x, y;
{
   int color;

   color = fg_getcolor();
   fg_setcolor(background);
   fg_move(x,y+1);
   fg_draw(x+8,y+1);
   fg_setcolor(color);
}

/**********************************************************************\
*                                                                      *
*  unblank -- remove trailing blanks and other garbage from a string   *
*                                                                      *
\**********************************************************************/

void unblank(s)
char *s;
{
   register int i;

   /* find the end of the string */

   for (i = 0; s[i]; i++)
       ;

   /* count back trailing spaces */

   while (i > 0 && !is_char(s[i-1]))
       i--;

   /* set new end of string */

   s[i] = '\0';
}

/**********************************************************************\
*                                                                      *
*  word_wrap -- take the last word of one string, put at beginning of  *
*  next string.                                                        *
*                                                                      *
\**********************************************************************/

void word_wrap(s1,s2)
char *s1, *s2;
{
   char tempstring[60];

   strcpy(tempstring,s1);
   strcat(tempstring," ");
   strcat(tempstring,s2);
   strcpy(s2,tempstring);
}
