/* ------------ helpbox.c ----------- */

#include "dflat.h"
#include "htree.h"

extern DBOX HelpBox;

/* -------- strings of D-Flat classes for calling default
      help text collections -------- */
char *ClassNames[] = {
    #undef ClassDef
    #define ClassDef(c,b,p,a) #c,
    #include "classes.h"
    NULL
};

#define MAXHEIGHT (SCREENHEIGHT-10)
#define MAXHELPKEYWORDS 50  /* --- maximum keywords in a window --- */
#define MAXHELPSTACK 100

static struct helps *FirstHelp;
static struct helps *ThisHelp;
static int HelpCount;

static int HelpStack[MAXHELPSTACK];
static int stacked;

/* --- keywords in the current help text -------- */
static struct keywords {
	struct helps *hkey;
    int lineno;
    int off1, off2, off3;
    char isDefinition;
} KeyWords[MAXHELPKEYWORDS];
static struct keywords *thisword;
static int keywordcount;

static FILE *helpfp;
static char hline [160];
static BOOL Helping;

static void SelectHelp(WINDOW, struct helps *, BOOL);
static void ReadHelp(WINDOW);
static struct helps *FindHelp(char *);
static void DisplayDefinition(WINDOW, char *);
static void BestFit(WINDOW, DIALOGWINDOW *);

/* ------------- CREATE_WINDOW message ------------ */
static void CreateWindowMsg(WINDOW wnd)
{
    Helping = TRUE;
    GetClass(wnd) = HELPBOX;
    InitWindowColors(wnd);
    if (ThisHelp != NULL)
        ThisHelp->hwnd = wnd;
}

/* ------------- COMMAND message ------------ */
static BOOL CommandMsg(WINDOW wnd, PARAM p1)
{
    switch ((int)p1)    {
        case ID_PREV:
            if (ThisHelp  != NULL)
                SelectHelp(wnd, FirstHelp+(ThisHelp->prevhlp), TRUE);
            return TRUE;
        case ID_NEXT:
            if (ThisHelp != NULL)
                SelectHelp(wnd, FirstHelp+(ThisHelp->nexthlp), TRUE);
            return TRUE;
        case ID_BACK:
			if (stacked)
				SelectHelp(wnd, FirstHelp+HelpStack[--stacked], FALSE);
            return TRUE;
        default:
            break;
    }
    return FALSE;
}

/* ------------- KEYBOARD message ------------ */
static BOOL KeyboardMsg(WINDOW wnd, PARAM p1)
{
    WINDOW cwnd;

    cwnd = ControlWindow(wnd->extension, ID_HELPTEXT);
    if (cwnd == NULL || inFocus != cwnd)
        return FALSE;
    switch ((int)p1)    {
        case '\r':
			if (keywordcount)
	            if (thisword != NULL)    {
					char *hp = thisword->hkey->hname;
        	        if (thisword->isDefinition)
            	        DisplayDefinition(GetParent(wnd), hp);
                	else
                    	SelectHelp(wnd, thisword->hkey, TRUE);
	            }
            return TRUE;
        case '\t':
			if (!keywordcount)
				return TRUE;
            if (thisword == NULL ||
					++thisword == KeyWords+keywordcount)
	            thisword = KeyWords;
            break;
        case SHIFT_HT:
			if (!keywordcount)
				return TRUE;
			if (thisword == NULL || thisword == KeyWords)
				thisword = KeyWords+keywordcount;
			--thisword;
			break;;
        default:
			return FALSE;
    }
    if (thisword->lineno < cwnd->wtop ||
            thisword->lineno >=
                cwnd->wtop + ClientHeight(cwnd))  {
        int distance = ClientHeight(cwnd)/2;
        do    {
            cwnd->wtop = thisword->lineno-distance;
            distance /= 2;
        }
        while (cwnd->wtop < 0);
    }
    SendMessage(cwnd, PAINT, 0, 0);
    return TRUE;
}

/* ---- window processing module for the HELPBOX ------- */
int HelpBoxProc(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
    switch (msg)    {
        case CREATE_WINDOW:
            CreateWindowMsg(wnd);
            break;
        case INITIATE_DIALOG:
            ReadHelp(wnd);
            break;
        case COMMAND:
            if (p2 != 0)
                break;
            if (CommandMsg(wnd, p1))
                return TRUE;
            break;
        case KEYBOARD:
            if (WindowMoving)
                break;
            if (KeyboardMsg(wnd, p1))
                return TRUE;
            break;
        case CLOSE_WINDOW:
            if (ThisHelp != NULL)
                ThisHelp->hwnd = NULL;
            Helping = FALSE;
            break;
        default:
            break;
    }
    return BaseWndProc(HELPBOX, wnd, msg, p1, p2);
}

