
#include "gkbd_term.h"

#include "gkbd_termio.h"
#include "gkbd_histo.h"
#include "gcore/gcommand_line.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/time.h>
#include <termios.h>
#else
#include "gsys/win32.h"
#include <conio.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

/* This code is taken (with many thanks but no money) from the GNUPLOT package
 * printable characters print as themselves (insert not overwrite)
 * ^A moves to the beginning of the line
 * ^B or Left Arrow moves back a single character
 * ^E moves to the end of the line
 * ^F or Right Arrow moves forward a single character
 * ^K kills from current position to the end of line
 * ^P or Up Arrow moves back through stack if enabled
 * ^N or Down Arrow moves forward through stack if enabled
 * ^H and DEL delete the previous character
 * ^D deletes the current character, or EOF if line is empty
 * ^L/^R redraw line in case it gets trashed
 * ^U kills the entire line
 * ^W kills last word
 * LF and CR return the entire line regardless of the cursor postition
 * EOF with an empty line returns
 * all other characters are ignored
 */

#define BACKSPACE 0x08         /* ^H */
#define SPACE    ' '

static char cur_line[MAXBUF];  /* current contents of the line */
static int cur_pos = 0;        /* current position of the cursor */
static int max_pos = 0;        /* maximum character position */

static int send_eof( char *line, int *code);
static void fix_line( );
static void redraw_line( char *prompt);
static void redraw_full_line( char *prompt);
static void clear_line( char *prompt);
static void clear_eoline( );
static void copy_line( char *line);

/**
 * Redraw prompt and empty input buffer
 *
 * @see kbd_line for parameters.
 */
void reset_kbd_line( char *prompt, char *line, int *code)
{
    /* print the prompt if necessary */
    if (*code >= 0)
        fputs( prompt, stderr);

    /* Load line if necessary */
    if (*code == 0) {
        cur_line[0] = '\0';
        cur_pos = 0;
        max_pos = 0;
        gkbd_histo_enable( );
    } else {
        clear_line( prompt);
        copy_line( line);
        if (*code == 2) {
            gkbd_histo_disable( );
        } else {
            gkbd_histo_enable( );
            /*
            gkbd_histo_pop( );
            */
        }
    }
}

static int send_eof( char *line, int *code)
{
    reset_termio( );
    copy_line( "");
    *code = 255;
    fix_line( );
    putc( '\n', stderr);
    strcpy( line, cur_line);
    return (int)strlen( line);
}

/**
 * Read a line on terminal.
 *
 * @param[in] prompt is a null terminated prompt
 * @param[out] line is a null terminated character string, supposedly long enough ??
 * @param[in,out] code is an integer variable indicating
 *    On input
 *       <0  do not print the prompt, start with a line
 *       =0  print the prompt, start with an empty line
 *       >0  print the prompt, start with a line
 *       >1  as above but ignore Recall codes
 *    On output
 *       -1  recall previous line, if any, and reenter
 *       0   OK, normal return
 *       +1  recall next line, if any, and reenter
 * @return the number of characters in the string @e line
 */
