/* $Id: $ */
/** @file
 *
 * spy1 - Hook DLL.
 *
 * Copyright (c) 2006 knut st. osmundsen <bird@anduin.net>
 *
 * GPL
 *
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define INCL_PM
#define INCL_BASE
#define INCL_ERRORS
#include <os2.h>
#include "spy1.h"


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** This dll. */
HMODULE     g_hmod = NULLHANDLE;
/** Consumer. */
HEV         g_hevConsumer = NULLHANDLE;

/** The event 'buffer'.
 * This is worked with two indexes, one for the producer (the hooks) and one
 * for the consumer (GetEvent).
 * When g_iConsumer equals g_iProducer, there are no events in the buffer. */
SPY1EVENT volatile  g_aEvents[1024];
/** The consumer index. */
unsigned volatile   g_iConsumer;
/** The producer index. */
unsigned volatile   g_iProducer;


/**
 * The DLL InitTerm function.
 */
ULONG EXPENTRY _DLL_InitTerm(HMODULE hmod, ULONG ulReason)
{
    HEV hev = g_hevConsumer;
    switch (ulReason) {
        case 0:
            if (hev != NULLHANDLE) {
                /* instance init */
                DosOpenEventSem(NULL, &hev);
                if (g_hmod != hmod) {
                    g_hmod = hmod;
                }
            } else {
                /* global init - can serialize this 100% if it becomes a problem. */
                if (DosCreateEventSem(NULL, &hev, DC_SEM_SHARED, FALSE) != NO_ERROR) {
                    DosBeep(110, 200);
                    return FALSE;
                }
                g_hevConsumer = hev;
                g_hmod = hmod;
                g_iConsumer = 0;
                g_iProducer = 0;
            }
            break;
        case 1:
            if (hev != NULLHANDLE) {
                DosCloseEventSem(hev);
            }
            break;
        default:
            return FALSE;
    }
    return TRUE;
}


/**
 * Sets the hooks.
 *
 * @returns PM error on failure. NO_ERROR on success.
 * @param   hab     The anchor block of the current thread.
 */
int EXPENTRY SetHooks(HAB hab)
{
    int rc;
    if (WinSetHook(hab, NULLHANDLE, HK_INPUT, (PFN)InputHook, g_hmod)) {
        if (WinSetHook(hab, NULLHANDLE, HK_JOURNALRECORD, (PFN)JournalRecordHook, g_hmod)) {
            return NO_ERROR;
        }

        /* bail out */
        rc = WinGetLastError(hab);
        WinReleaseHook(hab, NULLHANDLE, HK_INPUT, (PFN)InputHook, g_hmod);
    } else {
        rc = WinGetLastError(hab);
    }
    return rc;
}


/**
 * Releases the hooks.
 *
 * @returns PM error on failure. NO_ERROR on success.
 * @param   hab     The anchor block of the current thread.
 */
int EXPENTRY ReleaseHooks(HAB hab)
{
    int rc = NO_ERROR;
    if (!WinReleaseHook(hab, NULLHANDLE, HK_INPUT, (PFN)InputHook, g_hmod)) {
        rc = WinGetLastError(hab);
    }
    if (!WinReleaseHook(hab, NULLHANDLE, HK_JOURNALRECORD, (PFN)JournalRecordHook, g_hmod)) {
        rc = WinGetLastError(hab);
    }
    return rc;
}


/**
 * Gets a message.
 *
 * @returns DosWaitEventSem return code.
 * @param   pEvent          Where to store the event.
 * @param   ulTimeout       How many milliseconds to wait.
 */
int EXPENTRY GetEvent(PSPY1EVENT pEvent, ULONG ulTimeout)
{
    /*
     * Wait for an event to become ready.
     */
    unsigned iCur = g_iConsumer;
    if (iCur == g_iProducer) {
        do {
            ULONG cPosts;
            APIRET rc = DosResetEventSem(g_hevConsumer, &cPosts);
            if (rc != NO_ERROR && rc != ERROR_ALREADY_RESET) {
                return rc;
            }
            if (iCur != g_iProducer) {
                break;
            }
            rc = DosWaitEventSem(g_hevConsumer, ulTimeout);
            if (rc) {
                return rc;
            }
            iCur = g_iConsumer;
        } while (iCur == g_iProducer);
    }

    /*
     * Reap the event.
     */
    *pEvent = g_aEvents[iCur];
    const unsigned iNext = (iCur + 1) % (sizeof(g_aEvents) / sizeof(g_aEvents[0]));
    if (g_iConsumer == iCur) {
        g_iConsumer = iNext;
    }
    return NO_ERROR;
}


/**
 * Record a message.
 */
void RecordMsg(ULONG ulHook, const QMSG *pQmsg, BOOL fRemoved)
{
    const unsigned iCur = g_iProducer;
    const unsigned iNext = (iCur + 1) % (sizeof(g_aEvents) / sizeof(g_aEvents[0]));
    if (iNext != g_iConsumer) {
        PTIB pTib;
        PPIB pPib;

        /* fill in the message data. */
        g_aEvents[iCur].enmType = fRemoved ? SPY1EVENTTYPE_MSG_REMOVED : SPY1EVENTTYPE_MSG_PEEK;
        DosGetInfoBlocks(&pTib, &pPib);
        g_aEvents[iCur].pid = pPib->pib_ulpid;
        g_aEvents[iCur].tid = pTib->tib_ptib2->tib2_ultid;
        g_aEvents[iCur].ulHook = ulHook;
        g_aEvents[iCur].u.Msg = *pQmsg;

        /* commit the message. */
        if (iCur == g_iProducer) {
            g_iProducer = iNext;///@todo atomic compare and exchange!
            DosPostEventSem(g_hevConsumer);
        }
    } else {
        /* the buffer is full */
        DosPostEventSem(g_hevConsumer);
        DosSleep(0);
    }
}


/**
 * The input hook procedure (any context).
 *
 * @returns Processed indicator; TRUE if processed and should be discarded, FALSE if should be passed on.
 *
 * @param   hab     The anchor block of the current thread.
 * @param   pQmsg   The message structure containing the message we're called for.
 * @param   fs      The removal options (PM_REMOVE / PM_NOREMOVE).
 */
BOOL EXPENTRY InputHook(HAB hab, PQMSG pQmsg, ULONG fs)
{
    //if (fs == PM_REMOVE) {
        RecordMsg(HK_INPUT, pQmsg, fs == PM_REMOVE);
    //}
    return FALSE;
}


/**
 * The journal record hook procedure (any context).
 *
 * @param   hab     The anchor block of the current thread.
 * @param   pQmsg   The message structure containing the message we're called for.
 */
VOID EXPENTRY JournalRecordHook(HAB hab, PQMSG pQmsg)
{
    RecordMsg(HK_JOURNALRECORD, pQmsg, TRUE);
}

