/* 
   elmo - ELectronic Mail Operator

   Copyright (C) 2003, 2004 rzyjontko

   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; version 2.

   This program 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 this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  

   ----------------------------------------------------------------------

   
*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <string.h>
#include <dirent.h>
#include <ctype.h>

#include "ecurses.h"
#include "clock.h"
#include "xmalloc.h"
#include "cmd.h"
#include "read.h"
#include "choose.h"
#include "file.h"
#include "abook.h"
#include "interface.h"

/****************************************************************************
 *    IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
 ****************************************************************************/

#define CURSOR_AT_END() (buf.pos == buf.len)
#define CURSOR_AT_BEG() (buf.pos == 0)

#define BEEP() do { fprintf (stderr, "%c", '\a'); } while (0)

#define INITIAL_SIZE COLS

/**
 * This is a 2-bit value.  The higher bit is used as a boolean value
 * that says if we need right dots, and the lower one says if we need
 * left dots.
 */
enum dots {DOTS_NONE  = 0,
           DOTS_LEFT  = 1,
           DOTS_RIGHT = 2,
           DOTS_BOTH  = 3};

/****************************************************************************
 *    IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
 ****************************************************************************/

struct buf {
        char *buf;
        int   size;
        int   len;
        int   pos;
};

/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

/* The completeion mode is used when obtaining possible completions. */
static complete_mode_t mode = COMPLETE_NONE;

/* This controls wheter we should hide the string, and how. */
static hide_mode_t hide = HIDE_NO;

/* The length of the prompt string. */
static int prompt_len = 0;

/* Indicates if the user has cancelled or accepted the input. */
static int cancel = 0;
static int accept = 0;

/* This flag is used to check the meta-flag of the key.  Meta-keys send
   two characters.  First one is 27 (ESC), the second is the real key.
   This is why you have to press ESC twice. */
static int escape = 0;

/* Indicates if user has already pressed tab, because the completions
   are shown after second tab-press. */
static int tab = 0;

/* The buffer stores the input string whith all neccessary information:
   position, length and size. */
static struct buf buf;

/* These are used in completion. */
static char      *compl_string = NULL;
static rstring_t *compl_items  = NULL;

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/
/****************************************************************************
 *    BUFFER MANIPULATION FUNCTIONS
 ****************************************************************************/

static void
buf_init (void)
{
        if (buf.size == 0){
                buf.size = INITIAL_SIZE;
                buf.buf  = xmalloc (buf.size);
        }

        buf.len    = 0;
        buf.pos    = 0;
        buf.buf[0] = '\0';
}


static void
buf_destroy (void)
{
        if (buf.size)
                xfree (buf.buf);

        buf.buf  = NULL;
        buf.size = 0;
}


/**
 * makes buffer big enough to hold LEN more bytes
 */
static void
buf_reallocate (int len)
{
        while (buf.len + len + 1 > buf.size){
                buf.size *= 2;
        }

        buf.buf = xrealloc (buf.buf, buf.size);
}



/**
 * moves all after position LEN bytes forward
 */
static void
buf_move (int len)
{
        if (buf.len == buf.pos)
                return;

        memmove (buf.buf + buf.pos + len, buf.buf + buf.pos,
                 buf.len - buf.pos + 1);
}



static void
buf_put_str_len (const char *str, int len)
{
        if (str == NULL || len <= 0)
                return;

        buf_reallocate (len);
        buf_move (len);

        memcpy (buf.buf + buf.pos, str, len);

        buf.len += len;
        buf.pos += len;

        buf.buf[buf.len] = '\0';
}



static void
buf_put_str (const char *str)
{
        int len;

        if (str == NULL)
                return;
  
        len = strlen (str);

        buf_put_str_len (str, len);
}



static void
buf_put_char (int c)
{
        if (c == '\0')
                return;
  
        buf_reallocate (1);
        buf_move (1);

        buf.buf[buf.pos] = c;

        buf.len++;
        buf.pos++;

        buf.buf[buf.len] = '\0';
}


static void
buf_del_chars_backward (int len)
{
        if (len > buf.pos)
                len = buf.pos;

        memmove (buf.buf + buf.pos - len, buf.buf + buf.pos,
                 buf.len - buf.pos);

        buf.len -= len;
        buf.pos -= len;
        buf.buf[buf.len] = '\0';
}



static void
buf_del_chars_forward (int len)
{
        if (len > buf.len - buf.pos)
                len = buf.len - buf.pos;

        memmove (buf.buf + buf.pos, buf.buf + buf.pos + len,
                 buf.len - buf.pos - len);

        buf.len -= len;
        buf.buf[buf.len] = '\0';
}