/* ---- PAINT message for the helpbox text editbox ---- */
static int PaintMsg(WINDOW wnd, PARAM p1, PARAM p2)
{
    int rtn;
    if (thisword != NULL)    {
        WINDOW pwnd = GetParent(wnd);
        char *cp;
        cp = TextLine(wnd, thisword->lineno);
        cp += thisword->off1;
        *(cp+1) =
            (pwnd->WindowColors[SELECT_COLOR][FG] & 255) | 0x80;
        *(cp+2) =
            (pwnd->WindowColors[SELECT_COLOR][BG] & 255) | 0x80;
        rtn = DefaultWndProc(wnd, PAINT, p1, p2);
        *(cp+1) =
            (pwnd->WindowColors[HILITE_COLOR][FG] & 255) | 0x80;
        *(cp+2) =
            (pwnd->WindowColors[HILITE_COLOR][BG] & 255) | 0x80;
        return rtn;
    }
    return DefaultWndProc(wnd, PAINT, p1, p2);
}

/* ---- LEFT_BUTTON message for the helpbox text editbox ---- */
static int LeftButtonMsg(WINDOW wnd, PARAM p1, PARAM p2)
{
    int rtn, mx, my, i;

    rtn = DefaultWndProc(wnd, LEFT_BUTTON, p1, p2);
    mx = (int)p1 - GetClientLeft(wnd);
    my = (int)p2 - GetClientTop(wnd);
    my += wnd->wtop;
    thisword = KeyWords;
    for (i = 0; i < keywordcount; i++)    {
        if (my == thisword->lineno)    {
            if (mx >= thisword->off2 &&
                        mx < thisword->off3)    {
                SendMessage(wnd, PAINT, 0, 0);
                if (thisword->isDefinition)    {
                    WINDOW pwnd = GetParent(wnd);
                    if (pwnd != NULL)
                        DisplayDefinition(GetParent(pwnd),
                            thisword->hkey->hname);
                }
                break;
            }
        }
        thisword++;
    }
	if (i == keywordcount)
		thisword = NULL;
    return rtn;
}

/* --- window processing module for HELPBOX's text EDITBOX -- */
int HelpTextProc(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
    switch (msg)    {
		case KEYBOARD:
			break;
        case PAINT:
            return PaintMsg(wnd, p1, p2);
        case LEFT_BUTTON:
            return LeftButtonMsg(wnd, p1, p2);
        case DOUBLE_CLICK:
            PostMessage(wnd, KEYBOARD, '\r', 0);
            break;
        default:
            break;
    }
    return DefaultWndProc(wnd, msg, p1, p2);
}

/* -------- read the help text into the editbox ------- */
static void ReadHelp(WINDOW wnd)
{
    WINDOW cwnd = ControlWindow(wnd->extension, ID_HELPTEXT);
    int linectr = 0;
    if (cwnd == NULL)
        return;
    thisword = KeyWords;
	keywordcount = 0;
    cwnd->wndproc = HelpTextProc;
	SendMessage(cwnd, CLEARTEXT, 0, 0);
    /* ----- read the help text ------- */
    while (TRUE)    {
        unsigned char *cp = hline, *cp1;
        int colorct = 0;
        if (GetHelpLine(hline) == NULL)
            break;
        if (*hline == '<')
            break;
        hline[strlen(hline)-1] = '\0';
        /* --- add help text to the help window --- */
        while (cp != NULL)    {
            if ((cp = strchr(cp, '[')) != NULL)    {
                /* ----- hit a new key word ----- */
                if (*(cp+1) != '.' && *(cp+1) != '*')    {
                    cp++;
                    continue;
                }
                thisword->lineno = cwnd->wlines;
                thisword->off1 = (int) (cp - hline);
                thisword->off2 = thisword->off1 - colorct * 4;
                thisword->isDefinition = *(cp+1) == '*';
                colorct++;
                *cp++ = CHANGECOLOR;
                *cp++ =
            (wnd->WindowColors [HILITE_COLOR] [FG] & 255) | 0x80;
                *cp++ =
            (wnd->WindowColors [HILITE_COLOR] [BG] & 255) | 0x80;
                cp1 = cp;
                if ((cp = strchr(cp, ']')) != NULL)    {
                    if (thisword != NULL)
                        thisword->off3 =
                            thisword->off2 + (int) (cp - cp1);
                    *cp++ = RESETCOLOR;
                }
                if ((cp = strchr(cp, '<')) != NULL)    {
                    char *cp1 = strchr(cp, '>');
                    if (cp1 != NULL)    {
						char hname[80];
                        int len = (int) (cp1 - cp);
						memset(hname, 0, 80);
                        strncpy(hname, cp+1, len-1);
						thisword->hkey = FindHelp(hname);
                        memmove(cp, cp1+1, strlen(cp1));
                    }
                }
				thisword++;
				keywordcount++;
            }
        }
        PutItemText(wnd, ID_HELPTEXT, hline);
        /* -- display help text as soon as window is full -- */
        if (++linectr == ClientHeight(cwnd))	{
			struct keywords *holdthis = thisword;
		    thisword = NULL;
            SendMessage(cwnd, PAINT, 0, 0);
		    thisword = holdthis;
		}
        if (linectr > ClientHeight(cwnd) &&
                !TestAttribute(cwnd, VSCROLLBAR))    {
            AddAttribute(cwnd, VSCROLLBAR);
            SendMessage(cwnd, BORDER, 0, 0);
        }
    }
    thisword = NULL;
}

