/*
 * This is a general purpose editing function.  It returns a string.
 * by: James Willie (jwillie@werple.apana.org.au)
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <pc.h>
#include <keys.h>
#include <gppconio.h>

#include "mlj_edit.h"

#define BUFFER_LINES	400      /* Max number if lines read in     */
#define LINELEN		100	 /* How much allocate for each line */
#define START_ROW	3
#define SCREEN(X)	(X+START_ROW-top_row) /* map buffer row to screen */
#define bot_row		(top_row+20)

static void beep();
static void insert_line(int r);
static void delete_line(int r);
static void init_buffer();
static void string2buffer(char *s);
static void buffer2string(char *s);
static void handle_input();
static void show_info();
static void do_redraw_screen();
static void do_draw_header();
static void help_screen();

unsigned char word_wrap;

static int aborted;
static int row;
static int col;
static int max_row;
static int top_row;
static int store_row;
static int store_col;
static short store_screen[4096];
static char buffer[BUFFER_LINES][LINELEN];
static unsigned char cols[BUFFER_LINES];
static char header[100] = "\t\tEdit string by J.Willie. Copyright (C) 1994";

char *edit_string(char *buf, int create_mem)
{
  char *p;

  aborted = row = col = max_row = top_row = 0;

  init_buffer();
  string2buffer(buf);

  /* Save current screen and setup edit screen */
  ScreenRetrieve(store_screen);
  ScreenGetCursor(&store_row, &store_col);
  ScreenClear();
  ScreenSetCursor(0, 0);
  do_draw_header();
  do_redraw_screen();
  ScreenSetCursor(SCREEN(row), col);
  handle_input();
    
  /* Restore the old screen */
  ScreenUpdate(store_screen);
  ScreenSetCursor(store_row, store_col);

  if (create_mem == 0)
  {
    buffer2string(buf);
    p = buf;
  }
  else
  {
    int i, size;

    for (i = 0, size = 0; i <= max_row; i++)
      size += cols[i];
    p = (char *)malloc(size+1);
    buffer2string(p);
  }

  return ((aborted == 1) ? (char *)NULL : p);
}  