int CFC_API kbd_line( char *prompt, char *line, int *code)
{
    int cur_char;

    /* set the termio so we can do our own input processing */
    set_termio( );

    reset_kbd_line( prompt, line, code);

    /* determine if stdin is a tty or not and will enable exit on EOF if not */
    if (!isatty( STDIN_FILENO)) {
        return send_eof( line, code);
    }

    /* get characters */
    for (;;) {
        do {
#ifndef WIN32
            cur_char = getc( stdin);
#else
            cur_char = _getch( );
#endif
        } while (cur_char == EOF); /* to avoid return form getc on signal */

        if (cur_char == 011)
            cur_char = 040;        /* ^I mapped to <SP> */

        /* Convert Arrow keystrokes to Control characters: */
#ifndef WIN32
        /* <ESC> ansi */
        if (cur_char == 033) {
            /* Get the 1st extended code. */
            cur_char = getc( stdin);
            if (cur_char == 0133) {    /* [ */
                cur_char = getc( stdin);    /* Get the 2nd extended code. */
                switch (cur_char) {
                case 0104:        /* Left Arrow. <ESC>[D */
                    cur_char = 002;
                    break;
                case 0103:        /* Right Arrow. <ESC>[C */
                    cur_char = 006;
                    break;
                case 0101:        /* Up Arrow. <ESC>[A */
                    cur_char = 020;
                    break;
                case 0102:        /* Down Arrow. <ESC>[B */
                    cur_char = 016;
                    break;
                default:
                    cur_char = 0;
                    break;
                }
            } else if (cur_char == 0117) {    /* "O" is keypad 1st code */
                cur_char = getc( stdin);    /* Get the 2nd extended code. */
                switch (cur_char) {
                case 0104:        /* Left Arrow. <ESC>[D */
                    cur_char = 002;
                    break;
                case 0103:        /* Right Arrow. <ESC>[C */
                    cur_char = 006;
                    break;
                case 0101:        /* Up Arrow. <ESC>[A */
                    cur_char = 020;
                    break;
                case 0102:        /* Down Arrow. <ESC>[B */
                    cur_char = 016;
                    break;
                case 0162:        /* EDT End Of Line */
                    cur_char = 005;
                    break;
                case 0123:        /* EDT Kill Line */
                    cur_char = 025;    /* ^U */
                    break;
                case 0154:        /* EDT Kill Char */
                    cur_char = 004;    /* ^D */
                    break;
                case 0115:        /* EDT ENTER */
                    cur_char = 010;
                    break;
                case 0163:        /* EDT NEXT CHAR */
                    cur_char = 006;
                    break;
                default:
                    cur_char = 005;    /*does not any mess... */
                    break;
                }
            } else {
                cur_char = 005;    /*does not any mess... */
            }
        }
#else
        if ((cur_char == 0xe0) || (cur_char == 0)) { /* Extended */
            cur_char = _getch();

            switch(cur_char) {
            case 0107:
                cur_char = 001;
                break;
            case 0110:
                cur_char = 020;
                break;
            case 0111:
                cur_char = 0;
                break;
            case 0113:
                cur_char = 002;
                break;
            case 0115:
                cur_char = 006;
                break;
            case 0117:
                cur_char = 005;
                break;
            case 0120:
                cur_char = 016;
                break;
            case 0121:
                cur_char = 0;
                break;
            case 0122:
                cur_char = 0;
                break;
            case 0123:
                cur_char = 004;
                break;
            default:
                break;
            }
        }
#endif

        if (isprint( cur_char)) {
            int i;

            for (i = max_pos; i > cur_pos; i--) {
                cur_line[i] = cur_line[i - 1];
            }
            putc( cur_char, stderr);
            cur_line[cur_pos] = cur_char;
            cur_pos += 1;
            max_pos += 1;
            if (cur_pos < max_pos)
                fix_line( );
            cur_line[max_pos] = '\0';
            gkbd_histo_reset( );

/* else interpret unix terminal driver characters */
#ifdef VERASE
        } else if (cur_char == get_termio_char( VERASE)) {

            /* DEL? */
            if (cur_pos > 0) {
                int i;

                cur_pos -= 1;
                putc( BACKSPACE, stderr);
                for (i = cur_pos; i < max_pos; i++)
                    cur_line[i] = cur_line[i + 1];
                max_pos -= 1;
                fix_line( );
            }
#endif /* VERASE */
#ifdef VEOF
        } else if (cur_char == get_termio_char( VEOF)) {

            /* ^D? */
            if (max_pos == 0) {
                return send_eof( line, code);
            }
            if ((cur_pos < max_pos) && (cur_char == 004)) {

                /* ^D */
                int i;

                for (i = cur_pos; i < max_pos; i++)
                    cur_line[i] = cur_line[i + 1];
                max_pos -= 1;
                fix_line( );
            }
#endif /* VEOF */
#ifdef VKILL
        } else if (cur_char == get_termio_char( VKILL)) {

            /* ^U? */
            clear_line( prompt);
            gkbd_histo_reset( );
#endif /* VKILL */
#ifdef VWERASE
        } else if (cur_char == get_termio_char( VWERASE)) {

            /* ^W? */
            while ((cur_pos > 0) && (cur_line[cur_pos - 1] == SPACE)) {
                cur_pos -= 1;
                putc( BACKSPACE, stderr);
            }
            while ((cur_pos > 0) && (cur_line[cur_pos - 1] != SPACE)) {
                cur_pos -= 1;
                putc( BACKSPACE, stderr);
            }
            clear_eoline( );
            max_pos = cur_pos;
#endif /* VWERASE */
#ifdef VREPRINT
        } else if (cur_char == get_termio_char( VREPRINT)) {

            /* ^R? */
            putc( '\n', stderr);    /* go to a fresh line */
            redraw_line( prompt);
#else /* VREPRINT */
#ifdef VRPRNT                    /* on Ultrix VREPRINT is VRPRNT */
        } else if (cur_char == get_termio_char( VRPRNT)) {

            /* ^R? */
            putc( '\n', stderr);    /* go to a fresh line */
            redraw_line( prompt);
#endif /* VRPRNT */
#endif /* VREPRINT */
#ifdef VSUSP
        } else if (cur_char == get_termio_char( VSUSP)) {
            reset_termio( );
            kill( 0, SIGTSTP);
            /* process stops here */
            *code = 0;
            return 0;
#endif /* VSUSP */
        } else {

            /* do normal editing commands */

            /* some of these are also done above */
            int i;

            switch (cur_char) {
            case EOF:
            case 004:            /* ^D */
                if (cur_char == EOF || max_pos == 0) {
                    return send_eof( line, code);
                }
                if (cur_pos < max_pos) {
                    for (i = cur_pos; i < max_pos; i++)
                        cur_line[i] = cur_line[i + 1];
                    max_pos -= 1;
                    fix_line( );
                }
                break;
            case 001:            /* ^A */
                while (cur_pos > 0) {
                    cur_pos -= 1;
                    putc( BACKSPACE, stderr);
                }
                break;
            case 002:            /* ^B */
                if (cur_pos > 0) {
                    cur_pos -= 1;
                    putc( BACKSPACE, stderr);
                }
                break;
            case 005:            /* ^E */
                while (cur_pos < max_pos) {
                    putc( cur_line[cur_pos], stderr);
                    cur_pos += 1;
                }
                break;
            case 006:            /* ^F */
                if (cur_pos < max_pos) {
                    putc( cur_line[cur_pos], stderr);
                    cur_pos += 1;
                }
                break;
            case 013:            /* ^K */
                clear_eoline( );
                max_pos = cur_pos;
                break;

            /* Return the code to calling program */
            case 020:            /* ^P */
                if (*code < 2) {
                    gkbd_histo_prev( cur_line);
                    redraw_full_line( prompt);
                }
                break;
            case 016:            /* ^N */
                if (*code < 2) {
                    gkbd_histo_next( cur_line);
                    redraw_full_line( prompt);
                }
                break;

            /* go to a fresh line */
            case 014:            /* ^L */
            case 022:            /* ^R */
                putc( '\n', stderr);
                redraw_line( prompt);
                break;
            case 0177:            /* DEL */
            case 010:            /* ^H */
                if (cur_pos > 0) {
                    cur_pos -= 1;
                    putc( BACKSPACE, stderr);
                    for (i = cur_pos; i < max_pos; i++)
                        cur_line[i] = cur_line[i + 1];
                    max_pos -= 1;
                    fix_line( );
                }
                break;
            case 025:            /* ^U */
                clear_line( prompt);
                gkbd_histo_reset( );
                break;
            case 027:            /* ^W */
                while ((cur_pos > 0) && (cur_line[cur_pos - 1] == SPACE)) {
                    cur_pos -= 1;
                    putc( BACKSPACE, stderr);
                }
                while ((cur_pos > 0) && (cur_line[cur_pos - 1] != SPACE)) {
                    cur_pos -= 1;
                    putc( BACKSPACE, stderr);
                }
                clear_eoline( );
                max_pos = cur_pos;
                break;
                break;

            /* OK end of input */
            case '\n':            /* ^J */
            case '\r':            /* ^M */
                cur_line[max_pos + 1] = '\0';
                putc( '\n', stderr);
                *code = 0;
                gkbd_histo_push( cur_line);
                reset_termio( );
                strcpy( line, cur_line);
                return (int)strlen( line);
            default:
                break;
            }                    /* End switch */
        }                        /* End Else */
    }                            /* End For */
}                                /* End Code */