/****************************************************************************
 *    SCREEN FUNCTIONS
 ****************************************************************************/


static enum dots
which_dots (int echo_size, int offset)
{
        enum dots result = DOTS_NONE;

        if (buf.pos > echo_size)
                result |= DOTS_LEFT;

        if (buf.len - offset > echo_size)
                result |= DOTS_RIGHT;

        return result;
}


static int
calculate_offset (int echo_size)
{
        int result = 0;

        while (buf.pos - result > echo_size)
                result += echo_size;

        return result;
}


static void
echo_input (void)
{
        int       x, y;
        int       echo_size;
        int       offset;
        int       dots_off = 0;
        enum dots dots;

        getmaxyx (cmd_win, y, x);

        echo_size = x - prompt_len - 2 * 3;
        offset    = calculate_offset (echo_size);
        dots      = which_dots (echo_size, offset);
  
        wmove (cmd_win, 0, prompt_len);
        wclrtoeol (cmd_win);
  
        if (hide == HIDE_EMPTY)
                return;
        
        if (dots & DOTS_LEFT){
                window_addstr (cmd_win, "...");
                dots_off = 3;
        }

        if (hide == HIDE_NO)
                window_addnstr (cmd_win, buf.buf + offset, echo_size);
        else
                window_addnast (cmd_win, buf.buf + offset, echo_size);
  
        if (dots & DOTS_RIGHT){
                window_addstr (cmd_win, "...");
        }

        wmove (cmd_win, 0, prompt_len + buf.pos - offset + dots_off);
}


/****************************************************************************
 *    EDITING FUNCTIONS
 ****************************************************************************/


static void
put_char (int c)
{
        if (! isprint (c)){
                BEEP ();
                return;
        }
  
        buf_put_char (c);
}

/****************************************************************************
 *    COMPLETION FUNCTIONS
 ****************************************************************************/


static void
change_one_item (void)
{
        if (compl_items == NULL)
                return;
  
        if (compl_items->count == 1){
                compl_string = xstrdup (compl_items->array[0]);
                rstring_delete (compl_items);
                compl_items = NULL;
        }
}


static int
is_prefix (const char *a, const char *b)
{
        while (*a){
                if (*a != *b)
                        return 0;
                a++;
                b++;
        }
        return 1;
}


static int
select_fun (const struct dirent *entry)
{
        int   ret;
        char  c = buf.buf[buf.pos];
        char *fname;

        buf.buf[buf.pos] = '\0';
        fname = strrchr (buf.buf, '/');

        if (fname == NULL)
                fname = buf.buf;
        else
                fname++;

        if (*fname != '.' && *entry->d_name == '.'){
                buf.buf[buf.pos] = c;
                return 0;
        }
  
        ret = is_prefix (fname, entry->d_name);
        buf.buf[buf.pos] = c;

        return ret;
}


static void
complete_prepare_funs (void)
{
        compl_items = exec_get_functions (buf.buf);
        change_one_item ();
}



static void
complete_prepare_files (void)
{
        char  c = buf.buf[buf.pos];
        char *dir;
        char *ndir;
        char *seek;

        buf.buf[buf.pos] = '\0';
        seek             = strrchr (buf.buf, '/');
        buf.buf[buf.pos] = c;

        if (seek){
                dir = xmalloc (seek - buf.buf + 2);
                memcpy (dir, buf.buf, seek - buf.buf + 1);
                dir[seek - buf.buf + 1] = '\0';
                if (*dir == '~'){
                        ndir = file_expand_tilde (dir);
                        xfree (dir);
                        dir  = ndir;
                }
        }
        else {
                dir = xstrdup (".");
        }

        compl_items = file_dir_items (dir, select_fun);
        xfree (dir);
        change_one_item ();
}


static void
complete_prepare_addrs (void)
{
        char  c = buf.buf[buf.pos];
        char *seek;

        buf.buf[buf.pos] = '\0';
        seek             = strrchr (buf.buf, ',');
        buf.buf[buf.pos] = c;

        if (seek){
                do
                        seek++;
                while (isspace (*seek));
        }
        else {
                seek = buf.buf;
        }
  
        compl_items = abook_strings (seek);
        change_one_item ();
}


