/*
 * hist - interactive readline module
 *
 * Copyright (C) 1999-2007,2021-2023  David I. Bell
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:   1993/05/02 20:09:19
 * File existed as early as:    1993
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * Adapted from code written by Stephen Rothwell.
 *
 * GNU readline support added by Martin Buck <mbuck at debian dot org>
 *
 * Interactive readline module.  This is called to read lines of input,
 * while using emacs-like editing commands within a command stack.
 * The key bindings for the editing commands are (slightly) configurable.
 */


#include <stdio.h>
#include <ctype.h>
#if !defined(_WIN32) && !defined(_WIN64)
# include <pwd.h>
#endif

#include "have_unistd.h"
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif

#include "have_stdlib.h"
#if defined(HAVE_STDLIB_H)
#include <stdlib.h>
#endif

#include "calc.h"
#include "lib_calc.h"
#include "alloc.h"
#include "hist.h"
#include "strl.h"

#include "have_strdup.h"
#if !defined(HAVE_STRDUP)
# define strdup(x) calc_strdup((CONST char *)(x))
#endif /* HAVE_STRDUP */

#include "have_string.h"
#ifdef HAVE_STRING_H
# include <string.h>
#endif

#include "have_unused.h"


#include "errtbl.h"
#include "banned.h"     /* include after system header <> includes */


#define MIN(a,b) (((a) <= (b)) ? (a) : (b))

#if !defined(USE_READLINE)

E_FUNC FILE *curstream(void);

#define STDIN           0
#define SAVE_SIZE       256             /* size of save buffer */
#define MAX_KEYS        60              /* number of key bindings */


#define CONTROL(x)              ((char)(((int)(x)) & 0x1f))

STATIC  struct {
        char    *prompt;
        char    *buf;
        char    *pos;
        char    *end;
        char    *mark;
        int     bufsize;
        int     linelen;
        int     histcount;      /* valid history entries */
        int     curhist;
        bool    virgin_line;    /* 1 => never typed chars, 0 => chars typed */
} HS;


typedef void (*FUNCPTR)(int);

typedef struct {
        char    *name;
        FUNCPTR func;
} FUNC;

/* declare binding functions */
S_FUNC void flush_input(int key);
S_FUNC void start_of_line(int key);
S_FUNC void end_of_line(int key);
S_FUNC void forward_char(int key);
S_FUNC void backward_char(int key);
S_FUNC void forward_word(int key);
S_FUNC void backward_word(int key);
S_FUNC void delete_char(int key);
S_FUNC void forward_kill_char(int key);
S_FUNC void backward_kill_char(int key);
S_FUNC void forward_kill_word(int key);
S_FUNC void kill_line(int key);
S_FUNC void new_line(int key);
S_FUNC void save_line(int key);
S_FUNC void forward_history(int key);
S_FUNC void backward_history(int key);
S_FUNC void insert_char(int key);
S_FUNC void goto_line(int key);
S_FUNC void list_history(int key);
S_FUNC void refresh_line(int key);
S_FUNC void swap_chars(int key);
S_FUNC void set_mark(int key);
S_FUNC void yank(int key);
S_FUNC void save_region(int key);
S_FUNC void kill_region(int key);
S_FUNC void reverse_search(int key);
S_FUNC void quote_char(int key);
S_FUNC void uppercase_word(int key);
S_FUNC void lowercase_word(int key);
S_FUNC void ignore_char(int key);
S_FUNC void arrow_key(int key);
S_FUNC void quit_calc(int key) __attribute__((noreturn));


STATIC  FUNC    funcs[] =
{
        {"ignore-char",         ignore_char},
        {"flush-input",         flush_input},
        {"start-of-line",       start_of_line},
        {"end-of-line",         end_of_line},
        {"forward-char",        forward_char},
        {"backward-char",       backward_char},
        {"forward-word",        forward_word},
        {"backward-word",       backward_word},
        {"delete-char",         delete_char},
        {"forward-kill-char",   forward_kill_char},
        {"backward-kill-char",  backward_kill_char},
        {"forward-kill-word",   forward_kill_word},
        {"uppercase-word",      uppercase_word},
        {"lowercase-word",      lowercase_word},
        {"kill-line",           kill_line},
        {"goto-line",           goto_line},
        {"new-line",            new_line},
        {"save-line",           save_line},
        {"forward-history",     forward_history},
        {"backward-history",    backward_history},
        {"insert-char",         insert_char},
        {"list-history",        list_history},
        {"refresh-line",        refresh_line},
        {"swap-chars",          swap_chars},
        {"set-mark",            set_mark},
        {"yank",                yank},
        {"save-region",         save_region},
        {"kill-region",         kill_region},
        {"reverse-search",      reverse_search},
        {"quote-char",          quote_char},
        {"arrow-key",           arrow_key},
        {"quit",                quit_calc},
        {NULL,                  NULL}
};


