/* 
   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.  

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

   selection window
   
*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

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

#include "ecurses.h"
#include "select.h"
#include "xmalloc.h"
#include "error.h"
#include "line.h"
#include "color.h"
#include "ask.h"
#include "status.h"
#include "read.h"
#include "cmd.h"
#include "gettext.h"

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

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))


#define PREAMBLE do {if (select == NULL) return; } while (0)
#define NONEMPTY do {if (select->items->count == 0) return; } while (0)

/****************************************************************************
 *    IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

/* Color used in bar windows. */
static chtype text_color;
static chtype hilight_color;

static search_t *search = NULL;
static select_t *search_select = NULL;

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


static void
show_percentage (select_t *select, int count, int height)
{
        int p;

        if (count <= height)
                p = -1;
        else if (select->top_pos == 0)
                p = 0;
        else if (count - select->top_pos <= height)
                p = 100;
        else
                p = 100 * select->top_pos / count;
        
        if (p == 0 && select->top_pos != 0)
                p = 1;
        
        status_show_percentage (p);
}


static void
validate (select_t *select)
{
        int count = select->count (select);
        int nothing;
        int height;
        int position;
        int y, x;

        getmaxyx (select->win, height, nothing);
        
        if (select->bar == NULL){
                show_percentage (select, count, height);
                return;
        }

        if (select->top_pos >= count)
                select->top_pos = count - 1;
        if (select->top_pos < 0)
                select->top_pos = 0;
        if (select->bar_pos >= count)
                select->bar_pos = count - 1;
        if (select->bar_pos < 0)
                select->bar_pos = 0;

        if (select->top_pos > select->bar_pos)
                select->top_pos = select->bar_pos;
        if (select->top_pos + height < select->bar_pos + 1)
                select->top_pos = select->bar_pos - height + 1;
  
        position = select->bar_pos - select->top_pos;

        getbegyx (select->win, y, x);
        mvwin    (select->bar, y + position, x);

        show_percentage (select, count, height);
}


static void
draw_content (select_t *select)
{
        int width;
        int height;
        int offset;
        int i;

        validate (select);
        getmaxyx (select->win, height, width);

        offset = select->top_pos;
        
        for (i = 0; i < height; i++){
                wmove (select->win, i, 0);
                select->draw_line (select->win, width, i + offset, search);
        }
}



static void
draw_bar (select_t *select)
{
        int maxlen;
        int nothing;

        if (select->bar == NULL)
                return;

        getmaxyx (select->bar, nothing, maxlen);
  
        wmove (select->bar, 0, 0);
        select->draw_line (select->bar, maxlen, select->bar_pos, search);
}



static WINDOW *
get_bar (WINDOW *win)
{
        int     top, left;
        int     y, x;
        WINDOW *result;

        getmaxyx (win, y, x);
        getbegyx (win, top, left);
        
        result = newwin (1, x, top, left);
        wattrset (result, text_color);
        return result;
}



static void
refresh_me (select_t *select)
{
        touchwin (select->win);
        wnoutrefresh (select->win);

        if (select->bar){
                touchwin (select->bar);
                wnoutrefresh (select->bar);
        }
}



static int
is_on_last (select_t *select)
{
        int count = select->count (select);
        int height, nothing;

        getmaxyx (select->win, height, nothing);
  
        if (select->bar == NULL){
                count -= height - 1;
        }
        
        return select->bar_pos >= count - 1;
}



static int
is_on_first (select_t *select)
{
        return select->bar_pos == 0;
}



static int
bar_on_bottom (select_t *select)
{
        int win_top, bar_top, win_height, nothing;
        
        if (select->bar == NULL)
                return 1;

        getbegyx (select->win, win_top, nothing);
        getbegyx (select->bar, bar_top, nothing);
        getmaxyx (select->win, win_height, nothing);
        
        return bar_top >= win_top + win_height - 1;
}


static int
bar_on_top (select_t *select)
{
        int win_top, bar_top, nothing;
        
        if (select->bar == NULL)
                return 1;

        getbegyx (select->win, win_top, nothing);
        getbegyx (select->bar, bar_top, nothing);
        
        return bar_top <= win_top;
}



static int
bar_on_first_page (select_t *select)
{
        int height, nothing;
        
        if (select->bar == NULL)
                select->bar_pos = select->top_pos;

        getmaxyx (select->win, height, nothing);

        return select->bar_pos <= height;
}


static int
bar_on_last_page (select_t *select)
{
        int count = select->count (select);
        int height, nothing;
  
        if (select->bar == NULL)
                select->bar_pos = select->top_pos;

        getmaxyx (select->win, height, nothing);
        
        return select->bar_pos + height >= count;
}



