/*
    Edit line management file
    Copyright (c) 1993, 1994 Tudor Hulubei & Andrei Pitis

This file is part of UIT (UNIX Interactive Tools)

UIT 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, or (at your option) any later version.

UIT 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 General Public License for more
details.

You should have received a copy of the GNU General Public License along with
UIT; see the file COPYING.  If not, write to the Free Software Foundation,
675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <string.h>

#include "xmalloc.h"
#include "config.h"
#include "history.h"
#include "tty.h"
#include "window.h"
#include "edit.h"
#include "tilde.h"
#include "misc.h"


extern int  AnsiColorSequences;


#define MAX_INPUT_HISTORY	512	/* big enough I suppose ... */


#define EDIT_FIELDS	3

static char *EditFields[EDIT_FIELDS] =
{
    "CommandLineForeground",
    "CommandLineBackground",
    "CommandLineBrightness"
};

#ifdef HAVE_LINUX
static int EditColors[EDIT_FIELDS] =
{
    WHITE, BLACK, ON
};
#else	/* not HAVE_LINUX */
static int EditColors[EDIT_FIELDS] =
{
    WHITE, BLACK, ON
};
#endif	/* not HAVE_LINUX */

#define CommandLineForeground	EditColors[0]
#define CommandLineBackground	EditColors[1]
#define CommandLineBrightness	EditColors[2]


edit *edit_init(int _columns, int _begin_y)
{
    char *data;
    int index, i;

    edit *this        = (edit *)xmalloc(sizeof(edit));
    this->columns     = _columns;
    this->buf         = xmalloc(EDIT_BUFLEN + 1);

    edit_reset(this);
    this->win = window_init(0, _begin_y, 1, _columns);

    use_section("[UIT-Setup]");

    configuration_getvarinfo("HistoryFile", &data, 1, DO_SEEK);
    this->history_file = tilde_expand(data ? data : "~/.uithistory");

    use_section(AnsiColorSequences ? cSection : bwSection);

    get_colorset_var(EditColors, EditFields, EDIT_FIELDS);

    using_history();
    read_history(this->history_file);
    while (next_history());
    stifle_history(MAX_INPUT_HISTORY);
    return this;
} 


void edit_end(edit *this)
{
    write_history(this->history_file);
    free(this->buf);
    free(this->win);
}


/*
 * dest is a pointer to a NULL pointer or a pointer to a pointer allocated
 * with xmalloc(). See also the edit_gets() comment. 
 */

char *edit_gettext(edit *this, char **dest)
{
    *dest = xrealloc(*dest, strlen(this->buf + this->static_size) + 1);
    return strcpy(*dest, this->buf + this->static_size);
}


void edit_reset(edit *this)
{
    this->buf[this->index = this->static_size = 0] = 0;
}


int edit_putch(edit *this, char key)
{
    tty_status status;

    if (key == key_BACKSPACE && this->index == this->static_size) return;

    if (key != key_BACKSPACE && this->index == EDIT_BUFLEN)
    {
	tty_beep();
	return 0;
    }

    if (key == key_BACKSPACE)
        this->index--;
    else
    {
        if (!is_print(key)) return 0;
        this->buf[this->index++] = key;
    }

    this->buf[this->index] = 0;

    if (this->index < this->columns - 1)
    {
	tty_save(&status);

	tty_bright(CommandLineBrightness);
	tty_foreground(CommandLineForeground);
	tty_background(CommandLineBackground);

	if (key == key_BACKSPACE)
	{
	    window_cursormove_notify(this->win, 0, this->index);
	    window_putch(' ');
	    window_cursormove(this->win, 0, this->index);
	}
	else
	{
	    window_write(&key, 1);
	    if (key == ' ')
		window_cursormove(this->win, 0, this->index);
	}

	tty_restore(&status);
    }
    else
        edit_update(this);

    return 1;
}


static int separator(char c)
{
    if ((c >= 'a' && c <= 'z') ||
    	(c >= 'A' && c <= 'Z') ||
    	(c >= '0' && c <= '9') ||
    	(c == '.')	       ||
    	(c == '_')	       ||
    	(c == '-')	       ||
    	(c == '#')	       ||
    	(c == '~'))
	return 0;
    else
	return 1;
}


void edit_delete_last_word(edit *this)
{
    if (this->index > this->static_size)
	this->index--;
    else
	return;

    while (this->index > this->static_size && this->buf[this->index] == ' ')
	this->index--;

    while (this->index > this->static_size &&
    	   !separator(this->buf[this->index]))
	this->index--;

    if (this->index > this->static_size)
	this->buf[this->index++] = ' ';
    this->buf[this->index] = 0;

    edit_update(this);
}


