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

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

   Discussion:

   There are 2 kinds of windows:

     1. Windows with non-configurable size and position.

        These windows must be refreshed by the interface module on demand.
        But you cannot switch to such a window, it cannot take focus.


     2. Windows with configurable size.

        These are all the windows, that the user would generally consider
        as a window.  They can be focused, consist of multiple WINDOW
        objects and so on.

   This module must keep the screen clean, and up to date.  It must decide
   which windows are visible, and draw them in the correct order.
*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

#include <sys/types.h>
#include <signal.h>

#include "ecurses.h"
#include "interface.h"
#include "xmalloc.h"
#include "error.h"
#include "gettext.h"
#include "debug.h"
#include "status.h"
#include "topbar.h"
#include "cmd.h"

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

#define HEAP_SIZE 8

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

struct frame {
        struct frame *next;
        
        WINDOW *win;            /* window or NULL */
        char   *name;           /* name displayed in statusbar */
        int     num;            /* window id or -1 if it cannot get focus */
        int     hidden;         /* if it is hidden */

        void  (*show) (void);
        void  (*draw) (void);
        void  (*set_focus) (void);
        void  (*unset_focus) (void);
};

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

/* This is used in signal handler to indicate that this error is being
   handled (see info libc) */
static volatile sig_atomic_t error_indicator = 0;


/* I use heap to store numbers, that are assigned to windows, when they
   are displayed.  When window is being hidden, its number is inserted
   in the heap.  When window is being opened module assings the minimal
   value from the heap. */
static int num_heap[HEAP_SIZE];
static int heap_count = 0;


static struct frame *visible_windows = NULL;
static struct frame *hidden_windows  = NULL;
static struct frame *other_windows   = NULL;
static struct frame *focused_window  = NULL;

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

static void
heap_exchange (int i, int j)
{
        int tmp     = num_heap[i];
        num_heap[i] = num_heap[j];
        num_heap[j] = tmp;
}



static void
heapify_up (void)
{
        int i = heap_count;
        int p;

        while (1){
                if (i == 1)
                        break;

                p = i >> 1;
                if (num_heap[i] >= num_heap[p])
                        break;

                heap_exchange (i, p);
                i = p;
        }
}



static void
heapify_down (void)
{
        int i = 1;
        int l, r;

        while (1){
                if ((i << 1) >= heap_count)
                        break;

                l = i << 1;
                r = (i << 1) | 1;

                if (num_heap[l] > num_heap[r])
                        l = r;

                if (num_heap[i] <= num_heap[l])
                        break;

                heap_exchange (i, l);
                i = l;
        }
}



static void
heap_insert (int i)
{
        heap_count++;
        num_heap[heap_count] = i;
        heapify_up ();
}



static int
heap_extract_min (void)
{
        int last = heap_count;
        int ret  = num_heap[1];

        heap_count--;

        num_heap[1]    = num_heap[last];
        num_heap[last] = -1;

        heapify_down ();
        
        return ret;
}



static void
heap_init (void)
{
        int i;

        for (i = 0; i < HEAP_SIZE; i++)
                num_heap[i] = -1;

        for (i = 1; i < HEAP_SIZE; i++)
                heap_insert (i);
}

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


static void
curses_cleanup (int signum)
{
        if (error_indicator)
                raise (signum);

        error_indicator = 1;

        /* cleanup code start */
        endwin ();
        /* cleanup code end */

        signal (signum, SIG_DFL);
        raise (signum);
}



static void
ncurses_setup (void)
{
        signal (SIGTERM, curses_cleanup);
        signal (SIGQUIT, curses_cleanup);
        signal (SIGINT,  curses_cleanup);
        signal (SIGSEGV, curses_cleanup);
        signal (SIGFPE,  curses_cleanup);
        signal (SIGABRT, curses_cleanup);
        signal (SIGBUS,  curses_cleanup);
  
        if (! initscr ()){
                error_critical (1, 0, _("ncurses: initialization failure"));
        }

        cbreak ();
        noecho ();
        nonl ();
        curs_set (0);

        if (has_colors ()){
                start_color ();
        }
}



static void
destroy_list (struct frame *list)
{
        if (list == NULL)
                return;

        destroy_list (list->next);
        xfree (list);
}



static struct frame *
list_rem_win (struct frame **list, WINDOW *win)
{
        struct frame *result;
        
        if (*list == NULL)
                return NULL;

        if ((*list)->win == win){
                result = *list;
                *list  = (*list)->next;
                return result;
        }

        return list_rem_win (& (*list)->next, win);
}



static struct frame *
list_find_win (struct frame *list, WINDOW *win)
{
        if (list == NULL)
                return NULL;

        if (list->win == win)
                return list;

        return list_find_win (list->next, win);
}



static struct frame *
list_find_num (struct frame *list, int num)
{
        if (list == NULL)
                return NULL;

        if (list->num == num)
                return list;

        return list_find_num (list->next, num);
}


static void
unfocus (void)
{
        if (focused_window == NULL)
                return;
        if (focused_window->unset_focus)
                focused_window->unset_focus ();
}



static int
list_find_min (struct frame *list, int i)
{
        int x;
        
        if (list == NULL)
                return i;

        x = list_find_min (list->next, i);

        if (i == x){
                return list->num;
        }
        else if (x > i){
                if (list->num < x && list->num > i)
                        return list->num;
                return x;
        }
        else {
                if (list->num < x || list->num > i)
                        return list->num;
                return x;
        }
}