static void
scroll_down (select_t *select)
{
        select->top_pos++;
        draw_content (select);
}



static void
scroll_up (select_t *select)
{
        select->top_pos--;
        draw_content (select);
}



static chtype
get_color (ask_t *ask, const char *fg_field, const char *bg_field,
           const char *fg_def, const char *bg_def)
{
        char *fg = NULL;
        char *bg = NULL;

        if (ask){
                fg = ask_get_field (ask, fg_field);
                bg = ask_get_field (ask, bg_field);
        }

        if (fg && bg){
                return color_from_str (fg, bg);
        }
        else if (fg){
                return color_from_str (fg, bg_def);
        }
        else if (bg){
                return color_from_str (fg_def, bg);
        }
        return color_from_str (fg_def, bg_def);
}


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

void
select_init (void)
{
        ask_t *ask = ask_select_default ("win_bar");

        text_color = get_color (ask, "text_fg", "text_bg",
                                "black", "cyan");
        hilight_color = get_color (ask, "hilight_fg", "hilight_bg",
                                   "cyan", "blue");
        if (ask)
                ask_destroy (ask);
}



chtype
select_bar_color (void)
{
        return text_color;
}



chtype
select_hilight_color (void)
{
        return hilight_color;
}



select_t *
select_open (WINDOW *win, int no_bar,
             void (*draw_line) (WINDOW *, int, int, search_t *),
             int (*count) (select_t *))
{
        select_t *select   = xmalloc (sizeof (select_t));

        select->win        = win;
        select->top_pos    = 0;
        select->bar_pos    = 0;
        select->draw_line  = draw_line;
        select->count      = count;

        if (! no_bar){
                select->bar = get_bar (win);
        }
        else {
                select->bar = NULL;
        }
  
        return select;
}



void
select_close (select_t *select)
{
        if (select->win)
                delwin (select->win);

        if (select->bar)
                delwin (select->bar);

        xfree (select);
}



void
select_show (select_t *select)
{
        PREAMBLE;

        refresh_me (select);
}



void
select_next (select_t *select)
{
        int top, left;
        
        PREAMBLE;

        if (is_on_last (select))
                return;

        if (bar_on_bottom (select)){
                select->bar_pos++;
                scroll_down (select);
        }
        else {
                getbegyx (select->bar, top, left);
                select->bar_pos++;
                mvwin (select->bar, top + 1, left);
        }
        draw_bar (select);
        refresh_me (select);
}



void
select_prev (select_t *select)
{
        int top, left;
        
        PREAMBLE;

        if (is_on_first (select))
                return;

        if (bar_on_top (select)){
                select->bar_pos--;
                scroll_up (select);
        }
        else {
                getbegyx (select->bar, top, left);
                select->bar_pos--;
                mvwin (select->bar, top - 1, left);
        }
        draw_bar (select);
        refresh_me (select);
}



void
select_scroll_up (select_t *select)
{
        PREAMBLE;

        if (is_on_first (select))
                return;

        if (bar_on_bottom (select)){
                select->bar_pos--;
        }
        scroll_up (select);
        draw_bar (select);
        refresh_me (select);
}



void
select_scroll_down (select_t *select)
{
        PREAMBLE;

        if (is_on_last (select))
                return;

        if (bar_on_top (select)){
                select->bar_pos++;
        }
        scroll_down (select);
        draw_bar (select);
        refresh_me (select);
}


void
select_next_page (select_t *select)
{
        int height, nothing;
        
        PREAMBLE;

        if (bar_on_last_page (select)){
                select_last (select);
                return;
        }

        getmaxyx (select->win, height, nothing);

        select->bar_pos += height;
        select->top_pos += height;
        select_redraw (select);
}



void
select_prev_page (select_t *select)
{
        int height, nothing;
        
        PREAMBLE;

        if (bar_on_first_page (select)){
                select_first (select);
                return;
        }

        getmaxyx (select->win, height, nothing);
        
        select->bar_pos -= height;
        select->top_pos -= height;
        select_redraw (select);
}



void
select_first (select_t *select)
{
        PREAMBLE;

        select->bar_pos = 0;
        select->top_pos = 0;
        select_redraw (select);
}



void
select_last (select_t *select)
{
        int count;
        int pos;
        int height, nothing;
  
        PREAMBLE;

        getmaxyx (select->win, height, nothing);
        count = select->count (select);
        pos   = count - height;

        if (select->top_pos < pos)
                select->top_pos = pos;
        if (select->bar == NULL)
                select->bar_pos = select->top_pos;
        else
                select->bar_pos = count - 1;
        select_redraw (select);
}