static int
longest_common_prefix (rstring_t *vect)
{
        int i;
        int index = 0;

        if (vect->count == 0)
                return 0;
  
        if (vect->count == 1)
                return strlen (vect->array[0]);

        while (vect->array[0][index]){
                for (i = 1; i < vect->count; i++){
                        if (vect->array[0][index] != vect->array[i][index])
                                break;
                }
                if (i < vect->count)
                        break;
                index++;
        }
        return index;
}



static void
replace_file (const char *str, int len)
{
        char  c = buf.buf[buf.pos];
        char *fname;

        buf.buf[buf.pos] = '\0';
        fname            = strrchr (buf.buf, '/');
        buf.buf[buf.pos] = c;

        if (fname)
                fname++;
        else
                fname = buf.buf;

        buf_del_chars_backward (buf.buf + buf.pos - fname);
        buf_put_str_len (str, len);
}



static void
replace_fun (const char *str, int len)
{
        buf_init ();
        buf_put_str_len (str, len);
}



static void
replace_addr (const char *str, int len)
{
        char  c = buf.buf[buf.pos];
        char *addr;

        buf.buf[buf.pos] = '\0';
        addr             = strrchr (buf.buf, ',');
        buf.buf[buf.pos] = c;

        if (addr)
                addr++;
        else
                addr = buf.buf;

        buf_del_chars_backward (buf.buf + buf.pos - addr);
        if (addr != buf.buf)
                buf_put_char (' ');
        buf_put_str_len (str, len);
}



static void
replace (const char *str, int len)
{
        if (len == 0)
                return;

        switch (mode){

                case COMPLETE_NONE:
                        break;

                case COMPLETE_FILES:
                        replace_file (str, len);
                        break;

                case COMPLETE_FUNS:
                        replace_fun (str, len);
                        break;

                case COMPLETE_ADDRS:
                        replace_addr (str, len);
                        break;
        }
}



static void
complete (void)
{
        int len;

        switch (mode){

                case COMPLETE_NONE:
                        return;

                case COMPLETE_FILES:
                        complete_prepare_files ();
                        break;

                case COMPLETE_FUNS:
                        complete_prepare_funs ();
                        break;

                case COMPLETE_ADDRS:
                        complete_prepare_addrs ();
                        break;
        }

        if (compl_string){
                replace (compl_string, strlen (compl_string));
                xfree (compl_string);
                compl_string = NULL;
        }
        else if (compl_items){
                fprintf (stderr, "%c", '\a');
                len = longest_common_prefix (compl_items);
                replace (compl_items->array[0], len);
                tab = 1;
        }
        else {
                BEEP ();
        }
}


static void
choose_wrapper (void (*fun)(void))
{
        int y, x;

        getyx (cmd_win, y, x);
        fun ();
        wmove (cmd_win, 0, x);
}



static void
leave_choose (void)
{
        if (tab){
                tab = 0;
                choose_wrapper (choose_close);
        }

        if (compl_string)
                xfree (compl_string);

        if (compl_items)
                rstring_delete (compl_items);

        compl_string = NULL;
        compl_items  = NULL;
}


/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTIONS
 ****************************************************************************/

static void
write_prompt (const char *prompt, const char *def)
{
        int y;
  
        window_mvaddstr (cmd_win, 0, 0, prompt);
        getyx (cmd_win, y, prompt_len);
        if (def)
                window_addstr (cmd_win, def);
}



static void
get_string (void)
{
        int meta;
        int c;
  
        while (1){

                meta = 0;
                c    = wgetch (cmd_win);

                if (c == 27){
                        meta = 1;
                        c    = wgetch (cmd_win);
                }

                if (keymap_action (keymaps + CMD_READ, c, meta)){
                        leave_choose ();
                        put_char (c);
                }

                echo_input ();

                if (cancel || accept)
                        break;
        }
}



static void
clear_win (void)
{
        cmd_text_color ();
        wmove (cmd_win, 0, 0);
        wclrtoeol (cmd_win);
        wnoutrefresh (cmd_win);
}



static int
word_backward_len (void)
{
        char *seek = buf.buf + buf.pos;

        while (seek > buf.buf && ! isalnum (*seek))
                seek--;

        while (seek > buf.buf && isalnum (*seek))
                seek--;

        return buf.pos - (seek - buf.buf);
}


static int
word_forward_len (void)
{
        char *seek = buf.buf + buf.pos;

        while (seek < buf.buf + buf.len && ! isalnum (*seek))
                seek++;

        while (seek < buf.buf + buf.len && isalnum (*seek))
                seek++;

        return (seek - buf.buf) - buf.pos;
}