/*
**  This is the REAL guts of the program.  Handles all the input and decides
**  what should be done with it.  The function is invisible to the program
**  using edit_string()
*/
static void handle_input()
{
  int c;
  int i, j;
  int start_word = -1;
  int in_word = 0;

  while(1)
  {
    show_info();
    ScreenSetCursor(SCREEN(row), col);
    c = getxkey();

    if ((in_word == 0) && !isspace(c))
    {
      in_word = 1;
      start_word = col;
    }
    else if (isspace(c))
    {
      start_word = -1;
      in_word = 0;
    }
        
    switch (c)
    {
      case K_BackSpace:           
      case K_Delete:              
        if (col <= 0)  /* Need to join lines */
        {
          if (row > START_ROW)
          {
            row--;
            col = cols[row];

            /* Copy start of next line to end of this one */
            for (i = 0; (col <= 78); i++, col++)
            {
              buffer[row][col] = buffer[row+1][i];
              if (buffer[row+1][i] == '\0')
                break;
            }

            if (buffer[row+1][i] == '\0')
            {
              delete_line(row+1);
            }
            else
            {
              i--;
              if (buffer[row][col-1] != '\n')
              {
                buffer[row][col] = '\n';
                buffer[row][col+1] = '\0';
              }
              else
              {
                buffer[row][col] = '\0';
                col--;
              }
              /* Copy Rest of the next line, to start of line */
              for (j = 0; buffer[row+1][i] != '\0'; j++, i++)
              {
                buffer[row+1][j] = buffer[row+1][i];
              }    
              cols[row+1] = j-1;
              for (; j < 80; j++)
                buffer[row+1][j] = '\0';
            }

            i = cols[row];    /* Remember where we want the cursor */
            cols[row] = col;  /* Set new line length */
            col = i; 
            do_redraw_screen();
          }
          else
          {
            beep();
          }
        }
        else
        {
          for (i = col; buffer[row][i] != '\0'; i++)
          {
            buffer[row][i-1] = buffer[row][i];
            if (buffer[row][i] == '\n')
              ScreenPutChar(' ', 7, i-1, SCREEN(row));
            else
              ScreenPutChar(buffer[row][i], 7, i-1, SCREEN(row));
          }
          ScreenPutChar(' ', 7, i, SCREEN(row));
          buffer[row][i-1] = '\0';
          col--;
          cols[row] -= 1;
        }
        break;

      case K_Tab:                 
      case K_Escape:              

      case K_F2:                  
      case K_F3:                  
      case K_F4:                  
      case K_F5:                  
      case K_F6:                  
      case K_F7:                  
      case K_F8:                  
      case K_F9:                  
      case K_F10:                 
      case K_F11:                 
      case K_F12:                 
      case K_EHome:               
      case K_EPageUp:             
      case K_EEnd:                
      case K_EPageDown:           
      case K_EInsert:             
      case K_EDelete:             
        break;

      case K_EUp:                 
      case K_Up:                  
        if (row > START_ROW)
        {
          row--;
          if (col > cols[row])
            col = cols[row];
          if (row < top_row)
          {
            top_row = row;
            do_redraw_screen();
          }
        }
        else
        {
          beep();
        }
        break;

      case K_Left:                
      case K_Eleft:               
        if (col <= 0)
        {
          if (row > START_ROW)
          {
            row--;
            col = cols[row];
          }
          else
          {
            beep();
          }
        }
        else
        {
          col--;
        }
        break;

      case K_Right:               
      case K_ERight:              
        if ((col >= 78) || (col >= cols[row]))
        {
          if (row == max_row)
          {
            
          }
          else
          {
            col = 0;
            row++;
          }
        }
        else
          col++;
        break;
        
      case K_Down:                
      case K_EDown:               
        if (row < max_row)
        {
          row++;
          if (cols[row] < col)
            col = cols[row];
          if (row >= bot_row)
          {
            top_row++;
            do_redraw_screen();
          }
        }
        else
        {
          beep();
        }
        break;

      case K_PageUp:              
      case K_Home:                
      case K_Center:              
      case K_End:                 
      case K_PageDown:            
      case K_Insert:              
      case K_Shift_F2:            
      case K_Shift_F3:            
      case K_Shift_F4:            
      case K_Shift_F5:            
      case K_Shift_F6:            
      case K_Shift_F7:            
      case K_Shift_F8:            
      case K_Shift_F9:            
      case K_Shift_F10:           
      case K_Shift_F11:           
      case K_Shift_F12:           
        break;

      /* Ignore Alt keys */
      case K_Alt_KPMinus:         
      case K_Alt_KPPlus:          
      case K_Alt_KPStar:          
      case K_Alt_F2:              
      case K_Alt_F3:              
      case K_Alt_F4:              
      case K_Alt_F5:              
      case K_Alt_F6:              
      case K_Alt_F7:              
      case K_Alt_F8:              
      case K_Alt_F9:              
      case K_Alt_F10:             
      case K_Alt_F11:             
      case K_Alt_F12:             
      case K_Alt_1:               
      case K_Alt_2:               
      case K_Alt_3:               
      case K_Alt_4:               
      case K_Alt_5:               
      case K_Alt_6:               
      case K_Alt_7:               
      case K_Alt_8:               
      case K_Alt_9:               
      case K_Alt_0:               
      case K_Alt_Dash:            
      case K_Alt_Equals:          
      case K_Alt_EHome:           
      case K_Alt_EUp:             
      case K_Alt_EPageUp:         
      case K_Alt_ELeft:           
      case K_Alt_ERight:          
      case K_Alt_EEnd:            
      case K_Alt_EDown:           
      case K_Alt_EPageDown:       
      case K_Alt_EInsert:         
      case K_Alt_EDelete:         
      case K_Alt_KPSlash:         
      case K_Alt_Tab:             
      case K_Alt_Enter:           
      case K_Alt_Escape:          
      case K_Alt_Backspace:       
      case K_Alt_Q:               
      case K_Alt_W:               
      case K_Alt_E:               
      case K_Alt_R:               
      case K_Alt_T:               
      case K_Alt_Y:               
      case K_Alt_U:               
      case K_Alt_I:               
      case K_Alt_O:               
      case K_Alt_P:               
      case K_Alt_LBracket:        
      case K_Alt_RBracket:        
      case K_Alt_Return:          
      case K_Alt_A:               
      case K_Alt_S:               
      case K_Alt_D:               
      case K_Alt_F:               
      case K_Alt_G:               
      case K_Alt_H:               
      case K_Alt_J:               
      case K_Alt_K:               
      case K_Alt_L:               
      case K_Alt_Semicolon:       
      case K_Alt_Quote:           
      case K_Alt_Backquote:       
      case K_Alt_Backslash:       
      case K_Alt_Z:               
      case K_Alt_X:               
      case K_Alt_C:               
      case K_Alt_V:               
      case K_Alt_B:               
      case K_Alt_N:               
      case K_Alt_M:               
      case K_Alt_Comma:           
      case K_Alt_Period:          
      case K_Alt_Slash:          
        break;

      case K_Control_X:           
        return;
        break;
      case K_Control_R:           
        do_redraw_screen();
        break;

      case K_Control_A:           
#ifdef DEBUG
        ScreenClear();
        ScreenSetCursor(0,0);
        for(i = START_ROW; i <= max_row; i++)
        {
          printf("%s%2d:%-3d\033[0m    ",
                 (((i >= top_row) && (i<=bot_row)) ? "\033[35m" : ""),
                 i-START_ROW, cols[i]);
          if ((i-START_ROW)%5 == 4)
            printf("\n");
        }
        printf("\ntop_row = %d(%d),   bot_row = %d(%d)\n",
               top_row, (top_row-START_ROW), bot_row, (bot_row-START_ROW));
        getxkey();
        ScreenClear();
        do_draw_header();
        do_redraw_screen();
#endif
        break;

      /* Help keys */
      case K_Control_F1:          
      case K_Alt_F1:              
      case K_Shift_F1:            
      case K_F1:                  
        help_screen();
        break;

      /* ABORT the editing.  We set the aborted variable and return NULL */
      case K_Control_Q:           
        ScreenSetCursor(8, 6);
        printf(" =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= \n");
        ScreenSetCursor(9, 6);
        printf(" |      Are you sure you want to abort [Y]es/[N]o ?      | \n");
        ScreenSetCursor(10, 6);
        printf(" =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= \n");
        ScreenSetCursor(9, 58);
        i = getxkey();
        ScreenClear();
        do_draw_header();
        do_redraw_screen();
        if ((i == 'Y') || (i == 'y'))
        {
          aborted = 1;
          return;
        }
        break;
        
      /* Lets just beep at control chars except control X */
      case K_Control_ELeft:       
      case K_Control_ERight:      
      case K_Control_EEnd:        
      case K_Control_EPageDown:   
      case K_Control_EHome:
      case K_Control_EPageUp:      
      case K_Control_EUp:
      case K_Control_EDown:
      case K_Control_EInsert:
      case K_Control_EDelete:   
      case K_Control_B:           
      case K_Control_C:           
      case K_Control_D:           
      case K_Control_E:           
      case K_Control_F:           
      case K_Control_G:           
      case K_Control_K:           
      case K_Control_L:           
      case K_Control_N:           
      case K_Control_O:           
      case K_Control_P:           
      case K_Control_S:           
      case K_Control_T:           
      case K_Control_U:           
      case K_Control_V:           
      case K_Control_W:           
      case K_Control_Y:           
      case K_Control_Z:           
      case K_Control_BackSlash:   
      case K_Control_RBracket:    
      case K_Control_Caret:       
      case K_Control_Underscore:  
      case K_Control_At:          
      case K_Control_Backspace:   
      case K_Control_F2:          
      case K_Control_F3:          
      case K_Control_F4:          
      case K_Control_F5:          
      case K_Control_F6:          
      case K_Control_F7:          
      case K_Control_F8:          
      case K_Control_F9:          
      case K_Control_F10:         
      case K_Control_Left:        
      case K_Control_Right:       
      case K_Control_End:         
      case K_Control_PageDown:    
      case K_Control_Home:        
      case K_Control_PageUp:      
      case K_Control_F11:         
      case K_Control_F12:         
      case K_Control_Up:          
      case K_Control_KPDash:      
      case K_Control_Center:      
      case K_Control_KPPlus:      
      case K_Control_Down:        
      case K_Control_Insert:      
      case K_Control_Delete:      
      case K_Control_KPSlash:     
      case K_Control_KPStar:      
        break;
  
      case K_LineFeed:            
      case K_Return:              
        if (col <= cols[row])
        {
          insert_line(row+1);
          /* Copy end of line to start of new one */
          for (i = col; buffer[row][i] != '\0'; i++)
          {
            buffer[row+1][i-col] = buffer[row][i];
            buffer[row][i] = '\0';
          }
          buffer[row][col] = '\n';
          cols[row] = col;
          cols[row+1] = i-col;
        }
        else
        {
          buffer[row][col] = '\n';
          buffer[row][col+1] = '\0';
          cols[row] = col;
        }
        row++;
        if (row >= bot_row)
        {
          top_row++;
        }
        if (row > max_row)
          max_row = row;
        col = 0;
        do_redraw_screen();
        break;
      
      /* Lets display these ones and store them in the buffer */
      case K_ExclamationPoint:    
      case K_DoubleQuote:         
      case K_Hash:                
      case K_Dollar:              
      case K_Percent:             
      case K_Ampersand:           
      case K_Quote:               
      case K_LParen:              
      case K_RParen:              
      case K_Star:                
      case K_Plua:                
      case K_Comma:               
      case K_Dash:                
      case K_Period:              
      case K_Slash:               
      case K_Colon:               
      case K_SemiColon:           
      case K_LAngle:              
      case K_Equals:              
      case K_RAngle:              
      case K_QuestionMark:        
      case K_At:                  
      case K_LBracket:            
      case K_BackSlash:           
      case K_RBracket:            
      case K_Caret:               
      case K_UnderScore:          
      case K_BackQuote:           
      case K_LBrace:              
      case K_Pipe:                
      case K_RBrace:              
      case K_Tilde:               
      case K_Space:               
      default:
        ScreenPutChar(c, 7, col, SCREEN(row));

        /*  Move the characters already on the line one space left */
        if (col <= cols[row])
        {
          int i;

          for (i = cols[row]; i >= col; i--)
          {
            buffer[row][i+1] = buffer[row][i];
            if (buffer[row][i+1] != '\n')
              ScreenPutChar(buffer[row][i+1], 7, i+1, SCREEN(row));
          }
          cols[row] += 1;
        }

        buffer[row][col] = (char)c;
        col++;
        if (col >= 78)
        {
          if ((in_word == 1) && (word_wrap != 0) && (start_word >= 20))
          {
            cols[row] = start_word;
            insert_line(row+1);
            if (row >= bot_row)
            {
              top_row++;
              do_redraw_screen();
            }
            for (i = start_word; (i >= 0) && (i < col); i++)
            {
              buffer[row+1][i-start_word] = buffer[row][i];
              ScreenPutChar(' ', 7, i, SCREEN(row));
              ScreenPutChar((int)buffer[row][i], 7, i-start_word,SCREEN(row+1));
              buffer[row][i] = ' ';
            }
            buffer[row][start_word] = '\n';
            buffer[row][start_word+1] = '\0';
            col = i - start_word;
            start_word = -1;
            cols[row+1] = col;
          }
          else
          {
            buffer[row][col] = '\n';
            buffer[row][col+1] = '\0';
            cols[row] = col-1;
            col = 0;
          }
          row++;
          if (row > max_row)
            max_row = row;
          if (row >= bot_row)
          {
            top_row++;
            do_redraw_screen();
          }
        }
        break;
    }
  }
}

