/* This file was contributed by Suzanne Skinner and is copyrighted
   under the GNU General Public License. (C) 2002 Suzanne Skinner.

   The code contained in this file 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.

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

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

#include "dynstr.h"
#include "getline.h"

#include "defines.h"   /* for NAP_MIN(), NAP_MAX() */

#include "nap.h"
#include "winio.h"
#include "dlul_screen.h"
#include "colors.h"
#include "status_line.h"

/*** Private Function Prototypes ***/
static void sl_scroll_left(void);
static void sl_scroll_right(void);
static void sl_scroll_to(int);

static dynstr   sl_text          = NULL;
static dynstr   sl_input_text    = NULL;
static int      sl_scroll_pos    = 0;
static bool     sl_input_mode_on = FALSE;
static int      sl_input_cursor  = 0;

static const char *sl_input_mode_prefix = "/search ";

/* Set the text of the status line to "str". Note: input-mode keeps a separate
 * buffer which will not be effected by sl_set or sl_sprintf. This function
 * has the side effect of setting sl_scroll_pos to 0, unless str is identical
 * to the original status-line text or input-mode is on.
 */
void sl_set(const char *str)
{
    if (!sl_input_mode_on && strcmp(ds_get(&sl_text), str) != 0)
        sl_scroll_pos = 0;
    ds_set(&sl_text, str);
    sl_draw();
}

/* sprintf text into the status line (there is no length limit). Note:
 * input-mode keeps a separate buffer which will not be effected by sl_set or
 * sl_sprintf. This function has the side effect of setting sl_scroll_pos to 0
 * (unless input-mode is on).
 */
void sl_sprintf(const char *format, ...)
{
    va_list args;

    va_start(args, format);
    ds_vsprintf(&sl_text, format, args);
    va_end(args);
    if (!sl_input_mode_on) {
        sl_scroll_pos = 0;
        sl_draw();
    }
}

/* Draws the status line to the bottom line of the current window (determined
 * by checking the "screen" global variable). This function is called by any
 * sl_ function that changes the contents (or scroll-state) of the status line,
 * and may also be called externally as needed (e.g. after a screen switch).
 *
 * The status line will be displayed in white-on-red, unless input-mode is
 * active, in which case it is displayed in inverse video.
 *
 * Note: sl_draw will never call wrefresh, nor will any sl_ function.
 * Screen refreshing is left to the higher level.
 */
void sl_draw(void)
{
    WINDOW     *win;
    int         attrib;
    const char *text;

    switch (screen) {
    case RESULT_SCREEN: win = swin; break;
    case DLUL_SCREEN:   win = dlul_win; break;
    default:            return;
    }
    if (sl_input_mode_on) {
        attrib = A_REVERSE;
        text = ds_get(&sl_input_text);
    }
    else {
        attrib = COLOR_PAIR(CPWR);
        text = ds_get(&sl_text);
    }
    wattron(win, attrib);
    wmove(win, LINES-1, 0);
    whline(win, ' ', COLS);
    waddnstr(win, text + sl_scroll_pos,
             NAP_MIN(COLS, strlen(text + sl_scroll_pos)));
    if (sl_input_mode_on)
        wmove(win, LINES-1, sl_input_cursor - sl_scroll_pos);
    wattroff(win, attrib);
}

/* Call this function any time a keystroke is received on a screen that uses
 * status_line.c. It will return TRUE if the keystroke applied to the
 * status line (left/right scrolling, input mode, etc.), in which case no
 * further handling should be done. If it returns FALSE, the keystroke was not
 * of interest and should be handled by the caller instead.
 *
 * If input-mode is active and the user types ENTER after entering a search
 * command, the command will be executed via cmds.c:parseout(). Input mode is
 * then exited and the scroll position is set back to 0.
 *
 * Note: This function will grab *all* keystrokes if input-mode is on.
 */
bool sl_handle_keystroke(chtype ch)
{
    sock_t  *sock_data;
    int      sock;

    if (sl_input_mode_on) {
        sl_input_cursor =
            gl_handle_keystroke(&sl_input_text, strlen(sl_input_mode_prefix),
                                sl_input_cursor, ch);
        if (sl_input_cursor == GL_DONE) {
            sl_input_mode_on = FALSE;
            sl_scroll_pos = sl_input_cursor = 0;
	    sl_set("");
            if (ds_len(&sl_input_text) > strlen(sl_input_mode_prefix)) {
                sock_data = findsock("server");
                sock = (sock_data ? sock_data->fd : -1);
                parseout(sock, ds_get(&sl_input_text), wchan);
            }
        } else if (sl_input_cursor == GL_ABORT) {
  	    sl_input_mode_on = FALSE;
            sl_scroll_pos = sl_input_cursor = 0;
	    sl_set("Input aborted.");
	} else {
  	    sl_scroll_to(sl_input_cursor);
	}
        sl_draw();
        return TRUE;
    }

    switch (ch) {
    case KEY_LEFT:
        sl_scroll_left();
        sl_draw();
        return TRUE;

    case KEY_RIGHT:
        sl_scroll_right();
        sl_draw();
        return TRUE;

    case ' ':
        sl_input_mode_on = TRUE;
        ds_set(&sl_input_text, sl_input_mode_prefix);
        sl_scroll_pos = 0;
        sl_input_cursor = ds_len(&sl_input_text);
        sl_draw();
        return TRUE;
    }

    return FALSE;
}

/*** Private ***/

static void sl_scroll_left(void)
{
    sl_scroll_pos -= NAP_MAX(COLS/2, 1);
    if (sl_scroll_pos < 0)
        sl_scroll_pos = 0;
}

static void sl_scroll_right(void)
{
    int len;

    len = ds_len(sl_input_mode_on ? &sl_input_text : &sl_text);
    if (sl_scroll_pos + COLS > len)
        return;
    sl_scroll_pos += NAP_MAX(COLS/2, 1);
    if (sl_scroll_pos > len)
        sl_scroll_pos = len;
}

/* scoll so that cursor is visible. For a very long line, this "while"
   implementation is slower than an equivalent "modulo" calculation,
   but conceptually simpler. */
static void sl_scroll_to(int cursor) 
{
    while (cursor < sl_scroll_pos) {
        sl_scroll_left();
    }
    while (cursor >= sl_scroll_pos + COLS) {
        sl_scroll_right();    
    }
}