typedef struct key_ent  KEY_ENT;
typedef struct key_map  KEY_MAP;

struct key_ent  {
        FUNCPTR         func;
        KEY_MAP         *next;
};


struct key_map {
        char            *name;
        KEY_ENT         default_ent;
        KEY_ENT         *map[256];
};


STATIC char     base_map_name[] = "base-map";
STATIC char     esc_map_name[] = "esc-map";


STATIC KEY_MAP  maps[] = {
        {base_map_name, {NULL, NULL}, {NULL, NULL}},
        {esc_map_name, {NULL, NULL}, {NULL, NULL}},
};

typedef struct history_entry {
    int len;    /* length of data */
    char *data; /* varying length data */
    struct history_entry* prev;
    struct history_entry* next;
} HIST;

STATIC  int             inited;
STATIC  int             canedit;
STATIC  int             key_count;
STATIC  int             save_len;
STATIC  KEY_MAP         *cur_map;
STATIC  KEY_MAP         *base_map;
STATIC  KEY_ENT         key_table[MAX_KEYS];
STATIC  HIST*           hist_first = NULL;
STATIC  HIST*           hist_last = NULL;
STATIC  char            save_buffer[SAVE_SIZE];

/* declare other static functions */
S_FUNC  FUNCPTR find_func(char *name);
S_FUNC  HIST    *get_event(int n);
S_FUNC  HIST    *find_event(char *pat, int len);
S_FUNC  void    read_key(void);
S_FUNC  void    erasechar(void);
S_FUNC  void    newline(void);
S_FUNC  void    backspace(void);
S_FUNC  void    beep(void);
S_FUNC  void    echo_char(int ch);
S_FUNC  void    echo_string(char *str, int len);
S_FUNC  void    savetext(char *str, int len);
S_FUNC  void    memrcpy(char *dest, char *src, int len);
S_FUNC  int     read_bindings(FILE *fp);
S_FUNC  int     in_word(int ch);
S_FUNC  KEY_MAP *find_map(char *map);
S_FUNC  void    unbind_key(KEY_MAP *map, int key);
S_FUNC  void    raw_bind_key(KEY_MAP *map, int key,
                             FUNCPTR func, KEY_MAP *next_map);
S_FUNC  KEY_MAP *do_map_line(char *line);
S_FUNC  void    do_default_line(KEY_MAP *map, char *line);
S_FUNC  void    do_bind_line(KEY_MAP *map, char *line);
S_FUNC  void    back_over_char(int ch);
S_FUNC  void    echo_rest_of_line(void);
S_FUNC  void    goto_start_of_line(void);
S_FUNC  void    goto_end_of_line(void);
S_FUNC  void    remove_char(int ch);
S_FUNC  void    decrement_end(int n);
S_FUNC  void    insert_string(char *str, int len);


/*
 * Read a line into the specified buffer.  The line ends in a newline,
 * and is NULL terminated.  Returns the number of characters read, or
 * zero on an end of file or error.  The prompt is printed before reading
 * the line.
 */
size_t
hist_getline(char *prompt, char *buf, size_t len)
{
        /*
         * initialize if we have not already done so
         */
        if (!inited)
                (void) hist_init(calcbindings);

        /*
         * establish the beginning of a line condition
         */
        HS.prompt = prompt;
        HS.bufsize = len - 2;
        HS.buf = buf;
        HS.pos = buf;
        HS.end = buf;
        HS.mark = NULL;
        HS.linelen = -1;
        HS.virgin_line = true;

        /*
         * prep the I/O
         */
        fputs(prompt, stdout);
        fflush(stdout);

        /*
         * special case: non-interactive editing
         */
        if (!canedit) {
                if (fgets(buf, len, stdin) == NULL)
                        return 0;
                return strlen(buf);
        }

        /*
         * get the line
         */
        while (HS.linelen < 0) {

                /* get the next char */
                read_key();

                /* chars typed, no longer virgin */
                HS.virgin_line = false;
        }

        /*
         * return the line
         */
        return HS.linelen;
}


/*
 * Initialize the module by reading in the key bindings from the specified
 * filename, and then setting the terminal modes for noecho and cbreak mode.
 * If the supplied filename is NULL, then a default filename will be used.
 * We will search the CALCPATH for the file.
 *
 * Returns zero if successful, or a nonzero error code if unsuccessful.
 * If this routine fails, hist_getline, hist_saveline, and hist_term can
 * still be called but all fancy editing is disabled.
 */