/* fix up the line from cur_pos to max_pos
 * do not need any terminal capabilities except backspace,
 * and space overwrites a character
 */
static void fix_line( )
{
    int i;

    /* write tail of string */
    for (i = cur_pos; i < max_pos; i++)
        putc( cur_line[i], stderr);

    /* write a space at the end of the line in case we deleted one */
    putc( SPACE, stderr);

    /* backup to original position */
    for (i = max_pos + 1; i > cur_pos; i--)
        putc( BACKSPACE, stderr);

    gkbd_histo_reset( );
}


/* redraw the entire line, putting the cursor where it belongs */
static void redraw_line( char *prompt)
{
    int i;

    putc( '\r', stderr);
    fputs( prompt, stderr);
    fputs( cur_line, stderr);

    /* put the cursor where it belongs */
    for (i = max_pos; i > cur_pos; i--)
        putc( BACKSPACE, stderr);
}


static void redraw_full_line( char *prompt)
{
    static char sav_line[MAXBUF];

    strcpy( sav_line, cur_line);
    clear_line( prompt);
    copy_line( sav_line);
    redraw_line( prompt);
}


/* clear cur_line and the screen line */
static void clear_line( char *prompt)
{
    int i;

    for (i = 0; i < max_pos; i++)
        cur_line[i] = '\0';
    for (i = cur_pos; i > 0; i--)
        putc( BACKSPACE, stderr);
    for (i = 0; i < max_pos; i++)
        putc( SPACE, stderr);
    putc( '\r', stderr);
    fputs( prompt, stderr);
    cur_pos = 0;
    max_pos = 0;
}

/* clear to end of line and the screen end of line */
static void clear_eoline( )
{
    int i;

    for (i = cur_pos; i < max_pos; i++)
        cur_line[i] = '\0';
    for (i = cur_pos; i < max_pos; i++)
        putc( SPACE, stderr);
    for (i = cur_pos; i < max_pos; i++)
        putc( BACKSPACE, stderr);
}


/* copy line to cur_line, draw it and set cur_pos and max_pos */
static void copy_line( char *line)
{
    strcpy( cur_line, line);
    fputs( cur_line, stderr);
    cur_pos = max_pos = (int)strlen( cur_line);
}

/* End of READ_LINE */

