/*
 * help.c: centralised functions to launch Windows HTML Help files.
 */

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

#include "putty.h"
#include "putty-rc.h"

#ifdef NO_HTMLHELP

/* If htmlhelp.h is not available, we can't do any of this at all */
bool has_help(void) { return false; }
void init_help(void) { }
void shutdown_help(void) { }
void launch_help(HWND hwnd, const char *topic) { }
void quit_help(HWND hwnd) { }

#else

#include <htmlhelp.h>

static char *chm_path = NULL;
static bool chm_created_by_us = false;

static bool requested_help;
DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD_PTR));

static HRSRC chm_hrsrc;
static DWORD chm_resource_size = 0;
static const void *chm_resource = NULL;

int has_embedded_chm(void)
{
    static bool checked = false;
    if (!checked) {
        checked = true;

        chm_hrsrc = FindResource(
            NULL, MAKEINTRESOURCE(ID_CUSTOM_CHMFILE),
            MAKEINTRESOURCE(TYPE_CUSTOM_CHMFILE));
    }
    return chm_hrsrc != NULL ? 1 : 0;
}

static bool find_chm_resource(void)
{
    static bool checked = false;
    if (checked)       /* we've been here already */
        goto out;
    checked = true;

    /*
     * Look for a CHM file embedded in this executable as a custom
     * resource.
     */
    if (!has_embedded_chm())    /* set up chm_hrsrc and check if it's NULL */
        goto out;

    chm_resource_size = SizeofResource(NULL, chm_hrsrc);
    if (chm_resource_size == 0)
        goto out;

    HGLOBAL chm_hglobal = LoadResource(NULL, chm_hrsrc);
    if (chm_hglobal == NULL)
        goto out;

    chm_resource = (const uint8_t *)LockResource(chm_hglobal);

  out:
    return chm_resource != NULL;
}

static bool load_chm_resource(void)
{
    bool toret = false;
    char *filename = NULL;
    HANDLE filehandle = INVALID_HANDLE_VALUE;
    bool created = false;

    static bool tried_to_load = false;
    if (tried_to_load)
        goto out;
    tried_to_load = true;

    /*
     * We've found it! Now write it out into a separate file, so that
     * htmlhelp.exe can handle it.
     */

    /* GetTempPath is documented as returning a size of up to
     * MAX_PATH+1 which does not count the NUL */
    char tempdir[MAX_PATH + 2];
    if (GetTempPath(sizeof(tempdir), tempdir) == 0)
        goto out;

    unsigned long pid = GetCurrentProcessId();

    for (uint64_t counter = 0;; counter++) {
        filename = dupprintf(
            "%s\\putty_%lu_%"PRIu64".chm", tempdir, pid, counter);
        filehandle = CreateFile(
            filename, GENERIC_WRITE, FILE_SHARE_READ,
            NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

        if (filehandle != INVALID_HANDLE_VALUE)
            break;                     /* success! */

        if (GetLastError() != ERROR_FILE_EXISTS)
            goto out;                  /* failed for some other reason! */

        sfree(filename);
        filename = NULL;
    }
    created = true;

    const uint8_t *p = (const uint8_t *)chm_resource;
    for (DWORD pos = 0; pos < chm_resource_size ;) {
        DWORD to_write = chm_resource_size - pos;
        DWORD written = 0;

        if (!WriteFile(filehandle, p + pos, to_write, &written, NULL))
            goto out;
        pos += written;
    }

    chm_path = filename;
    filename = NULL;
    chm_created_by_us = true;
    toret = true;

  out:
    if (created && !toret)
        DeleteFile(filename);
    sfree(filename);
    if (filehandle != INVALID_HANDLE_VALUE)
        CloseHandle(filehandle);
    return toret;
}

static bool find_chm_from_installation(void)
{
    static const char *const reg_paths[] = {
        "Software\\SimonTatham\\PuTTY64\\CHMPath",
        "Software\\SimonTatham\\PuTTY\\CHMPath",
    };

    for (size_t i = 0; i < lenof(reg_paths); i++) {
        char *filename = get_reg_sz_simple(
            HKEY_LOCAL_MACHINE, reg_paths[i], NULL);

        if (filename) {
            chm_path = filename;
            chm_created_by_us = false;
            return true;
        }
    }

    return false;
}

void init_help(void)
{
    /* Just in case of multiple calls */
    static bool already_called = false;
    if (already_called)
        return;
    already_called = true;

    /*
     * Don't even try looking for the CHM file if we can't even find
     * the HtmlHelp() API function.
     */
    HINSTANCE dllHH = load_system32_dll("hhctrl.ocx");
    GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA);
    if (!p_HtmlHelpA) {
        FreeLibrary(dllHH);
        return;
    }

    /*
     * If there's a CHM file embedded in this executable, we should
     * use that as the first choice.
     */
    if (find_chm_resource())
        return;

    /*
     * Otherwise, try looking for the CHM in the location that the
     * installer marked in the registry.
     */
    if (find_chm_from_installation())
        return;
}

void shutdown_help(void)
{
    if (chm_path && chm_created_by_us) {
        p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0);
        DeleteFile(chm_path);
    }
    sfree(chm_path);
    chm_path = NULL;
    chm_created_by_us = false;
}

bool has_help(void)
{
    return chm_path != NULL || chm_resource != NULL;
}

void launch_help(HWND hwnd, const char *topic)
{
    if (!chm_path && chm_resource) {
        /*
         * If we've been called without already having a file name for
         * the CHM file, that might be because we've located it in our
         * resource section but not written it to a temp file yet. Do
         * so now, on first use.
         */
        load_chm_resource();
    }

    /* If we _still_ don't have a CHM pathname, we just can't display help. */
    if (!chm_path)
        return;

    if (topic) {
        char *fname = dupprintf(
            "%s::/%s.html>main", chm_path, topic);
        p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0);
        sfree(fname);
    } else {
        p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0);
    }
    requested_help = true;
}

void quit_help(HWND hwnd)
{
    if (requested_help)
        p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0);
    if (chm_path && chm_created_by_us)
        DeleteFile(chm_path);
}

#endif /* NO_HTMLHELP */