static void do_redraw_screen()
{
  int i, j;

  ScreenSetCursor(START_ROW, 0);
  for (i = top_row; (i <= bot_row); i++)
  {
    for (j = 0; j < 80; j++)
    {
      int y = START_ROW+i-top_row;

      if ((cols[i] > j) && (buffer[i][j] != '\n'))
        ScreenPutChar(buffer[i][j], 7, j, y);
      else
       ScreenPutChar(' ', 7, j, y);
    }
  }
  ScreenSetCursor(SCREEN(row), col);
  return; 
}

static void do_draw_header()
{
  ScreenSetCursor(0,0);
  printf("%s\n", header);
  printf("MOVE: Arrow keys.  Finish: ^X  Help:F1     Max Row:      Row:   "
         "Col:   \n"
         "=-=-=-=-=-"
         "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"
        );
  show_info();
  ScreenSetCursor(SCREEN(row), col);
  return;
}

/*
**  show_info() displays the max_row, row, col variables etc
*/
static void show_info()
{
  int r,c;

  ScreenGetCursor(&r, &c);
  ScreenSetCursor(1, 54);
  printf("%-2d", max_row-START_ROW);
  fflush(stdout);
  ScreenSetCursor(1, 61);
  printf("%-2d", row-START_ROW);
  fflush(stdout);
  ScreenSetCursor(1, 68);
  printf("%-2d", col);
  fflush(stdout);
  ScreenSetCursor(r, c);
}