/* ---- compute the displayed length of a help text line --- */
static int HelpLength(char *s)
{
    int len = strlen(s);
    char *cp = strchr(s, '[');
    while (cp != NULL)    {
        len -= 4;
        cp = strchr(cp+1, '[');
    }
    cp = strchr(s, '<');
    while (cp != NULL)    {
        char *cp1 = strchr(cp, '>');
        if (cp1 != NULL)
            len -= (int) (cp1-cp)+1;
        cp = strchr(cp1, '<');
    }
    return len;
}

/* ----------- load the help text file ------------ */
void LoadHelpFile()
{
	long where;
	int i;
    if (Helping)
        return;
    UnLoadHelpFile();
    if ((helpfp = OpenHelpFile()) == NULL)
        return;
	fseek(helpfp, - (long) sizeof(long), SEEK_END);
	fread(&where, sizeof(long), 1, helpfp);
	fseek(helpfp, where, SEEK_SET);
	fread(&HelpCount, sizeof(int), 1, helpfp);
	FirstHelp = DFcalloc(sizeof(struct helps) * HelpCount, 1);
	for (i = 0; i < HelpCount; i++)	{
		int len;
		fread(&len, sizeof(int), 1, helpfp);
		if (len)	{
			(FirstHelp+i)->hname = DFcalloc(len+1, 1);
			fread((FirstHelp+i)->hname, len+1, 1, helpfp);
		}
		fread(&len, sizeof(int), 1, helpfp);
		if (len)	{
			(FirstHelp+i)->comment = DFcalloc(len+1, 1);
			fread((FirstHelp+i)->comment, len+1, 1, helpfp);
		}
		fread(&(FirstHelp+i)->hptr, sizeof(int)*5+sizeof(long), 1, helpfp);
	}
    fclose(helpfp);
}

/* ------ free the memory used by the help file table ------ */
void UnLoadHelpFile(void)
{
	int i;
	for (i = 0; i < HelpCount; i++)	{
        free((FirstHelp+i)->comment);
        free((FirstHelp+i)->hname);
	}
	free(FirstHelp);
	FirstHelp = NULL;
    free(HelpTree);
	HelpTree = NULL;
}

