/*  readline.cpp
    A simple version of the GNU readline function.

    31.09.2001: tk, history functions were quickly hacked in.
                They're ugly, but work fine. There's no function
                to free the history buffers, but the c-runtime
                should free them on program exit.
    30.09.2001: tk, initial implementation.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of
    the License, or (at your option) any later version. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files -----
//
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <assert.h>
#include <string.h>
#include <malloc.h>
#include "readline.h"

#define HISTORY_SIZE   8

static char *history[HISTORY_SIZE];
static int   numlines = 0;


/**
 * Add a new line to the history buffer. If the
 * history is already full, the oldest entry gets
 * discarded.
 *
 * \param s  string to add
 */
static void addline(char *s)
{
  if (numlines == HISTORY_SIZE) {
    if (history[0])
      free(history[0]);
    for (int i=0; i<numlines-1; i++)
      history[i] = history[i+1];
    numlines--;
  }
                         
  history[numlines++] = strdup(s);
}


/**
 * Get a line from the history buffer.
 *
 * \param   line  line number to get
 * \return  string from the history, or NULL
 *          if line was out of bounds.
 */
static char* getline(int line)
{
  if (line>=0 || line<numlines)
    return history[line];
  else
    return NULL;
}


/**
 * A simple version of the GNU readline function. Only
 * the most basic editing functions are implemented.
 *
 * \param  s  pointer to string to read into
 * \param  n  maximum length (including \0)
 * \return    pointer to string
 */
char *readline(char *s, int n)
{
  int  c, cursor=0, len=0, line=numlines-1;
  bool reprint = true;

  *s = '\0';
  printf("\033[s");
  while ((c=getch()) != '\r') {
    // process user input
    //
    switch (c) {
      case 0x1b:  // ansi escape code?
        if (getch() == '[')
          switch(getch()) {
            case 'A':  // cursor up
              strncpy(s, getline(line), n-1);
              if (line>0)
                line--;
              cursor = len = strlen(s);
              reprint = true;
              break;
            case 'B':  // cursor down
              if (line<numlines-1)
                line++;
              strncpy(s, getline(line), n-1);
              cursor = len = strlen(s);
              reprint = true;
              break;
            case 'C':  // cursor right
              if (cursor<len) {
                cursor++;
                printf("\033[C");
              }
              break;
            case 'D':  // cursor left
              if (cursor>0) {
                cursor--;
                printf("\033[D");
              }
              break;
          }
        break;

      case 0x08:  // backspace
        if (cursor>0) {
          len--;
          cursor--;
          for (int i=cursor; i<len; i++)
            s[i] = s[i+1];
          reprint = true;
        }
        break;

      case 0x7f:  // delete
        if (cursor < len) {
          len--;
          for (int i=cursor; i<len; i++)
            s[i] = s[i+1];
          reprint = true;
        }
        break;

      default:  // insert char
        if (len<n-1) {
          for (int i=len; i>=cursor; i--)
            s[i+1] = s[i];
          s[cursor]=c;
          len++;
          cursor++;
          reprint = true;
        } else printf("\a");
        break;
    }
    assert(len >= 0);  assert(len < n);
    assert(cursor >= 0);  assert(cursor <= len);
    s[len] = '\0';

    // print new string
    //
    if (reprint) {
      printf("\033[u\033[s");
      printf("%s \b", s);
      if (len-cursor > 0)
        printf("\033[%dD", len-cursor);
      reprint = false;
    }
  };
  if (len>0)
    addline(s);

  return s;
}