void init_buffer()
{
  int r;

  for (r = 0; r < BUFFER_LINES; r++)
  {
    bzero((char *)buffer[r], LINELEN);
    buffer[r][0] = '\0';
    cols[r] = 0;
  }
}

/*
**  string2buffer takes the string to be edited and turns it into
**  a buffer type.
*/
void string2buffer(char *s)
{
  int r,c;
  char *p;
  int wd = -1;

  for (p = s, r = START_ROW, c = 0; *p != '\0'; p++)
  {
    buffer[r][c] = *p;

    if (*p == '\n')
    {
      cols[r] = c;
      c = 0;
      r++;
      wd = -1;
    }
    else if (isspace(*p))
    {
      wd = -1;
      c++;
    }
    else if ((wd == -1) && (isalnum(*p)))
    {
      wd = c;
      c++;
    }
    else if (c >= 78)
    {
      if ((word_wrap != 0) && (wd > 20))
      {
        cols[r] = wd+1;
        r++;
        for (c = 0; c <= 78-wd; c++)
        {
          buffer[r][c] = buffer[r-1][wd+c];
          buffer[r-1][wd+c] = '\0';
        }
        buffer[r-1][wd] = '\n';
      }
      else
      {
        buffer[r][c+1] = '\n';
        buffer[r][c+2] = '\0';
        cols[r] = c+1;
        r++;
        c = 0;
      }
    }
    else
    {
      c++;
    }
  }
  row = r;
  max_row = r;
  col = c;
  cols[r] = c;
  top_row = START_ROW;
}