/****************************************************************************
 *    INTERFACE FUNCTIONS
 ****************************************************************************/

void
read_abort (void)
{
        BEEP ();
  
        if (tab){
                leave_choose ();
        }
        else {
                clear_win ();
                cancel = 1;
        }
}



void
read_accept (void)
{
        int index;
  
        if (tab){
                index = choose_index ();
                replace (compl_items->array[index],
                         strlen (compl_items->array[index]));
                leave_choose ();
        }
        else {
                accept = 1;
        }
}



void
read_backward_char (void)
{
        if (tab){
                read_choose_prev ();
        }
        else {
                leave_choose ();
  
                if (CURSOR_AT_BEG ()){
                        BEEP ();
                        return;
                }

                buf.pos--;
        }
}



void
read_forward_char (void)
{
        if (tab){
                read_choose_next ();
        }
        else {
                leave_choose ();
  
                if (CURSOR_AT_END ()){
                        BEEP ();
                        return;
                }

                buf.pos++;
        }
}


void
read_backward_word (void)
{
        leave_choose ();

        if (CURSOR_AT_BEG ()){
                return;
        }

        buf.pos -= word_backward_len ();
}


void
read_forward_word (void)
{
        leave_choose ();

        if (CURSOR_AT_END ()){
                return;
        }

        buf.pos += word_forward_len ();
}


void
read_begin (void)
{
        if (tab){
                read_choose_first ();
        }
        else {
                leave_choose ();
                buf.pos = 0;
        }
}



void
read_end (void)
{
        if (tab){
                read_choose_last ();
        }
        else {
                leave_choose ();
                buf.pos = buf.len;
        }
}



void
read_del_char_fwd (void)
{
        leave_choose ();
  
        if (CURSOR_AT_END ()){
                BEEP ();
                return;
        }
  
        buf_del_chars_forward (1);
}



void
read_del_char_back (void)
{
        leave_choose ();
  
        if (CURSOR_AT_BEG ()){
                BEEP ();
                return;
        }
  
        buf_del_chars_backward (1);
}



void
read_del_word_fwd (void)
{
        int len;
        
        leave_choose ();

        if (CURSOR_AT_END ()){
                return;
        }

        len = word_forward_len ();
        buf_del_chars_forward (len);
}



void
read_del_word_back (void)
{
        int len;
        
        leave_choose ();

        if (CURSOR_AT_BEG ()){
                return;
        }

        len = word_backward_len ();
        buf_del_chars_backward (len);
}



void
read_kill_line (void)
{
        leave_choose ();
  
        buf_del_chars_forward (buf.len - buf.pos);
}



void
read_complete (void)
{
        int y, x;
  
        if (mode == COMPLETE_NONE){
                leave_choose ();
                put_char ('\t');
        }
        else if (tab){
                getyx (cmd_win, y, x);
                choose_open (compl_items->count, compl_items->array);
                wmove (cmd_win, 0, x);
        }
        else {
                complete ();
        }
}



void
read_choose_first (void)
{
        choose_wrapper (choose_first);
}



void
read_choose_last (void)
{
        choose_wrapper (choose_last);
}



void
read_choose_next (void)
{
        choose_wrapper (choose_next);
}



void
read_choose_prev (void)
{
        choose_wrapper (choose_prev);
}



void
read_prepare (const char *prompt, const char *def, complete_mode_t cmode,
              hide_mode_t hmode)
{
        buf_init ();
        buf_put_str (def);
        clear_win ();
        curs_set (1);
        write_prompt (prompt, def);

        keymap_disable_default ();
        mode = cmode;
        hide = hmode;

        tab    = 0;
        cancel = 0;
        accept = 0;
        escape = 0;
}


void
read_restore (void)
{
        mode = COMPLETE_NONE;
        hide = HIDE_NO;
        keymap_enable_default ();
  
        clear_win ();
        curs_set (0);
}



void
read_put_char (int c)
{
        put_char (c);
}



void
read_echo_input (void)
{
        echo_input ();
}



char *
read_argument (const char *prompt, const char *def, complete_mode_t cmode,
               hide_mode_t hmode)
{
        read_prepare (prompt, def, cmode, hmode);
        get_string ();
        read_restore ();

        if (cancel)
                return NULL;

        return buf.buf;
}



void
read_free_resources (void)
{
        buf_destroy ();
}

/****************************************************************************
 *    INTERFACE CLASS BODIES
 ****************************************************************************/
/****************************************************************************
 *
 *    END MODULE read.c
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1