void
select_recenter (select_t *select)
{
        int height, nothing;
        int top;
        
        PREAMBLE;

        if (select->bar == NULL)
                return;
        
        getmaxyx (select->win, height, nothing);

        top = select->bar_pos - height / 2;
        if (top < 0)
                top = 0;
        select->top_pos = top;
        select_redraw (select);
}



void
select_goto (select_t *select, int index)
{
        int count;

        PREAMBLE;

        count = select->count (select);
  
        if (index >= count)
                return;

        if (select->bar)
                select->bar_pos = index;
        else {
                select->bar_pos = index;
                select->top_pos = index;
        }
	validate (select);
}


void
select_redraw (select_t *select)
{
        PREAMBLE;

        draw_content (select);
        draw_bar (select);

        wnoutrefresh (select->win);
        if (select->bar)
                wnoutrefresh (select->bar);
}



/****************************************************************************
 *    SEARCH PRIVATE FUNCTIONS
 ****************************************************************************/

static int
first_true (select_t *select, int start)
{
        int i;
        int count = select->count (select);
        
        for (i = start; i < count; i++){
                if (select->match (search, i))
                        return i;
        }
        return -1;
}


static int
first_true_back (select_t *select, int start)
{
        int i;

        for (i = start; i >= 0; i--){
                if (select->match (search, i))
                        return i;
        }
        return -1;
}



static int
forward_search (int off)
{
        int index;
        int count = search_select->count (search_select);
        
        if (search_select->bar_pos >= count - 1)
                return 0;
        
        
        index = first_true (search_select, search_select->bar_pos + off);
        if (index != -1)
                select_goto (search_select, index);
        else 
                fprintf (stderr, "%c", '\a');
        select_redraw (search_select);
        return index != -1;
}


static int
backward_search (int off)
{
        int index;
        
        if (search_select->bar_pos == 0)
                return 0;

        index = first_true_back (search_select, search_select->bar_pos - off);
        if (index != -1)
                select_goto (search_select, index);
        else
                fprintf (stderr, "%c", '\a');
        select_redraw (search_select);
        return index != -1;
}


static void
loop_search (int forward, int mistakes)
{
        int    ret;
        int    meta;
        int    c;
        str_t *str = str_create ();;
        
        if (mistakes)
                str_sprintf (str, _("[%d] search: "), mistakes);
        else
                str_put_string (str, _("search: "));
        
        read_prepare (str->str, NULL, COMPLETE_NONE, HIDE_NO);
        str_destroy (str);

        while (1){
                meta = 0;
                c    = wgetch (cmd_win);

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

                if (keymap_action (keymaps + CMD_SEARCH, c, meta)){
                        if (c > 256 || ! isprint (c)){
                                fprintf (stderr, "%c", '\a');
                                break;
                        }
                        if (search_add_char (search, c) == 0)
                                read_put_char (c);
                        else
                                fprintf (stderr, "%c", '\a');
                }

                if (forward)
                        ret = forward_search (0);
                else
                        ret = backward_search (0);

                if (! ret)
                        select_search_backspace ();

                read_echo_input ();
        }
        
        search_destroy (search);
        read_restore ();
        search               = NULL;
        search_select->match = NULL;
        select_redraw (search_select);
        search_select        = NULL;
}

/****************************************************************************
 *    SEARCH FUNCTIONS
 ****************************************************************************/


void
select_search_setup_forward (select_t *select, int (*match)(search_t *, int))
{
        int mistakes;
        
        mistakes      = cmd_prefix_arg ();
        search        = search_create (SEARCH_INSENSITIVE, mistakes);
        search_select = select;
        search_select->match = match;
        loop_search (1, mistakes);
}



void
select_search_setup_backward (select_t *select, int (*match)(search_t *, int))
{
        int mistakes;
        
        mistakes      = cmd_prefix_arg ();
        search        = search_create (SEARCH_INSENSITIVE, mistakes);
        search_select = select;
        search_select->match = match;
        loop_search (0, mistakes);
}


void
select_search_forward (void)
{
        if (search)
                forward_search (1);
}


void
select_search_backward (void)
{
        if (search)
                backward_search (1);
}


void
select_search_backspace (void)
{
        if (search){
                search_chop (search);
                read_del_char_back ();
                select_redraw (search_select);
        }
}


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


syntax highlighted by Code2HTML, v. 0.9.1