/*
**  buffer2string takes the edit buffer and turns it into a string
*/
static void buffer2string(char *s)
{
  int r;

  *s = '\0';
  for (r = 0; r <= max_row; r++)
  {
    strcat(s, buffer[r]);
  }
}

static void beep()
{
  return;
}

static void delete_line(int r)
{
  int x, y;

  for (y = r; y <= max_row; y++)
  {
    for (x = 0; x <= 80; x++)
    {
      if (x <= cols[y+1])
        buffer[y][x] = buffer[y+1][x];
      else
        buffer[y][x] = '\0';
    }
    cols[y] = cols[y+1];
  }
  for (x = 0; x < LINELEN; x++)
    buffer[y][x] = '\0';
  cols[y] = 0;
  max_row--;
}

static void insert_line(int r)
{
  int x, y;

  for (y = max_row; y >= r; y--)
  {
    for (x = 0; x <= 80; x++)
    {
      if (x <= cols[y])
        buffer[y+1][x] = buffer[y][x];
      else
        buffer[y+1][x] = '\0';
    }
    cols[y+1] = cols[y];
  }
  for (x = 0; x < LINELEN; x++)
    buffer[r][x] = '\0';
  cols[r] = 0;
  max_row++;
}

void set_header(char *s)
{
  int i;

  if ((s == NULL) || (*s == '\0'))
  {
    header[0] = '\0';
  } 
  else
  {
    for (i = 0; (i < 60) && (s[i] != '\n') && (s[i] != '\0'); i++)
      header[i] = s[i];
    header[i] = '\0';
  }
}

static void help_screen()
{
  ScreenClear();
  ScreenSetCursor(0, 0);
  printf("  Edit String by: James Willie.  Copyright (C) 1994\n\n");
  printf("^X  <-- means press control and X together\n\n");
  printf("Keys		Function\n--------------------------\n");
  printf("^X		Quit and return string to caller (save)\n");
  printf("^Q		Abort editing.\n");
  printf("^R		Redraw the screen\n");
  printf("F1		This help screen\n");
  printf("Del/Backspace	Delete the current character and move 1 space back\n");
  printf("(Arrow keys)	Move around the screen.\n");

  printf("\n\nPress a key to return to editor ");
  fflush(stdout);
  getxkey();
  
  ScreenClear();
  do_draw_header();
  do_redraw_screen();
  return;
}

int abort_edit()
{
  return ((aborted == 0) ? 0 : 1);
}