static int
list_find_max (struct frame *list, int i)
{
        int x;
        
        if (list == NULL)
                return i;

        x = list_find_min (list->next, i);

        if (i == x){
                return list->num;
        }
        else if (x < i){
                if (list->num > x && list->num < i)
                        return list->num;
                return x;
        }
        else {
                if (list->num > x || list->num < i)
                        return list->num;
                return x;
        }
}


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


void
interface_free_resources (void)
{
        destroy_list (visible_windows);
        destroy_list (hidden_windows);
        destroy_list (other_windows);
}



void
interface_setup (void)
{
        error_indicator = 0;

        heap_init ();
        ncurses_setup ();
}



void
interface_post_setup (void)
{
        interface_focus (1);
}



void
interface_redraw (void)
{
        struct frame *list;

        for (list = other_windows; list; list = list->next){
                if (list->draw)
                        list->draw ();
                else
                        touchwin (list->win);
                if (list->show)
                        list->show ();
                else
                        wnoutrefresh (list->win);
        }
        for (list = visible_windows; list; list = list->next){
                if (list->draw)
                        list->draw ();
                else
                        touchwin (list->win);
                if (list->show)
                        list->show ();
                else
                        wnoutrefresh (list->win);
        }
        if (focused_window->draw)
                focused_window->draw ();
        if (focused_window->show)
                focused_window->show ();
}



void
interface_focus (int num)
{
        struct frame *frame = list_find_num (visible_windows, num);

        if (frame == NULL)
                return;
        
        if (focused_window && focused_window->num == num)
                return;

        unfocus ();

        status_switch_window (frame->name, frame->num);

        if (frame->set_focus)
                frame->set_focus ();
        focused_window = frame;

        topbar_refresh ();

        list_rem_win (& visible_windows, frame->win);
        frame->next     = visible_windows;
        visible_windows = frame;
}


void
interface_next_window (void)
{
        int i;
        
        if (focused_window == NULL)
                return;

        i = list_find_min (visible_windows, focused_window->num);
        if (i == focused_window->num)
                return;

        interface_focus (i);
}



void
interface_prev_window (void)
{
        int i;

        if (focused_window == NULL)
                return;

        i = list_find_max (visible_windows, focused_window->num);
        if (i == focused_window->num)
                return;

        interface_focus (i);
}


void
interface_num_window (void)
{
        int i = cmd_last_char ();

        if (i < '0' || i > '9')
                return;

        interface_focus (i - '0');
}

/****************************************************************************
 *    WINDOW FUNCTIONS
 ****************************************************************************/


WINDOW *
window_create (char *name, int height, int width, int top, int left,
               int focus)
{
        struct frame *frame = xmalloc (sizeof (struct frame));

        frame->show        = NULL;
        frame->draw        = NULL;
        frame->set_focus   = NULL;
        frame->unset_focus = NULL;
        frame->name        = name;
        frame->num         = -1;
        frame->hidden      = 1;
        frame->win         = newwin (height, width, top, left);

        if (frame->win == NULL)
                error_critical (1, 0, _("invalid window"));

        if (focus){
                frame->next    = hidden_windows;
                hidden_windows = frame;
        }
        else {
                frame->next    = other_windows;
                other_windows  = frame;
        }

        return frame->win;
}


void
window_set_functions (WINDOW *win, void (*show)(void), void (*draw)(void),
                      void (*set_focus)(void), void (*unset_focus)(void))
{
        struct frame *frame = list_find_win (hidden_windows, win);

        if (frame == NULL){
                debug_msg (DEBUG_ERROR, "no frame found in window_set_funs");
                return;
        }

        frame->show        = show;
        frame->draw        = draw;
        frame->set_focus   = set_focus;
        frame->unset_focus = unset_focus;
}



void
window_hide (WINDOW *win)
{
        struct frame *frame = list_rem_win (& visible_windows, win);

        if (frame == NULL)
                return;

        heap_insert (frame->num);

        frame->num     = -1;
        frame->next    = hidden_windows;
        hidden_windows = frame;

        if (visible_windows)
                interface_focus (visible_windows->num);

        interface_redraw ();
}



void
window_show (WINDOW *win)
{
        struct frame *frame = list_rem_win (& hidden_windows, win);

        if (frame == NULL){
                frame = list_find_win (visible_windows, win);
                if (frame)
                        interface_focus (frame->num);
                return;
        }

        frame->num      = heap_extract_min ();
        frame->next     = visible_windows;
        visible_windows = frame;

        interface_focus (frame->num);
}



int
window_addch (WINDOW *win, chtype c)
{
        int ret;

        if (win == NULL)
                return ERR;

        if (c == '\r' || c == '\n')
                return ERR;

        if (c == '\t')
                c = ' ';

        if (c < 32){
                wattron (win, A_REVERSE);
                ret = waddch (win, c + 64);
                wattroff (win, A_REVERSE);
                return ret;
        }

        waddch (win, c);
        return OK;
}



int
window_addnstr (WINDOW *win, const char *str, int n)
{
        int i = n;

        if (win == NULL || str == NULL)
                return 0;

        while (i && *str){
                if (window_addch (win, (unsigned char) *str) != ERR)
                        i--;
                str++;
        }

        return n - i;
}



int
window_addnast (WINDOW *win, const char *str, int n)
{
        int i = n;

        if (win == NULL || str == NULL)
                return 0;

        while (i && *str){
                if (window_addch (win, '*') != ERR)
                        i--;
                str++;
        }

        return n - i;
}



int
window_addchnstr (WINDOW *win, const chtype *str, int n)
{
        int i = n;

        if (win == NULL || str == NULL)
                return 0;

        while (i && *str){
                if (window_addch (win, *str) != ERR)
                        i--;
                str++;
        }

        return n - i;
}


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


syntax highlighted by Code2HTML, v. 0.9.1