int
hist_init(char *filename)
{
        /*
         * prevent multiple initializations
         */
        if (inited) {
                if (conf->calc_debug & CALCDBG_TTY)
                        printf("hist_init: inited already set\n");
                return HIST_INITED;
        }

        /*
         * setup
         */
        inited = 1;
        canedit = 0;
        if (conf->calc_debug & CALCDBG_TTY)
                printf("hist_init: Set inited, cleared canedit\n");

        /*
         * open the bindings file
         */
        if (filename == NULL)
                filename = HIST_BINDING_FILE;
        if (opensearchfile(filename, calcpath, NULL, false) > 0)
                return HIST_NOFILE;

        /*
         * load the bindings
         */
        if (read_bindings(curstream()))
                return HIST_NOFILE;

        /*
         * close the bindings
         */
        closeinput();

        /*
         * setup the calc TTY on STDIN
         */
        if (!calc_tty(STDIN)) {
                return HIST_NOTTY;
        }

        canedit = 1;
        if (conf->calc_debug & CALCDBG_TTY)
                printf("hist_init: Set canedit\n");

        return HIST_SUCCESS;
}


/*
 * Reset the terminal modes just before exiting.
 */
void
hist_term(void)
{
        if (!inited || !canedit) {
                if (conf->calc_debug & CALCDBG_TTY) {
                        if (!inited)
                                printf("hist_term: inited already cleared\n");
                        if (!canedit)
                                printf("hist_term: canedit already cleared\n");
                }
                inited = 0;
                if (conf->calc_debug & CALCDBG_TTY)
                        printf("hist_term: Cleared inited\n");
                return;
        }

        if (hist_first) {
                HIST* hp = hist_first;
                HIST* next;
                while(hp) {
                        next = hp->next;
                        free(hp->data);
                        free(hp);
                        hp = next;
                }
        }
        hist_first = NULL;
        hist_last = NULL;

        /*
         * restore original tty state
         */
        (void) orig_tty(STDIN);
}


S_FUNC KEY_MAP *
find_map(char *map)
{
        unsigned int i;

        for (i = 0; i < sizeof(maps) / sizeof(maps[0]); i++) {
                if (strcmp(map, maps[i].name) == 0)
                        return &maps[i];
        }
        return NULL;
}


S_FUNC void
unbind_key(KEY_MAP *map, int key)
{
        map->map[key] = NULL;
}


S_FUNC void
raw_bind_key(KEY_MAP *map, int key, FUNCPTR func, KEY_MAP *next_map)
{
        if (map->map[key] == NULL) {
                if (key_count >= MAX_KEYS)
                        return;
                map->map[key] = &key_table[key_count++];
        }
        map->map[key]->func = func;
        map->map[key]->next = next_map;
}


S_FUNC KEY_MAP *
do_map_line(char *line)
{
        char    *cp;
        char    *map_name;

        cp = line;
        while (isspace((int)*cp))
                cp++;
        if (*cp == '\0')
                return NULL;
        map_name = cp;
        while ((*cp != '\0') && !isspace((int)*cp))
                cp++;
        *cp = '\0';
        return find_map(map_name);
}


S_FUNC void
do_bind_line(KEY_MAP *map, char *line)
{
        char            *cp;
        char            key;
        char            *func_name;
        char            *next_name;
        KEY_MAP         *next;
        FUNCPTR         func;

        if (map == NULL)
                return;
        cp = line;
        key = *cp++;
        if (*cp == '\0') {
                unbind_key(map, key);
                return;
        }
        if (key == '^') {
                if (*cp == '?') {
                        key = 0177;
                        cp++;
                } else {
                        key = CONTROL(*cp++);
                }
        } else if (key == '\\') {
                key = *cp++;
        }

        while (isspace((int)*cp))
                cp++;
        if (*cp == '\0') {
                unbind_key(map, key);
                return;
        }

        func_name = cp;
        while ((*cp != '\0') && !isspace((int)*cp))
                cp++;
        if (*cp) {
                *cp++ = '\0';
                while (isspace((int)*cp))
                        cp++;
        }
        func = find_func(func_name);
        if (func == NULL) {
                fprintf(stderr, "Unknown function \"%s\"\n", func_name);
                return;
        }

        if (*cp == '\0') {
                next = map->default_ent.next;
                if (next == NULL)
                        next = base_map;
        } else {
                next_name = cp;
                while ((*cp != '\0') && !isspace((int)*cp))
                        cp++;
                if (*cp) {
                        *cp++ = '\0';
                        while (isspace((int)*cp))
                                cp++;
                }
                next = find_map(next_name);
                if (next == NULL)
                        return;
        }
        raw_bind_key(map, key, func, next);
}


S_FUNC void
do_default_line(KEY_MAP *map, char *line)
{
        char            *cp;
        char            *func_name;
        char            *next_name;
        KEY_MAP         *next;
        FUNCPTR         func;

        if (map == NULL)
                return;
        cp = line;
        while (isspace((int)*cp))
                cp++;
        if (*cp == '\0')
                return;

        func_name = cp;
        while ((*cp != '\0') && !isspace((int)*cp))
                cp++;
        if (*cp != '\0') {
                *cp++ = '\0';
                while (isspace((int)*cp))
                        cp++;
        }
        func = find_func(func_name);
        if (func == NULL)
                return;

        if (*cp == '\0') {
                next = map;
        } else {
                next_name = cp;
                while ((*cp != '\0') && !isspace((int)*cp))
                        cp++;
                if (*cp != '\0') {
                        *cp++ = '\0';
                        while (isspace((int)*cp))
                                cp++;
                }
                next = find_map(next_name);
                if (next == NULL)
                        return;
        }

        map->default_ent.func = func;
        map->default_ent.next = next;
}


