/*
    Copyright (C) 1995-2026, The AROS Development Team. All rights reserved.

    Desc: console.device function CDInputHandler()
*/

#include <proto/exec.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <proto/console.h>
#include <proto/intuition.h>
#include <intuition/intuitionbase.h>

#include <aros/asmcall.h>

#include <devices/inputevent.h>

#include "console_gcc.h"

#define DEBUG 0
#include <aros/debug.h>

/* protos */
static Object *obtainconunit(struct Window *window,
    struct ConsoleBase *ConsoleDevice);
static VOID releaseconunit(Object *o, struct ConsoleBase *ConsoleDevice);

/*************************************************************************

    NAME */
        AROS_LH2I(struct InputEvent *, CDInputHandler,

/*  SYNOPSIS */
        AROS_LHA(struct InputEvent *, events, A0),
        AROS_LHA(APTR, consoleDevice, A1),

/*  LOCATION */
        struct Library *, ConsoleDevice, 7, Console)

/*  FUNCTION

    INPUTS

    RESULT

    NOTES

    EXAMPLE

    BUGS

    SEE ALSO

    INTERNALS

*****************************************************************************/
{
    AROS_LIBFUNC_INIT
#undef ConsoleDevice
    struct ConsoleBase *ConsoleDevice = (struct ConsoleBase *)consoleDevice;

    struct InputEvent *ie;

    struct cdihData *cdihdata = (struct cdihData *)&ConsoleDevice->consIHData;

    D(bug("CDInputHandler(events=%p, cdihdata=%p)\n", events, cdihdata));

    for (ie = events; ie; ie = ie->ie_NextEvent)
    {
        /* A rawkey event ? */
        if ((ie->ie_Class == IECLASS_RAWKEY
                && !(ie->ie_Code & IECODE_UP_PREFIX))
            || (ie->ie_Class == IECLASS_SIZEWINDOW)
            || (ie->ie_Class == IECLASS_CLOSEWINDOW)
            || (ie->ie_Class == IECLASS_REFRESHWINDOW)
            || (ie->ie_Class == IECLASS_GADGETDOWN)
            || (ie->ie_Class == IECLASS_GADGETUP)
            || (ie->ie_Class == IECLASS_MENULIST)
            || (ie->ie_Class == IECLASS_RAWMOUSE)
            || (ie->ie_Class == IECLASS_ACTIVEWINDOW)
            || (ie->ie_Class == IECLASS_INACTIVEWINDOW)
            || (ie->ie_Class == IECLASS_TIMER))
        {
            /* What console do we send it to ? */
            Object *unit;

            D(bug("Got some event\n"));
            /* find and prevent deletion of unit */
            unit = obtainconunit(
                (ie->ie_Class == IECLASS_ACTIVEWINDOW ||
                 ie->ie_Class == IECLASS_INACTIVEWINDOW) ?
                    (struct Window *)ie->ie_EventAddress : NULL,
                ConsoleDevice);
            if (unit)
            {
                /* Hand the event to the console task asynchronously: one
                   message per event, freed by the task after processing.
                   NEVER wait for the console task here. This handler runs
                   in the input.device task, and the console task renders
                   (RectFill obtains the window's layer lock). During an
                   interactive size/drag intuition holds LockLayers() across
                   many input events, so waiting for the console task while
                   it waits for the layer lock deadlocks all input. Under
                   memory pressure the event is dropped instead. */
                struct cdihMessage *message =
                    AllocMem(sizeof(struct cdihMessage),
                    MEMF_PUBLIC | MEMF_CLEAR);

                D(bug("Event should be passed to unit %p\n", unit));

                if (message)
                {
                    message->msg.mn_Length = sizeof(struct cdihMessage);
                    message->unit = unit;
                    message->ie = *ie;
                    PutMsg(cdihdata->inputPort, (struct Message *)message);
                }

                /* deletion of unit is now allowed */
                releaseconunit(unit, ConsoleDevice);

            } /* if (RAWKEY event was meant for a console window) */
        }
        else
        {
            D(bug("Ignoring event of ie_Class %d\n", ie->ie_Class));
        }
    } /* for (each event in the chain) */

    ReturnPtr("CDInputHandler", struct InputEvent *, events);

    AROS_LIBFUNC_EXIT
} /* CDInputHandler */

#undef IntuitionBase

/***********************
**  Support funtions  **
***********************/

/* Obtains a conunit object, and locks it, so that it's
   not deleted while we work on it
*/

static Object *obtainconunit(struct Window *window,
    struct ConsoleBase *ConsoleDevice)
{
    struct IntuitionBase *IntuitionBase =
        (APTR) ConsoleDevice->cb_IntuitionBase;
    struct Window *activewin;
    Object *o, *ostate;
    ULONG lock;
    struct Node *node;

