/* XView help viewer routines.
   Copyright (C) 1995 Jakub Jelinek.
   
   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; either version 2 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>

#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <xview/xview.h>
#include <xview/frame.h>
#include <xview/panel.h>
#include <xview/svrimage.h>
#include <xview/canvas.h>
#include <xview/scrollbar.h>
#include <xview/cms.h>
#include <xview/xv_xrect.h>
#include "help.h"
#include "../src/mad.h"
#include "../src/util.h"
#include "xvmain.h"

#include "help.icons"

#define TOPMARGIN 10
#define LEFTMARGIN 10
#define RIGHTMARGIN 10
#define BOTTOMMARGIN 10

extern Frame mcframe;
extern Server_image mcimage; /* xvinfo.c */
Frame helpframe = XV_NULL;
Panel helppanel;
Canvas helpcanvas;
Cms helpcms;
extern Display *dpy;
typedef struct {
    Xv_Font font;
    GC gc;
    int height;
} fontstruct;

extern char *currentpoint, *startpoint;
static void repaint (void);
static void clear_link_areas (void);

static void x_index_cmd (void)
{
    help_index_cmd ();
    repaint ();
}

static void x_back_cmd (void)
{
}

static void x_previous_cmd (void)
{
}

static void x_next_cmd (void)
{
}

static void x_search_cmd (void)
{
}

static void x_help_on_help (void)
{
    help_help_cmd ();
    repaint ();
}

static void x_quit_cmd (void)
{
    xv_destroy_safe (helpframe);
    clear_link_areas ();
    helpframe = XV_NULL;
    interactive_display_finish ();
    xv_dispatch_a_bit ();
}

fontstruct getfont (char *name, int fg)
{
    fontstruct f;
    XGCValues gcvalues;
    
    f.font = (Xv_Font) xv_find (helpcanvas, FONT, FONT_NAME, name, NULL);
    if (f.font == XV_NULL)
        f.font = (Xv_Font) xv_get (helpframe, XV_FONT);
    gcvalues.font = (Font) xv_get (f.font, XV_XID);
    gcvalues.foreground = (unsigned long) xv_get (helpcms, CMS_PIXEL, fg);
    gcvalues.background = (unsigned long) xv_get (helpcms, CMS_BACKGROUND_PIXEL);    
    gcvalues.graphics_exposures = FALSE;
    f.gc = XCreateGC (dpy, RootWindow (dpy, DefaultScreen (dpy)), 
        GCForeground|GCBackground|GCFont|GCGraphicsExposures, &gcvalues);
    f.height = xv_get (f.font, FONT_DEFAULT_CHAR_HEIGHT);
    return f;
}

static char buffer [200];
int x, h, y;
int bufferindex;
fontstruct fonormal, fobold, folink, fotitle;
#define NORMAL &fonormal
#define BOLD &fobold
#define LINK &folink
#define TITLE &fotitle
fontstruct *curfont, *linefont, *wordfont;
int startup;
int paintwidth;
Window xId;
char *linebegin, *wordbegin;
int tabsize = 30;
struct area;
typedef struct area *AREA;
struct area {
    int wx;  /* West x, Northwest y, Southwest y etc. */
    int nwy;
    int swy;
    int ex;
    int ney;
    int sey;
    char *jump;
    AREA next;
};
AREA firstarea = NULL, lastarea = NULL;
int do_link_areas = 1;
static int painting = 1, linepainting = 1;

static void clear_link_areas (void)
{
    AREA p;
    
    while (firstarea != NULL) {
        p = firstarea;
        firstarea = firstarea->next;
        free (p);
    }
    lastarea = NULL;
}

static void start_link_area (char *link, int test)
{
    AREA p;
    
    if (test || !do_link_areas)
        return;
    
    p = (AREA) xmalloc (sizeof (struct area), "Help link area");
    if (firstarea != NULL)
        lastarea->next = p;
    else
    	firstarea = p;
    lastarea = p;
    p->wx = x;
    p->nwy = y - h;
    p->swy = y;
    p->ex = x;
    p->ney = y - h;
    p->sey = y;
    p->jump = link;
    p->next = NULL;
}