/*
 * Read bindings from specified open file.
 *
 * Returns nonzero on error.
 */
S_FUNC int
read_bindings(FILE *fp)
{
        char    *cp;
        KEY_MAP *input_map;
        char    line[BUFSIZ+1];

        base_map = find_map(base_map_name);
        cur_map = base_map;
        input_map = base_map;

        if (fp == NULL)
                return 1;

        while (fgets(line, sizeof(line) - 1, fp)) {
                line[BUFSIZ] = '\0';
                cp = line;
                while (isspace((int)*cp))
                        cp++;

                if ((*cp == '\0') || (*cp == '#') || (*cp == '\n'))
                        continue;

                if (cp[strlen(cp) - 1] == '\n')
                        cp[strlen(cp) - 1] = '\0';

                if (memcmp(cp, "map", 3) == 0)
                        input_map = do_map_line(&cp[3]);
                else if (memcmp(cp, "default", 7) == 0)
                        do_default_line(input_map, &cp[7]);
                else
                        do_bind_line(input_map, cp);
        }
        return 0;
}


S_FUNC void
read_key(void)
{
        KEY_ENT         *ent;
        int             key;

        fflush(stdout);
        key = fgetc(stdin);
        if (key == EOF) {
                HS.linelen = 0;
                HS.buf[0] = '\0';
                return;
        }

        ent = cur_map->map[key];
        if (ent == NULL)
                ent = &cur_map->default_ent;
        if (ent->next)
                cur_map = ent->next;
        if (ent->func != NULL) {
                (*ent->func)(key);
        } else {
                insert_char(key);
        }
}


/*
 * Return the Nth history event, indexed from zero.
 * Earlier history events are lower in number.
 */
S_FUNC HIST *
get_event(int n)
{
        register HIST * hp = hist_first;
        int i = 0;

        do {
                if(!hp)
                    break;
                if(i == n)
                    return hp;
                ++i;
                hp = hp->next;
        } while(hp);
        return NULL;
}


/*
 * Search the history list for the specified pattern.
 * Returns the found history, or NULL.
 */
S_FUNC HIST *
find_event(char *pat, int len)
{
        register HIST * hp = hist_first;

        for(hp = hist_first; hp != NULL; hp = hp->next) {
            if ((hp->len == len) && (memcmp(hp->data, pat, len) == 0))
                    return hp;
        }
        return NULL;
}


/*
 * Insert a line into the end of the history table.
 * If the line already appears in the table, then it is moved to the end.
 * If the table is full, then the earliest commands are deleted as necessary.
 * Warning: the incoming line cannot point into the history table.
 */
void
hist_saveline(char *line, int len)
{
        HIST *  hp;

        if ((len > 0) && (line[len - 1] == '\n'))
                len--;
        if (len <= 0)
                return;

        /*
         * See if the line is already present in the history table.
         * If so, and it is already at the end, then we are all done.
         * Otherwise delete it since we will reinsert it at the end.
         */
        hp = find_event(line, len);
        if (hp) {
                if (hp == hist_last)
                        return;
                if (hp->prev)
                        hp->prev->next = hp->next;
                hp->next->prev = hp->prev;

                hist_last->next = hp;
                hp->next = NULL;
                hp->prev = hist_last;
                hist_last = hp;
                return;
        }

        /*
         * If there is not enough room left in the history buffer to add
         * the new command, then repeatedly delete the earliest command
         * as many times as necessary in order to make enough room.
         */
        if (HS.histcount >= HIST_SIZE) {
                HIST *new_first = hist_first->next;
                free(hist_first->data);
                free(hist_first);
                new_first->prev = NULL;
                hist_first = new_first;
                HS.histcount--;
        }

        /*
         * Add the line to the end of the history table.
         */
        hp = malloc(sizeof(HIST));
        if (hp == NULL) {
                fprintf(stderr,
                        "Out of memory adding line to the history table #0\n");
                return;
        }
        hp->next = NULL;
        hp->prev = NULL;
        hp->len = len;
        hp->data = malloc(len);
        if (hp->data == NULL) {
                fprintf(stderr,
                        "Out of memory adding line to the history table #1\n");
                return;
        }
        memcpy(hp->data, line, len);
        HS.curhist = ++HS.histcount;
        if (!hist_first)
                hist_first = hp;
        if (!hist_last)
                hist_last = hp;
        else {
                hist_last->next = hp;
                hp->prev = hist_last;
                hist_last = hp;
        }
}


/*
 * Find the function for a specified name.
 */