void edit_update(edit *this)
{
    char *temp;
    unsigned len;
    tty_status status;

    tty_save(&status);

    tty_bright(CommandLineBrightness);
    tty_foreground(CommandLineForeground);
    tty_background(CommandLineBackground);

    temp = xmalloc(this->columns);

    memset(temp, ' ', this->columns);
    len = (this->index >= this->columns) ? this->index - this->columns + 1 : 0;
    memcpy(temp, this->buf + this->static_size + len,
	   this->index - this->static_size - len);
    window_cursormove_notify(this->win, 0, 0);
    window_write(this->buf, this->static_size);
    window_write(temp, this->columns - this->static_size);

    free(temp);

    tty_restore(&status);
}


void edit_eos(edit *this)
{
    this->static_size = this->index;
}


void edit_setcursor(edit *this)
{
    tty_bright(CommandLineBrightness);
    tty_foreground(CommandLineForeground);
    tty_background(CommandLineBackground);
    window_cursormove(this->win, 0, strlen(this->buf));
}


void edit_del(edit *this)
{
    this->buf[this->index = this->static_size] = 0;
    edit_update(this);
}


int edit_puts(edit *this, char *str)
{
    if (this->index + strlen(str) <= EDIT_BUFLEN)
    {
	strcpy(this->buf + this->index, str);
	this->index = strlen(this->buf);
	return 1;
    }
    else
    {
	tty_beep();
	return 0;
    }
}


/*
 * WARNING: dest *must* be a pointer to a NULL pointer or a pointer to a
 * pointer allocated with xmalloc. In the first case, edit_gets() will
 * return a string allocated with xmalloc(). In the second case, edit_gets()
 * will reallocate the pointer as needed using xrealloc. You should free
 * this pointer yourself.
 */

char *edit_gets(edit *this, char *static_text, char **dest, char *default_str)
{
    char key;
    struct key_struct *ks;
    int oldindex = this->index, oldstatic_size = this->static_size;
    char *oldbuf;

    oldbuf = xmalloc(strlen(this->buf) + 1);
    strcpy(oldbuf, this->buf);
    edit_reset(this);
    if (static_text) edit_puts(this, static_text);
    edit_eos(this);
    if (default_str) edit_puts(this, default_str);
    edit_update(this);
    edit_setcursor(this);

    for (;;)
    {
	ks  = tty_getkey(NULL);
	key = ks->key_seq[0];
	if (ks->aux_data == NULL && (is_print(key) || key == key_BACKSPACE))
	    edit_putch(this, key);
	else
	    break;
    }

    if (key == key_ENTER) edit_gettext(this, dest);

    strcpy(this->buf, oldbuf);
    free(oldbuf);
    this->index = oldindex;
    this->static_size = oldstatic_size;
    edit_update(this);
    edit_setcursor(this);
    return (key == key_ENTER) ? *dest : NULL;
}


void edit_history(edit *this, int dir)
{
    static int browsing = 0;
    static int last_history_pos;
    HIST_ENTRY *hist;

    switch (dir)
    {
        case EDIT_PREVIOUS:

	    if (!browsing)
	    {
		browsing = 1;
		last_history_pos = where_history();
	    }

	    if ((hist = previous_history()))
	    {
		if (this->static_size + strlen(hist->line) <= EDIT_BUFLEN)
		    strcpy(this->buf + this->static_size, hist->line);
		else
		{
		    this->buf[this->static_size] = 0;
		    tty_beep();
		}

		this->index = strlen(this->buf);
		edit_update(this);
	    }

	    break;

        case EDIT_NEXT:

	    if (!browsing)
	    {
		browsing = 1;
		last_history_pos = where_history();
	    }

	    if ((hist = next_history()))
	    {
		if (this->static_size + strlen(hist->line) <= EDIT_BUFLEN)
		    strcpy(this->buf + this->static_size, hist->line);
		else
		{
		    this->buf[this->static_size] = 0;
		    tty_beep();
		}

		this->index = strlen(this->buf);
		edit_update(this);
	    }
	    else
		edit_del(this);

	    break;

        case EDIT_RECORD:

	    if (browsing)
	    {
		history_set_pos(last_history_pos);
		browsing = 0;
	    }

	    if ((hist = previous_history()))
	    {
		if (strcmp(this->buf + this->static_size, hist->line) != 0)
		{
		    add_history(this->buf + this->static_size);
		    next_history();
		}
	    }
	    else
		add_history(this->buf + this->static_size);

	    next_history();

	    break;

        default:

            break;
    }
}