static void end_link_area (int test)
{
    if (test || !do_link_areas || !lastarea)
        return;
    
    lastarea->ex = x;
    lastarea->ney = y - h;
    lastarea->sey = y;
}

static AREA is_in_area (int x, int y)
{
    AREA p;
    
    for (p = firstarea; p != (AREA) NULL; p = p->next) {
        if (y < p->nwy || y >= p->sey)
            continue;
        if (y < p->swy) {
            if (x < p->wx)
                continue;
            if (p->nwy == p->ney && x >= p->ex)
                continue;
        } else if (y >= p->ney) {
            if (x >= p->ex)
                continue;
        }
        return p;
    }
    return NULL;
}

static void hparse (char *paint_start, int test);

static void setfont (fontstruct *f)
{
    curfont = f;
}

static void newline (void)
{
    x = LEFTMARGIN;
    y += h;
    curfont = linefont;
    hparse (linebegin, 0);
    h = 0;
    x = LEFTMARGIN;
    linepainting = painting;
    linebegin = wordbegin;
    linefont = curfont;
}

static int flush (char *p, int test)
{
    Font_string_dims dims;

    buffer [bufferindex] = 0;
    xv_get (curfont->font, FONT_STRING_DIMS, buffer, &dims);
    if (x + dims.width > paintwidth - RIGHTMARGIN && x) {
        if (test)
            newline ();
        else
            return 1;
    }
    if (!test) {
        XDrawString (dpy, xId, curfont->gc, x, y, buffer, strlen (buffer));
    }
    x += dims.width;
    if (dims.height > h)
        h = dims.height;
    wordbegin = p + 1;
    bufferindex = 0;
    return 0;
} 

static void hparse (char *paint_start, int test)
{
    char *p;
    int  c;

    if (test) {
        painting = 1;
        linepainting = 1;
    } else
    	painting = linepainting;
    	
    if (test)
        setfont (NORMAL);
    h = 0;
    x = LEFTMARGIN;
    bufferindex = 0;
    if (test) {
        wordbegin = paint_start;
        linebegin = paint_start;
        linefont = curfont;
    }
    
    for (p = paint_start; *p != CHAR_NODE_END; p++){
	c = *p;
	switch (c){
	case CHAR_LINK_START:
	    if (flush (p, test))
	        return;
	    setfont (LINK);
	    start_link_area (p, test);
	    break;
	case CHAR_LINK_POINTER:
	    if (flush (p, test))
	        return;
	    setfont (NORMAL);
	    painting = 0;
	    end_link_area (test);
	    break;
	case CHAR_LINK_END:
	    painting = 1;
	    break;
	case CHAR_ALTERNATE:
	    break;
	case CHAR_NORMAL:
	    break;
	case CHAR_VERSION:
	    strcpy (buffer + bufferindex, VERSION);
	    bufferindex += strlen (VERSION);
	    break;
	case CHAR_BOLD_ON:
	    if (flush (p, test))
	        return;
	    setfont (BOLD);
	    break;
	case CHAR_BOLD_OFF:
	    if (flush (p, test))
	        return;
	    setfont (NORMAL);
	    break;
	case CHAR_MCLOGO:
	    {
	        int mcimage_width = (int) xv_get (mcimage, XV_WIDTH);
	        int mcimage_height = (int) xv_get (mcimage, XV_HEIGHT);
	    
	        if (flush (p, test))
	            return;
	        if (!test) {
	            XCopyArea (dpy, 
	                (Drawable) xv_get (mcimage, SERVER_IMAGE_PIXMAP),
	                xId, curfont->gc, 0,0, mcimage_width, mcimage_height,
	                x, y - mcimage_height);
	        }
	        x += mcimage_width;
	        if (h < mcimage_height)
	            h = mcimage_height;
	        break;
	    }
	case CHAR_TEXTONLY_START:
	    while (*p && *p != CHAR_NODE_END && *p != CHAR_TEXTONLY_END)
	        p++;
	    if (*p != CHAR_TEXTONLY_END)
	        p--;
	    break;
	case CHAR_XONLY_START:
	case CHAR_XONLY_END:
	    break;
	case '\n':
	    if (p [1] == '\n') {
	        if (flush (p, test))
	            return;
	        if (x > LEFTMARGIN) {
	            if (!test)
	                return;
	            else
	                newline ();
	        }
	        break;
	    }
	case ' ':
	    buffer [bufferindex++] = ' ';
	    if (flush (p, test))
	        return;
	    break;
	case '\t':
	    if (flush (p, test))
	        return;
	    x = (x + tabsize - LEFTMARGIN) / tabsize * tabsize + LEFTMARGIN;
	    if (x > paintwidth - RIGHTMARGIN) {
	        if (!test)
	            return;
	        else
	            newline ();
	        x = tabsize + LEFTMARGIN;
	    }
	    break;
	default:
	    if (!painting)
		continue;
	    buffer [bufferindex++] = c;
	    break;
	}
    }
    if (flush (p, test))
       return;
    if (x > LEFTMARGIN) {
        if (!test)
            return;
        else
            newline ();
    }
}