S_FUNC FUNCPTR
find_func(char *name)
{
        FUNC    *fp;

        for (fp = funcs; fp->name; fp++) {
                if (strcmp(fp->name, name) == 0)
                        return fp->func;
        }
        return NULL;
}


S_FUNC void
arrow_key(int UNUSED(key))
{
        switch (fgetc(stdin)) {
        case 'A':
                backward_history(0);
                break;
        case 'B':
                forward_history(0);
                break;
        case 'C':
                forward_char(0);
                break;
        case 'D':
                backward_char(0);
                break;
        }
}


S_FUNC void
back_over_char(int ch)
{
        backspace();
        if (!isprint(ch))
                backspace();
}


S_FUNC void
remove_char(int ch)
{
        erasechar();
        if (!isprint(ch))
                erasechar();
}


S_FUNC void
echo_rest_of_line(void)
{
        echo_string(HS.pos, HS.end - HS.pos);
}


S_FUNC void
goto_start_of_line(void)
{
        while (HS.pos > HS.buf)
                back_over_char((int)(*--HS.pos));
}


S_FUNC void
goto_end_of_line(void)
{
        echo_rest_of_line();
        HS.pos = HS.end;
}


S_FUNC void
decrement_end(int n)
{
        HS.end -= n;
        if (HS.mark && (HS.mark > HS.end))
                HS.mark = NULL;
}


S_FUNC void
ignore_char(int UNUSED(key))
{
}


S_FUNC void
flush_input(int UNUSED(key))
{
        echo_rest_of_line();
        while (HS.end > HS.buf)
                remove_char((int)(*--HS.end));
        HS.pos = HS.buf;
        HS.mark = NULL;
}


S_FUNC void
start_of_line(int UNUSED(key))
{
        goto_start_of_line();
}


S_FUNC void
end_of_line(int UNUSED(key))
{
        goto_end_of_line();
}


S_FUNC void
forward_char(int UNUSED(key))
{
        if (HS.pos < HS.end)
                echo_char(*HS.pos++);
}


S_FUNC void
backward_char(int UNUSED(key))
{
        if (HS.pos > HS.buf)
                back_over_char((int)(*--HS.pos));
}


S_FUNC void
uppercase_word(int UNUSED(key))
{
        while ((HS.pos < HS.end) && !in_word((int)(*HS.pos)))
                echo_char(*HS.pos++);
        while ((HS.pos < HS.end) && in_word((int)(*HS.pos))) {
                if ((*HS.pos >= 'a') && (*HS.pos <= 'z'))
                        *HS.pos += 'A' - 'a';
                echo_char(*HS.pos++);
        }
}


S_FUNC void
lowercase_word(int UNUSED(key))
{
        while ((HS.pos < HS.end) && !in_word((int)(*HS.pos)))
                echo_char(*HS.pos++);
        while ((HS.pos < HS.end) && in_word((int)(*HS.pos))) {
                if ((*HS.pos >= 'A') && (*HS.pos <= 'Z'))
                        *HS.pos += 'a' - 'A';
                echo_char(*HS.pos++);
        }
}


S_FUNC void
forward_word(int UNUSED(key))
{
        while ((HS.pos < HS.end) && !in_word((int)(*HS.pos)))
                echo_char(*HS.pos++);
        while ((HS.pos < HS.end) && in_word((int)(*HS.pos)))
                echo_char(*HS.pos++);
}


S_FUNC void
backward_word(int UNUSED(key))
{
        if ((HS.pos > HS.buf) && in_word((int)(*HS.pos)))
                back_over_char((int)(*--HS.pos));
        while ((HS.pos > HS.buf) && !in_word((int)(*HS.pos)))
                back_over_char((int)(*--HS.pos));
        while ((HS.pos > HS.buf) && in_word((int)(*HS.pos)))
                back_over_char((int)(*--HS.pos));
        if ((HS.pos < HS.end) && !in_word((int)(*HS.pos)))
                echo_char(*HS.pos++);
}


S_FUNC void
forward_kill_char(int UNUSED(key))
{
        int     rest;
        char    ch;

        rest = HS.end - HS.pos;
        if (rest-- <= 0)
                return;
        ch = *HS.pos;
        if (rest > 0) {
                memcpy(HS.pos, HS.pos + 1, rest);
                *(HS.end - 1) = ch;
        }
        echo_rest_of_line();
        remove_char((int)ch);
        decrement_end(1);
        while (rest > 0)
                back_over_char((int)(HS.pos[--rest]));
}


S_FUNC void
delete_char(int UNUSED(key))
{
        /*
         * quit delete_char (usually ^D) is at start of line and we are allowed
         *
         * We exit of start of line and config("ctrl_d", "empty") or
         * if config("ctrl_d", "virgin") and we have never typed on the line.
         */
        if ((HS.end == HS.buf) &&
            (conf->ctrl_d == CTRL_D_EMPTY_EOF ||
             (conf->ctrl_d == CTRL_D_VIRGIN_EOF && HS.virgin_line == true))) {
                quit_calc(0);
        }

        /*
         * normal case: just forward_kill_char
         */
        if (HS.end > HS.buf)
                forward_kill_char(0);
}