static void BuildHelpBox(WINDOW wnd)
{
    int offset, i;

    /* -- seek to the first line of the help text -- */
    SeekHelpLine(ThisHelp->hptr, ThisHelp->bit);
    /* ----- read the title ----- */
    GetHelpLine(hline);
    hline[strlen(hline)-1] = '\0';
	free(HelpBox.dwnd.title);
    HelpBox.dwnd.title = DFmalloc(strlen(hline)+1);
    strcpy(HelpBox.dwnd.title, hline);
    /* ----- set the height and width ----- */
    HelpBox.dwnd.h = min(ThisHelp->hheight, MAXHEIGHT)+7;
    HelpBox.dwnd.w = max(45, ThisHelp->hwidth+6);
    /* ------ position the help window ----- */
	if (wnd != NULL)
	    BestFit(wnd, &HelpBox.dwnd);
    /* ------- position the command buttons ------ */
    HelpBox.ctl[0].dwnd.w = max(40, ThisHelp->hwidth+2);
    HelpBox.ctl[0].dwnd.h =
                min(ThisHelp->hheight, MAXHEIGHT)+2;
    offset = (HelpBox.dwnd.w-40) / 2;
	for (i = 1; i < 5; i++)    {
   		HelpBox.ctl[i].dwnd.y =
           		min(ThisHelp->hheight, MAXHEIGHT)+3;
   		HelpBox.ctl[i].dwnd.x = (i-1) * 10 + offset;
	}
    /* ---- disable ineffective buttons ---- */
    if (ThisHelp->nexthlp == -1)
        DisableButton(&HelpBox, ID_NEXT);
	else
        EnableButton(&HelpBox, ID_NEXT);
    if (ThisHelp->prevhlp == -1)
        DisableButton(&HelpBox, ID_PREV);
	else 
        EnableButton(&HelpBox, ID_PREV);
}

/* ----- select a new help window from its name ----- */
static void SelectHelp(WINDOW wnd, struct helps *newhelp, BOOL recall)
{
	if (newhelp != NULL)	{
		int i, x, y;
		SendMessage(wnd, HIDE_WINDOW, 0, 0);
		if (recall && stacked < MAXHELPSTACK)
			HelpStack[stacked++] = ThisHelp-FirstHelp;
		ThisHelp = newhelp;
		SendMessage(GetParent(wnd), DISPLAY_HELP, (PARAM) ThisHelp->hname, 0);
		if (stacked)
		    EnableButton(&HelpBox, ID_BACK);
		else 
		    DisableButton(&HelpBox, ID_BACK);
		BuildHelpBox(NULL);
		AddTitle(wnd, HelpBox.dwnd.title);
		/* --- reposition and resize the help window --- */
		HelpBox.dwnd.x = (SCREENWIDTH-HelpBox.dwnd.w)/2;
		HelpBox.dwnd.y = (SCREENHEIGHT-HelpBox.dwnd.h)/2;
		SendMessage(wnd, MOVE, HelpBox.dwnd.x, HelpBox.dwnd.y);
		SendMessage(wnd, SIZE,
						HelpBox.dwnd.x + HelpBox.dwnd.w - 1,
						HelpBox.dwnd.y + HelpBox.dwnd.h - 1);
		/* --- reposition the controls --- */
	    for (i = 0; i < 5; i++)    {
			WINDOW cwnd = HelpBox.ctl[i].wnd;
			x = HelpBox.ctl[i].dwnd.x+GetClientLeft(wnd);
			y = HelpBox.ctl[i].dwnd.y+GetClientTop(wnd);
			SendMessage(cwnd, MOVE, x, y);
			if (i == 0)	{
				x += HelpBox.ctl[i].dwnd.w - 1;
				y += HelpBox.ctl[i].dwnd.h - 1;
				SendMessage(cwnd, SIZE, x, y);
			}
		}
		/* --- read the help text into the help window --- */
		ReadHelp(wnd);
		SendMessage(wnd, SHOW_WINDOW, 0, 0);
	}
}
/* ---- strip tildes from the help name ---- */
static void StripTildes(char *fh, char *hp)
{
	while (*hp)	{
		if (*hp != '~')
			*fh++ = *hp;
		hp++;
	}
	*fh = '\0';
}
/* --- return the comment associated with a help window --- */
char *HelpComment(char *Help)
{
	char FixedHelp[30];
	StripTildes(FixedHelp, Help);
    if ((ThisHelp = FindHelp(FixedHelp)) != NULL)
		return ThisHelp->comment;
	return NULL;
}
/* ---------- display help text ----------- */
BOOL DisplayHelp(WINDOW wnd, char *Help)
{
	char FixedHelp[30];
	BOOL rtn = FALSE;

    if (Helping)
        return TRUE;
	StripTildes(FixedHelp, Help);
	stacked = 0;
	wnd->isHelping++;
    if ((ThisHelp = FindHelp(FixedHelp)) != NULL)	{
        if ((helpfp = OpenHelpFile()) != NULL)    {
			BuildHelpBox(wnd);
		    DisableButton(&HelpBox, ID_BACK);
            /* ------- display the help window ----- */
            DialogBox(NULL, &HelpBox, TRUE, HelpBoxProc);
            free(HelpBox.dwnd.title);
			HelpBox.dwnd.title = NULL;
            fclose(helpfp);
            rtn = TRUE;
        }
    }
	--wnd->isHelping;
    return rtn;
}