static void canvas_repaint (Canvas canvas, Xv_Window paint)
{
    if (startup)
        return;
    xId = (Window) xv_get (paint, XV_XID);
    y = TOPMARGIN;
    paintwidth = (int) xv_get (xv_get (paint, CANVAS_PAINT_VIEW_WINDOW), XV_WIDTH);
    XClearWindow (dpy, xId);
    hparse (startpoint, 1);
    XFlush (dpy);
}

static void canvas_resize (Canvas canvas, int width, int height)
{
    if (startup)
        return;
    if (width != paintwidth)
        repaint ();
}

static void repaint (void) 
{
    Xv_Window paint, view;
    Scrollbar scroll;

    clear_link_areas ();
    do_link_areas = 1;
    OPENWIN_EACH_VIEW (helpcanvas, view)
        paint = (Xv_Window) xv_get (view, CANVAS_VIEW_PAINT_WINDOW);
        canvas_repaint (helpcanvas, paint);
        if (do_link_areas) {
            do_link_areas = 0;
            xv_set (helpcanvas,
                CANVAS_MIN_PAINT_HEIGHT, y + BOTTOMMARGIN,
                NULL);
        }
        scroll = (Scrollbar) xv_get (helpcanvas, 
            OPENWIN_VERTICAL_SCROLLBAR, view);
            
        if (scroll != XV_NULL) {
            xv_set (scroll,
                SCROLLBAR_VIEW_START, 0,
                NULL);
            xv_set (scroll,
                SCROLLBAR_OBJECT_LENGTH, y + BOTTOMMARGIN,
                NULL);
        }
    OPENWIN_END_EACH
}

static struct {
    unsigned short *bits;
    void (*callback) (void);
} buttons [] = { 
index_bits, x_index_cmd,
back_bits, x_back_cmd,
previous_bits, x_previous_cmd,
next_bits, x_next_cmd,
search_bits, x_search_cmd };
Cursor cursors [2];

static void event_handler (Xv_Window paint, Event *event)
{
    int x, y;
    AREA p;
    static int current_cursor = 0;

    switch (event_action (event)) {
        case ACTION_SELECT:
            x = event_x (event);
            y = event_y (event);
            p = is_in_area (x, y);
            if (p != NULL && p->jump != NULL) {
                currentpoint = startpoint = help_follow_link (startpoint, p->jump);
                repaint ();
            }
            break;
        case LOC_MOVE:
            x = event_x (event);
            y = event_y (event);
            p = is_in_area (x, y);
            if (p != NULL) {
                if (!current_cursor) {
                    XDefineCursor (dpy, xv_get (paint, XV_XID), cursors [1]);
                    current_cursor = 1;
                }
            } else {
                if (current_cursor) {
                    XDefineCursor (dpy, xv_get (paint, XV_XID), cursors [0]);
                    current_cursor = 0;
                }
            }
            break;
    }
}