S_FUNC void
backward_kill_char(int UNUSED(key))
{
        if (HS.pos > HS.buf) {
                HS.pos--;
                back_over_char((int)(*HS.pos));
                forward_kill_char(0);
        }
}


S_FUNC void
forward_kill_word(int UNUSED(key))
{
        char    *cp;

        if (HS.pos >= HS.end)
                return;
        echo_rest_of_line();
        for (cp = HS.end; cp > HS.pos;)
                remove_char((int)(*--cp));
        cp = HS.pos;
        while ((cp < HS.end) && !in_word((int)(*cp)))
                cp++;
        while ((cp < HS.end) && in_word((int)(*cp)))
                cp++;
        savetext(HS.pos, cp - HS.pos);
        memcpy(HS.pos, cp, HS.end - cp);
        decrement_end(cp - HS.pos);
        echo_rest_of_line();
        for (cp = HS.end; cp > HS.pos;)
                back_over_char((int)(*--cp));
}


S_FUNC void
kill_line(int UNUSED(key))
{
        if (HS.end <= HS.pos)
                return;
        savetext(HS.pos, HS.end - HS.pos);
        echo_rest_of_line();
        while (HS.end > HS.pos)
                remove_char((int)(*--HS.end));
        decrement_end(0);
}


/*
 * This is the function which completes a command line editing session.
 * The final line length is returned in the HS.linelen variable.
 * The line is NOT put into the edit history, so that the caller can
 * decide whether or not this should be done.
 */
S_FUNC void
new_line(int UNUSED(key))
{
        int     len;

        newline();
        fflush(stdout);

        HS.mark = NULL;
        HS.end[0] = '\n';
        HS.end[1] = '\0';
        len = HS.end - HS.buf + 1;
        if (len <= 1) {
                HS.curhist = HS.histcount;
                HS.linelen = 1;
                return;
        }
        HS.curhist = HS.histcount;
        HS.pos = HS.buf;
        HS.end = HS.buf;
        HS.linelen = len;
}


S_FUNC void
save_line(int UNUSED(key))
{
        int     len;

        len = HS.end - HS.buf;
        if (len > 0) {
                hist_saveline(HS.buf, len);
                flush_input(0);
        }
        HS.curhist = HS.histcount;
}


S_FUNC void
goto_line(int UNUSED(key))
{
        int     num;
        char    *cp;
        HIST    *hp;

        num = 0;
        cp = HS.buf;
        while ((*cp >= '0') && (*cp <= '9') && (cp < HS.pos))
                num = num * 10 + (*cp++ - '0');
        if ((num <= 0) || (num > HS.histcount) || (cp != HS.pos)) {
                beep();
                return;
        }
        flush_input(0);
        HS.curhist = HS.histcount - num;
        hp = get_event(HS.curhist);
        memcpy(HS.buf, hp->data, hp->len);
        HS.end = &HS.buf[hp->len];
        goto_end_of_line();
}


S_FUNC void
forward_history(int UNUSED(key))
{
        HIST    *hp;

        flush_input(0);
        if (++HS.curhist >= HS.histcount)
                HS.curhist = 0;
        hp = get_event(HS.curhist);
        if (hp) {
                memcpy(HS.buf, hp->data, hp->len);
                HS.end = &HS.buf[hp->len];
        }
        goto_end_of_line();
}


S_FUNC void
backward_history(int UNUSED(key))
{
        HIST    *hp;

        flush_input(0);
        if (--HS.curhist < 0)
                HS.curhist = HS.histcount - 1;
        hp = get_event(HS.curhist);
        if (hp) {
                memcpy(HS.buf, hp->data, hp->len);
                HS.end = &HS.buf[hp->len];
        }
        goto_end_of_line();
}


S_FUNC void
insert_char(int key)
{
        int     len;
        int     rest;

        len = HS.end - HS.buf;
        if (len >= HS.bufsize) {
                beep();
                return;
        }
        rest = HS.end - HS.pos;
        if (rest > 0)
                memrcpy(HS.pos + 1, HS.pos, rest);
        HS.end++;
        *HS.pos++ = key;
        echo_char(key);
        echo_rest_of_line();
        while (rest > 0)
                back_over_char((int)(HS.pos[--rest]));
}


S_FUNC void
insert_string(char *str, int len)
{
        int     rest;
        int     totallen;

        if (len <= 0)
                return;
        totallen = (HS.end - HS.buf) + len;
        if (totallen > HS.bufsize) {
                beep();
                return;
        }
        rest = HS.end - HS.pos;
        if (rest > 0)
                memrcpy(HS.pos + len, HS.pos, rest);
        HS.end += len;
        memcpy(HS.pos, str, len);
        HS.pos += len;
        echo_string(str, len);
        echo_rest_of_line();
        while (rest > 0)
                back_over_char((int)(HS.pos[--rest]));
}