/* ------- display a definition window --------- */
static void DisplayDefinition(WINDOW wnd, char *def)
{
    WINDOW dwnd;
    WINDOW hwnd = wnd;
    int y;

    if (GetClass(wnd) == POPDOWNMENU)
        hwnd = GetParent(wnd);
    y = GetClass(hwnd) == MENUBAR ? 2 : 1;
    if ((ThisHelp = FindHelp(def)) != NULL)    {
        clearBIOSbuffer();
        if ((helpfp = OpenHelpFile()) != NULL)    {
            clearBIOSbuffer();
            dwnd = CreateWindow(
                        TEXTBOX,
                        NULL,
                        GetClientLeft(hwnd),
                        GetClientTop(hwnd)+y,
                        min(ThisHelp->hheight, MAXHEIGHT)+3,
                        ThisHelp->hwidth+2,
                        NULL,
                        wnd,
                        NULL,
                        HASBORDER | NOCLIP | SAVESELF);
            if (dwnd != NULL)    {
                clearBIOSbuffer();
                /* ----- read the help text ------- */
                SeekHelpLine(ThisHelp->hptr, ThisHelp->bit);
                while (TRUE)    {
                    clearBIOSbuffer();
                    if (GetHelpLine(hline) == NULL)
                        break;
                    if (*hline == '<')
                        break;
                    hline[strlen(hline)-1] = '\0';
                    SendMessage(dwnd,ADDTEXT,(PARAM)hline,0);
                }
                SendMessage(dwnd, SHOW_WINDOW, 0, 0);
                SendMessage(NULL, WAITKEYBOARD, 0, 0);
                SendMessage(NULL, WAITMOUSE, 0, 0);
                SendMessage(dwnd, CLOSE_WINDOW, 0, 0);
            }
            fclose(helpfp);
        }
    }
}

/* ------ compare help names with wild cards ----- */
static BOOL wildcmp(char *s1, char *s2)
{
    while (*s1 || *s2)    {
        if (tolower(*s1) != tolower(*s2))
            if (*s1 != '?' && *s2 != '?')
                return TRUE;
        s1++, s2++;
    }
    return FALSE;
}

/* --- ThisHelp = the help window matching specified name --- */
static struct helps *FindHelp(char *Help)
{
	int i;
	struct helps *thishelp = NULL;
	for (i = 0; i < HelpCount; i++)	{
        if (wildcmp(Help, (FirstHelp+i)->hname) == FALSE)	{
		    thishelp = FirstHelp+i;
            break;
		}
	}
	return thishelp;
}

static int OverLap(int a, int b)
{
    int ov = a - b;
    if (ov < 0)
        ov = 0;
    return ov;
}

/* ----- compute the best location for a help dialogbox ----- */
static void BestFit(WINDOW wnd, DIALOGWINDOW *dwnd)
{
    int above, below, right, left;
    if (GetClass(wnd) == MENUBAR ||
                GetClass(wnd) == APPLICATION)    {
        dwnd->x = dwnd->y = -1;
        return;
    }
    /* --- compute above overlap ---- */
    above = OverLap(dwnd->h, GetTop(wnd));
    /* --- compute below overlap ---- */
    below = OverLap(GetBottom(wnd), SCREENHEIGHT-dwnd->h);
    /* --- compute right overlap ---- */
    right = OverLap(GetRight(wnd), SCREENWIDTH-dwnd->w);
    /* --- compute left  overlap ---- */
    left = OverLap(dwnd->w, GetLeft(wnd));

    if (above < below)
        dwnd->y = max(0, GetTop(wnd)-dwnd->h-2);
    else
        dwnd->y = min(SCREENHEIGHT-dwnd->h, GetBottom(wnd)+2);
    if (right < left)
        dwnd->x = min(GetRight(wnd)+2, SCREENWIDTH-dwnd->w);
    else
        dwnd->x = max(0, GetLeft(wnd)-dwnd->w-2);

    if (dwnd->x == GetRight(wnd)+2 ||
            dwnd->x == GetLeft(wnd)-dwnd->w-2)
        dwnd->y = -1;
    if (dwnd->y ==GetTop(wnd)-dwnd->h-2 ||
            dwnd->y == GetBottom(wnd)+2)
        dwnd->x = -1;
}