static void create_frame (void)
{
    Panel_button_item button;
    int i;

    helpcms = (Cms) xv_create (XV_NULL, CMS,
        CMS_CONTROL_CMS, TRUE,
        CMS_SIZE, CMS_CONTROL_COLORS + 5,
        CMS_NAMED_COLORS, "black", "black", "green4", "red4", "black", NULL,
        NULL);
        
    helpframe = xv_create (mcframe, FRAME, 
        FRAME_LABEL, "Midnight X Commander Help",
        WIN_CMS, helpcms,
        FRAME_INHERIT_COLORS, TRUE,
        NULL);
    
    helppanel = xv_create (helpframe, PANEL,
        NULL);
        
    xv_create (helppanel, PANEL_BUTTON,
        PANEL_LABEL_STRING, "Help",
        PANEL_ITEM_MENU, xv_create (XV_NULL, MENU,
            MENU_ITEM,
                MENU_STRING, "Help on Help",
                MENU_NOTIFY_PROC, x_help_on_help,
                NULL,
            MENU_ITEM,
                MENU_STRING, "Index",
                MENU_NOTIFY_PROC, x_index_cmd,
                NULL,
            MENU_ITEM,
                MENU_STRING, "Back",
                MENU_NOTIFY_PROC, x_back_cmd,
                NULL,
            MENU_ITEM,
                MENU_STRING, "Previous",
                MENU_NOTIFY_PROC, x_previous_cmd,
                NULL,
            MENU_ITEM,
                MENU_STRING, "Next",
                MENU_NOTIFY_PROC, x_next_cmd,
                NULL,
            MENU_ITEM,
                MENU_STRING, "Search",
                MENU_NOTIFY_PROC, x_search_cmd,
                NULL,
            NULL),
        NULL);
    button = (Panel_button_item) xv_create (helppanel, PANEL_BUTTON,
        PANEL_LABEL_STRING, "Quit",
        PANEL_NOTIFY_PROC, x_quit_cmd,
        NULL);
    for (i = 0; i < sizeof (buttons) / sizeof (buttons [0]); i++) {
        button = (Panel_button_item) xv_create (helppanel, PANEL_BUTTON,
            PANEL_LABEL_IMAGE, xv_create (XV_NULL, SERVER_IMAGE,
                SERVER_IMAGE_DEPTH, 1,
                XV_WIDTH, 48,
                XV_HEIGHT, 48,
                SERVER_IMAGE_BITS, buttons [i].bits,
                NULL),
            PANEL_NOTIFY_PROC, buttons [i].callback,
            NULL);
        if (!i)
            xv_set (button,
        	PANEL_NEXT_ROW, -1,
		NULL);
    }

    window_fit (helppanel);
    
    xv_set (helpframe,
        XV_WIDTH, xv_get (helppanel, XV_WIDTH),
        NULL);
        
    xv_set (helppanel,
        XV_WIDTH, WIN_EXTEND_TO_EDGE,
        NULL);
        
    startup = 1;
        
    helpcanvas = xv_create (helpframe, CANVAS,
        WIN_BELOW, helppanel,
        CANVAS_REPAINT_PROC, canvas_repaint,
        CANVAS_RESIZE_PROC, canvas_resize,
        CANVAS_X_PAINT_WINDOW, FALSE,
        CANVAS_PAINTWINDOW_ATTRS,
            WIN_CONSUME_EVENTS,
            WIN_MOUSE_BUTTONS, LOC_MOVE, NULL,
            WIN_EVENT_PROC, event_handler,
            NULL,
        NULL);
        
    cursors [0] = (Cursor) xv_get (xv_get (helpcanvas, WIN_CURSOR), XV_XID);
    cursors [1] = XCreateFontCursor (dpy, XC_hand2);
        
    xv_create (helpcanvas, SCROLLBAR,
        SCROLLBAR_DIRECTION, SCROLLBAR_VERTICAL,
        SCROLLBAR_SPLITTABLE, TRUE,
        NULL);
        
    fonormal = getfont ("lucidasans-12", CMS_CONTROL_COLORS);
    fobold = getfont ("lucidasans-bold-12", CMS_CONTROL_COLORS + 1);
    folink = getfont ("lucidasans-12", CMS_CONTROL_COLORS + 2);
    fotitle = getfont ("lucidasans-bold-18", CMS_CONTROL_COLORS + 3);
    
    window_fit (helpframe);
    
    xv_set (helpframe,
        XV_SHOW, TRUE,
        NULL);
        
    startup = 0;
        
    repaint ();
}

void x_interactive_display (void)
{
    if (helpframe == XV_NULL)
        create_frame ();
}