S_FUNC void
list_history(int UNUSED(key))
{
        HIST    *hp;
        int     hnum;

        for (hnum = 0; hnum < HS.histcount; hnum++) {
                hp = get_event(hnum);
                printf("\n%3d: ", HS.histcount - hnum);
                echo_string(hp->data, hp->len);
        }
        refresh_line(0);
}


S_FUNC void
refresh_line(int UNUSED(key))
{
        char    *cp;

        newline();
        fputs(HS.prompt, stdout);
        if (HS.end > HS.buf) {
                echo_string(HS.buf, HS.end - HS.buf);
                cp = HS.end;
                while (cp > HS.pos)
                        back_over_char((int)(*--cp));
        }
}


S_FUNC void
swap_chars(int UNUSED(key))
{
        char    ch1;
        char    ch2;

        if ((HS.pos <= HS.buf) || (HS.pos >= HS.end))
                return;
        ch1 = *HS.pos--;
        ch2 = *HS.pos;
        *HS.pos++ = ch1;
        *HS.pos = ch2;
        back_over_char((int)ch2);
        echo_char(ch1);
        echo_char(ch2);
        back_over_char((int)ch2);
}


S_FUNC void
set_mark(int UNUSED(key))
{
        HS.mark = HS.pos;
}


S_FUNC void
save_region(int UNUSED(key))
{
        int     len;

        if (HS.mark == NULL)
                return;
        len = HS.mark - HS.pos;
        if (len > 0)
                savetext(HS.pos, len);
        if (len < 0)
                savetext(HS.mark, -len);
}


S_FUNC void
kill_region(int UNUSED(key))
{
        char    *cp;
        char    *left;
        char    *right;

        if ((HS.mark == NULL) || (HS.mark == HS.pos))
                return;

        echo_rest_of_line();
        if (HS.mark < HS.pos) {
                left = HS.mark;
                right = HS.pos;
                HS.pos = HS.mark;
        } else {
                left = HS.pos;
                right = HS.mark;
                HS.mark = HS.pos;
        }
        savetext(left, right - left);
        for (cp = HS.end; cp > left;)
                remove_char((int)(*--cp));
        if (right < HS.end)
                memcpy(left, right, HS.end - right);
        decrement_end(right - left);
        echo_rest_of_line();
        for (cp = HS.end; cp > HS.pos;)
                back_over_char((int)(*--cp));
}


S_FUNC void
yank(int UNUSED(key))
{
        insert_string(save_buffer, save_len);
}


S_FUNC void
reverse_search(int UNUSED(key))
{
        int     len;
        int     count;
        int     testhist;
        HIST    *hp;
        char    *save_pos;

        count = HS.histcount;
        len = HS.pos - HS.buf;
        if (len <= 0)
                count = 0;
        testhist = HS.curhist;
        do {
                if (--count < 0) {
                        beep();
                        return;
                }
                if (--testhist < 0)
                        testhist = HS.histcount - 1;
                hp = get_event(testhist);
        } while ((hp == NULL) || (hp->len < len) ||
                memcmp(hp->data, HS.buf, len));

        HS.curhist = testhist;
        save_pos = HS.pos;
        flush_input(0);
        memcpy(HS.buf, hp->data, hp->len);
        HS.end = &HS.buf[hp->len];
        goto_end_of_line();
        while (HS.pos > save_pos)
                back_over_char((int)(*--HS.pos));
}


S_FUNC void
quote_char(int UNUSED(key))
{
        int     ch;

        ch = fgetc(stdin);
        if (ch != EOF)
                insert_char(ch);
}


/*
 * Save data in the save buffer.
 */
S_FUNC void
savetext(char *str, int len)
{
        save_len = 0;
        if (len <= 0)
                return;
        if (len > SAVE_SIZE)
                len = SAVE_SIZE;
        memcpy(save_buffer, str, len);
        save_len = len;
}


/*
 * Test whether a character is part of a word.
 */
S_FUNC int
in_word(int ch)
{
        return (isalnum(ch) || (ch == '_'));
}


S_FUNC void
erasechar(void)
{
        fputs("\b \b", stdout);
}


S_FUNC void
newline(void)
{
        fputc('\n', stdout);
}


S_FUNC void
backspace(void)
{
        fputc('\b', stdout);
}


S_FUNC void
beep(void)
{
        fputc('\007', stdout);
}


S_FUNC void
echo_char(int ch)
{
        if (isprint(ch)) {
                putchar(ch);
        } else {
                putchar('^');
                putchar((ch + '@') & 0x7f);
        }
}


S_FUNC void
echo_string(char *str, int len)
{
        while (len-- > 0)
                echo_char(*str++);
}


