/*
 * winhelp.c --
 *
 *	Tcl extension module to access HTMLHelp API.
 *	Provides the "winhelp" tcl command.
 *
 *	(c) 2000-2021 chw at ch minus werner dot de
 *
 *	See the file "license.terms" for information on
 *	usage and redistribution of this file, and for a
 *	DISCLAIMER OF ALL WARRANTIES.
 */

#include <windows.h>
#include <htmlhelp.h>
#include <tk.h>
#include <tkWinInt.h>

/*
 * Structure for deferred start of HTML help control.
 */

typedef struct {
    Tk_Window tkwin;		/* Tk window for which to open HTML help */
    Tcl_Obj *obj1, *obj2;	/* Arguments for HtmlHelp() */
} HandlerInfo;

/*
 * Local procedures.
 */

static HWND	CallHtmlHelp(HWND hwnd, const char *fileName,
			     UINT command, const char *data);
static void	MapHandler(ClientData clientData, XEvent *eventPtr);
static int	GetTkWin(Tk_Window mainwin, Tcl_Interp *interp,
			 const char *name, Tk_Window *winPtr);
static int	WinHelpObjCmd(ClientData clientData, Tcl_Interp *interp,
			      int objc, Tcl_Obj *const objv[]);

/*
 *----------------------------------------------------------------------
 *
 *  CallHtmlHelp --
 *
 *	This procedure is called to load the HTML Help DLL and call
 *	the HtmlHelp function therein.
 *
 *----------------------------------------------------------------------
 */