    D(bug("obtainconunit()\n"));

    ForeachNode(&ConsoleDevice->unitList, node)
    {
        D(bug("Node: %p\n", node));
    }

    /* Lock the console list */
    ObtainSemaphoreShared(&ConsoleDevice->unitListLock);

    /* Activation events name the affected window explicitly.  This matters
     * for INACTIVEWINDOW, because ActiveWindow already points at the newly
     * activated window by the time the event is delivered. */
    if (window)
        activewin = window;
    else
    {
        D(bug("Obtaining IBase\n"));
        lock = LockIBase(0UL);
        activewin = IntuitionBase->ActiveWindow;
        UnlockIBase(lock);
    }
    D(bug("Released IBase, active win=%p\n", activewin));

    /* Try to find the correct unit object for taht window */
    ostate = (Object *) ConsoleDevice->unitList.mlh_Head;

    D(bug("Searching for con unit\n"));
    while ((o = NextObject(&ostate)))
    {
        D(bug("Trying unit %p, win=%p\n", o, CU(o)->cu_Window));
        /* Is this console the currently active window ? */
        if (CU(o)->cu_Window == activewin)
        {
            D(bug("Unit found: %p\n", o));
            /* Delay deltion of this console object */
            ICU(o)->conFlags |= CF_DELAYEDDISPOSE;
            break;
        }
    }

    /* Unlock the console list */
    ReleaseSemaphore(&ConsoleDevice->unitListLock);

    ReturnPtr("obtainconunit", Object *, o);
}

static VOID releaseconunit(Object *o, struct ConsoleBase *ConsoleDevice)
{
    struct IntuitionBase *IntuitionBase =
        (APTR) ConsoleDevice->cb_IntuitionBase;

    /* Lock all units */
    ObtainSemaphore(&ConsoleDevice->unitListLock);

    /* Needn't prevent the unit from being disposed anymore */
    ICU(o)->conFlags &= ~CF_DELAYEDDISPOSE;

    /* If unit is scheduled for deletion, then delete it */
    if (ICU(o)->conFlags & CF_DISPOSE)
    {
        ULONG mID = OM_REMOVE;

        /* Remove from list */
        DoMethodA(o, (Msg) &mID);

        /* Delete it */
        DisposeObject(o);
    }

    ReleaseSemaphore(&ConsoleDevice->unitListLock);
}

/****************
** initCDIH()  **
****************/
/* This function should be executed on the console.device task's context only,
   so that the inputport is set correctly
*/

struct Interrupt *initCDIH(struct ConsoleBase *ConsoleDevice)
{
    struct Interrupt *cdihandler;
    struct cdihData *cdihdata;

    D(bug("initCDIH(ConsoleDevice=%p)\n", ConsoleDevice));

    cdihandler =
        AllocMem(sizeof(struct Interrupt), MEMF_PUBLIC | MEMF_CLEAR);
    if (cdihandler)
    {
        cdihdata = &ConsoleDevice->consIHData;
        cdihdata->inputPort = CreateMsgPort();
        if (cdihdata->inputPort)
        {
            /* Initialize Interrupt struct */
            cdihandler->is_Code =
                (VOID_FUNC) AROS_SLIB_ENTRY(CDInputHandler, Console, 7);
            cdihandler->is_Data = ConsoleDevice;
            /* Intuition runs at priority 50 and leaves the original event
             * available to lower-priority handlers.  Run afterwards so its
             * ActiveWindow is current before obtainconunit() routes the event,
             * matching the classic console.device input chain. */
            cdihandler->is_Node.ln_Pri = 0;
            cdihandler->is_Node.ln_Name = "console.device InputHandler";

            ReturnPtr("initCDIH", struct Interrupt *, cdihandler);
        }
        FreeMem(cdihandler, sizeof(struct Interrupt));
    }
    ReturnPtr("initCDIH", struct Interrupt *, NULL);
}

/****************
** CleanupIIH  **
****************/

VOID cleanupCDIH(struct Interrupt *cdihandler,
    struct ConsoleBase *ConsoleDevice)
{
    struct cdihData *cdihdata;

    cdihdata = &ConsoleDevice->consIHData;

    /* Drop any events still queued for the console task */
    {
        struct Message *msg;
        while ((msg = GetMsg(cdihdata->inputPort)))
            FreeMem(msg, sizeof(struct cdihMessage));
    }
    DeleteMsgPort(cdihdata->inputPort);

    FreeMem(cdihandler, sizeof(struct Interrupt));

    return;
}