S_FUNC void
memrcpy(char *dest, char *src, int len)
{
        dest += len - 1;
        src += len - 1;
        while (len-- > 0)
                *dest-- = *src--;
}

#endif /* !USE_READLINE */

S_FUNC void
quit_calc(int UNUSED(ch))
{
        hist_term();
        putchar('\n');
        libcalc_call_me_last();
        exit(0);
}

#if defined(USE_READLINE)


#define HISTORY_LEN (1024)      /* number of entries to save */


#include <readline/readline.h>
#include <readline/history.h>


/*
 * The readline/history libs do most of the dirty work for us, so we can
 * replace hist_init() and hist_term() with dummies when using readline.
 * For hist_getline() we have to add a newline that readline removed but
 * calc expects. For hist_saveline(), we have to undo this. hist_getline()
 * also has to cope with the different memory management schemes of calc and
 * readline (pointer to target buffer passed to hist_getline() vs. returned
 * malloc()ed buffer from readline()). While doing that, we also split
 * multi-line strings potentially returned by readline() in case of
 * bracketed paste mode even though its documentation promises to only return
 * single lines. For details, see https://github.com/lcn2/calc/issues/138
 * and https://lists.gnu.org/archive/html/bug-readline/2024-01/msg00000.html
 */


size_t
hist_getline(char *prompt, char *buf, size_t len)
{
        STATIC char *rlbuf, *rlcur;

        if (!rlbuf) {
                rlbuf = rlcur = readline(prompt);
                if (!rlbuf) {
                        buf[0] = '\0';
                        switch (conf->ctrl_d) {
                        case CTRL_D_NEVER_EOF:
                                return 0;
                        case CTRL_D_VIRGIN_EOF:
                        case CTRL_D_EMPTY_EOF:
                        default:
                                quit_calc(0);
                                not_reached();
                        }
                }
        }

        /* eol: pointer to trailing newline (if there is one) or \0 */
        char *eol = strchr(rlcur, '\n');
        if (!eol) {
                eol = rlcur + strlen(rlcur);
        }
        /* len: length of line in target buffer including (possibly added)
         * newline, truncated if buffer is too small. Note that we reduce
         * the available buffer size by 1 so that we can safely add the
         * newline below.
         */
        len = MIN(len - 1, (size_t)(eol - rlcur + 1));
        strlcpy(buf, rlcur, len);
        /* make sure we have a newline and NUL */
        buf[len - 1] = '\n';
        buf[len] = '\0';

        /* skip over newline in readline buffer */
        if (*eol) {
                eol++;
        }
        /* prepare for next invocation: point to next line or free readline
         * buffer if we've reached EOL
         */
        if (*eol) {
                rlcur = eol;
        } else {
                free(rlbuf);
                rlbuf = rlcur = NULL;
        }

        return len;
}


void
hist_term(void)
{
}


S_FUNC void
my_stifle_history (void)
{
        /* only save last number of entries */
        stifle_history(HISTORY_LEN);

        if (calc_history)
                write_history(calc_history);
}


int
hist_init(char *UNUSED(filename))
{
        /* used when parsing conditionals in ~/.inputrc */
        rl_readline_name = "calc";

        /* initialize interactive variables */
        using_history();

        /* name of history file */
        if (calc_history == NULL) {
                calc_history = tilde_expand("~/.calc_history");
        }

        /* read previous history */
        read_history(calc_history);

        atexit(my_stifle_history);

        return HIST_SUCCESS;
}

void
hist_saveline(char *line, int len)
{
        STATIC char *prev = NULL;

        if (len <= 1)
                return;

        /* ignore if identical with previous line */
        if (prev != NULL && strcmp(prev, line) == 0)
                return;

        free(prev);

        /* fail silently */
        prev = strdup(line);

        line[len - 1] = '\0';
        add_history(line);
        line[len - 1] = '\n';
}


#endif /* USE_READLINE */


#if defined(HIST_TEST)

/*
 * Main routine to test history.
 */
int
main(int argc, char **argv)
{
        char    *filename;
        int     len;
        char    buf[BUFSIZ+1];

        filename = NULL;
        if (argc > 1)
                filename = argv[1];

        switch (hist_init(filename)) {
        case HIST_SUCCESS:
                break;
        case HIST_NOFILE:
                fprintf(stderr, "Binding file was not found\n");
                break;
        case HIST_NOTTY:
                fprintf(stderr, "Cannot set terminal parameters\n");
                break;
        case HIST_INITED:
                fprintf(stderr, "Hist is already inited\n");
                break;
        default:
                fprintf(stderr, "Unknown error from hist_init\n");
                break;
        }

        do {
                len = hist_getline("HIST> ", buf, sizeof(buf));
                hist_saveline(buf, len);
        } while (len && (buf[0] != 'q'));

        hist_term();
        exit(0);
}

#endif /* HIST_TEST */