static HWND
CallHtmlHelp(
    HWND hwnd,
    const char *fileName,
    UINT command,
    const char *data)
{
    TCL_DECLARE_MUTEX(initMutex);
    static HINSTANCE inst = NULL;
    typedef HWND (WINAPI hhproc)(HWND, void *, UINT, void *);
    static hhproc *proc = NULL;
    HWND result = NULL;

    Tcl_MutexLock(&initMutex);
    if (inst == NULL) {
	inst = LoadLibraryA("hhctrl.ocx");
	if (inst != NULL) {
	    Tcl_DString testDS;
	    int len;

	    Tcl_WinUtfToTChar("T", 1, &testDS);
	    len = Tcl_DStringLength(&testDS);
	    Tcl_DStringFree(&testDS);
	    proc = (hhproc *) GetProcAddress(inst, (len == 1) ?
					"HtmlHelpA" : "HtmlHelpW");
	    if (proc == NULL) {
		FreeLibrary(inst);
	    }
	}
    }
    Tcl_MutexUnlock(&initMutex);
    if (proc == NULL) {
	MessageBeep(MB_ICONEXCLAMATION);
    } else {
	Tcl_DString fileDS, dataDS;
	void *filePtr, *dataPtr = NULL;

	filePtr = (void *) Tcl_WinUtfToTChar(fileName, -1, &fileDS);
	if (data != NULL) {
	    dataPtr = (void *) Tcl_WinUtfToTChar(data, -1, &dataDS);
	}
	result = proc(hwnd, filePtr, command, dataPtr);
	Tcl_DStringFree(&fileDS);
	if (data != NULL) {
	    Tcl_DStringFree(&dataDS);
	}
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 *  MapHandler --
 *
 *	This procedure is called when a toplevel is initially mapped.
 *      It starts up the HTML help control with the arguments taken
 *	from the HandlerInfo.
 *
 *----------------------------------------------------------------------
 */

static void
MapHandler(
    ClientData clientData,
    XEvent *eventPtr)
{
    HandlerInfo *hiPtr = (HandlerInfo *) clientData;

    if (eventPtr->type == DestroyNotify) {
	goto cleanup;
    } else if (eventPtr->type == MapNotify) {
	HWND hWnd = Tk_GetHWND(Tk_WindowId(hiPtr->tkwin));

	if (hWnd != NULL) {
	    hWnd = GetParent(hWnd);
	    if (hWnd != NULL) {
	        const char *arg1, *arg2 = NULL;

		arg1 = Tcl_GetString(hiPtr->obj1);
		if (hiPtr->obj2 != NULL) {
		    arg2 = Tcl_GetString(hiPtr->obj2);
		}
		CallHtmlHelp(hWnd, arg1, HH_DISPLAY_TOPIC, arg2);
	    }
	}
	Tk_DeleteEventHandler(hiPtr->tkwin, StructureNotifyMask,
			      MapHandler, clientData);
cleanup:
	Tcl_DecrRefCount(hiPtr->obj1);
	if (hiPtr->obj2 != NULL) {
	    Tcl_DecrRefCount(hiPtr->obj2);
	}
	ckfree((char *) hiPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 *  GetTkWin --
 *
 *	Given window's pathname this procedure returns the correspoding
 *	Tk_Window token for that window. It complains if the window is
 *	not a toplevel. In that case an error message is left in the
 *	interpreter.
 *
 *----------------------------------------------------------------------
 */

static int
GetTkWin(
    Tk_Window mainwin,
    Tcl_Interp *interp,
    const char *name,
    Tk_Window *winPtr)
{
    Tk_Window tkwin;

    tkwin = Tk_NameToWindow(interp, name, mainwin);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    if (!Tk_IsTopLevel(tkwin)) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"\"%s\" is not a toplevel window", name));
	return TCL_ERROR;
    }
    Tk_MakeWindowExist(tkwin);
    *winPtr = tkwin;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 *  WinHelpCmd --
 *
 *	This procedure implements the "winhelp" Tcl command.
 *
 *----------------------------------------------------------------------
 */

static int
WinHelpObjCmd(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    Tk_Window tkwin, mainwin = (Tk_Window) clientData;
    HWND hWnd;
    HandlerInfo *hiPtr;

    if (objc < 3 || objc > 4) {
	Tcl_WrongNumArgs(interp, 1, objv, "pathName file ?file?");
	return TCL_ERROR;
    }
    if (GetTkWin(mainwin, interp, Tcl_GetString(objv[1]), &tkwin) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tk_IsMapped(tkwin)) {
	hWnd = Tk_GetHWND(Tk_WindowId(tkwin));
	if (hWnd != NULL &&
	    (hWnd = GetParent(hWnd)) != NULL) {
	    CallHtmlHelp(hWnd, Tcl_GetString(objv[2]), HH_DISPLAY_TOPIC,
			 (objc > 3) ? Tcl_GetString(objv[3]) : NULL);
	    return TCL_OK;
	}
    }

    /*
     * Window might not exist at this point. Start of the HTML help
     * control is deferred until window is mapped.
     */

    hiPtr = (HandlerInfo *) ckalloc(sizeof (HandlerInfo));
    hiPtr->tkwin = tkwin;
    hiPtr->obj1 = objv[2];
    hiPtr->obj2 = (objc > 3) ? objv[3] : NULL;
    Tcl_IncrRefCount(hiPtr->obj1);
    if (hiPtr->obj2 != NULL) {
	Tcl_IncrRefCount(hiPtr->obj2);
    }
    Tk_CreateEventHandler(tkwin, StructureNotifyMask, MapHandler,
			  (ClientData) hiPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 *  Winhelp_Init --
 *
 *	Initialization procedure for this module; creates the "winhelp"
 *	Tcl command.
 *
 *----------------------------------------------------------------------
 */

int DLLEXPORT
Winhelp_Init(Tcl_Interp *interp)
{
#ifdef USE_TCL_STUBS
    if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) {
	return TCL_ERROR;
    }
#else
    if (Tcl_PkgRequire(interp, "Tcl", "8.5-", 0) == NULL) {
	return TCL_ERROR;
    }
#endif
#ifdef USE_TK_STUBS
    if (Tk_InitStubs(interp, "8.5-", 0) == NULL) {
	return TCL_ERROR;
    }
#else
    if (Tcl_PkgRequire(interp, "Tk", "8.5-", 0) == NULL) {
	return TCL_ERROR;
    }
#endif
    Tcl_CreateObjCommand(interp, "winhelp", WinHelpObjCmd,
			 (ClientData) Tk_MainWindow(interp),
			 (Tcl_CmdDeleteProc *) NULL);
    return Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION);
}
