/* -----------------------------------------------------------------------------

 timer.api ©1999 Dietmar Eilert

 API plug-in. This example demonstrates message port handling. Dice:

 DMAKE

 -------------------------------------------------------------------------------

*/

#include "defs.h"

/// "prototypes"

// library functions

Prototype LibCall struct APIClient *APIMountClient(__A0 struct APIMessage *, __A1 char *);
Prototype LibCall void              APICloseClient(__A0 struct APIClient *,  __A1 struct APIMessage *);
Prototype LibCall void              APIBriefClient(__A0 struct APIClient *,  __A1 struct APIMessage *);
Prototype LibCall void              APIFree       (__A0 struct APIClient  *, __A1 struct APIOrder   *);

// private functions

Prototype struct HostContext       *AddHost       (struct Task *);
Prototype void                      AddTimerEvent (struct HostContext *);
Prototype ULONG                     Dispatch      (struct APIClient *, struct APIMessage *);
Prototype struct HostContext       *FindContext   (struct Task *);
Prototype void                      RemoveHost    (struct HostContext *);

///
/// "globals"

struct List HostList;

///
/// "library functions"

LibCall struct APIClient *
APIMountClient(__A0 struct APIMessage *apiMsg, __A1 char *args)
{
    struct PlugInContext *plugInContext;

    if (plugInContext = (struct PlugInContext *)AllocVec(sizeof(struct PlugInContext), MEMF_CLEAR | MEMF_PUBLIC)) {

        struct HostContext *hostContext;
        struct Task        *task;

        task = FindTask(NULL);

        // find context of host task (or allocate new context if a new host starts using this plug-in)

        hostContext = FindContext(task);

        if (hostContext == NULL)

            hostContext = AddHost(task);

        if (hostContext) {

            // track number of plug-in instances used by host task

            ++hostContext->OpenCount;

            // build plug-in description for host

            plugInContext->APIClient.api_APIVersion = API_INTERFACE_VERSION;
            plugInContext->APIClient.api_Version    = 1;
            plugInContext->APIClient.api_Name       = "Timer";
            plugInContext->APIClient.api_Info       = "API example";
            plugInContext->APIClient.api_Commands   = NULL;
            plugInContext->APIClient.api_Serial     = 0;
            plugInContext->APIClient.api_Classes    = API_CLASS_SYSTEM;
            plugInContext->APIClient.api_Area       = NULL;
            plugInContext->APIClient.api_Signals    = 1L<<(hostContext->TimerPort->mp_SigBit);

            // remember host context for this instance

            plugInContext->HostContext = hostContext;
        }
        else {

            FreeVec(plugInContext);

            plugInContext = NULL;
        }
    }

    return((struct APIClient *)plugInContext);
}

LibCall void
APICloseClient(__A0 struct APIClient *handle, __A1 struct APIMessage *apiMsg)
{
    if (handle) {

        struct HostContext *context;

        // find host context for this plug-in instance

        context = ((struct PlugInContext *)handle)->HostContext;

        // check open count (closing last instance used by this task ?)

        if (context->OpenCount > 1) {

            --context->OpenCount;
        }
        else
            RemoveHost(context);

        FreeVec(handle);
    }
}

LibCall void
APIBriefClient(__A0 struct APIClient *handle, __A1 struct APIMessage *apiMsg)
{
    struct APIMessage *msg;

    // handle host's command notify

    for (msg = apiMsg; msg; msg = msg->api_Next) {

        if (msg->api_State == API_STATE_NOTIFY) {

            switch (msg->api_Class) {

                case API_CLASS_SYSTEM:

                    switch (msg->api_Action) {

                        case API_ACTION_SIGNAL:

                            {
                                struct HostContext *context;

                                // find host task context for this plug-in instance

                                if (context = ((struct PlugInContext *)handle)->HostContext) {

                                    ULONG signal = 1L<<(context->TimerPort->mp_SigBit);

                                    // is this our signal ?

                                    if (signal & (ULONG)apiMsg->api_Data) {

                                        struct timerequest *timerequest;

                                        // process event

                                        msg->api_Error = Dispatch(handle, apiMsg);

                                        // the first plug-in instance seeing this signal gets the actual message and reinitializes the timer)

                                        if (timerequest = (struct timerequest *)GetMsg(context->TimerPort)) {

                                            FreeVec(timerequest);

                                            // no pending timer requests for this host

                                            context->IOPending = NULL;

                                            // restart timer for this host

                                            AddTimerEvent(context);
                                        }
                                    }
                                }
                            }

                            break;

                        default:

                            msg->api_Error = API_ERROR_UNKNOWN;
                    }

                    break;

                default:

                    msg->api_Error = API_ERROR_UNKNOWN;
            }
        }
    }
}

LibCall void
APIFree(__A0 struct APIClient *handle, __A1 struct APIOrder *apiOrder)
{
    // no ressources to be freed
}

///
/// "private"

/* -------------------------------- FindContext --------------------------------

 Find context for this task. A "context" is a structure allocated for each host
 task (ie. each GoldED task) using this plug-in. It holds task-specific data.
 The concept of contexts is used because the resources used by this plug-in
 (specifically message ports) may only be allocated and used on a per-task
 basis undrer AmigaOS3.

*/

struct HostContext *
FindContext(task)

struct Task *task;
{
    struct HostContext *context;

    for (context = (struct HostContext *)HostList.lh_Head; context->Node.ln_Succ; context = (struct HostContext *)context->Node.ln_Succ)

        if (context->Task == task)

            return(context);

    return(NULL);
}


/* ---------------------------------- AddHost ----------------------------------

 Add host to list of registered hosts, allocate resources (e.g. one message
 port per host) and start the timer. Usually a plug-in is used by one host
 only but theoretically plug-ins can be used by multiple hosts simultaneously.

*/

struct HostContext *
AddHost(task)

struct Task *task;
{
    struct HostContext *context;

    if (context = (struct HostContext *)AllocVec(sizeof(struct HostContext), MEMF_CLEAR | MEMF_PUBLIC)) {

        context->Task = task;

        if (context->TimerPort = CreateMsgPort()) {

            if (context->TimeRequest = (struct timerequest *)CreateExtIO(context->TimerPort, sizeof(struct timerequest))) {

                if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)context->TimeRequest, 0) == 0) {

                    AddTail(&HostList, &context->Node);

                    // start timer (create initial timer request)

                    AddTimerEvent(context);

                    return(context);
                }

                DeleteExtIO((struct IORequest *)context->TimeRequest);
            }

            DeleteMsgPort(context->TimerPort);
        }

        FreeVec(context);
    }

    return(NULL);
}


/* -------------------------------- RemoveHost ---------------------------------

 Remove host from list of registered hosts (stop the timer for this host).

*/

void
RemoveHost(context)

struct HostContext *context;
{
    Remove(&context->Node);

    if (context->IOPending) {

        AbortIO((struct IORequest *)context->IOPending);

        WaitIO ((struct IORequest *)context->IOPending);

        FreeVec(context->IOPending);
    }

    if (context->TimeRequest) {

        CloseDevice((struct IORequest *)context->TimeRequest);

        DeleteExtIO((struct IORequest *)context->TimeRequest);
    }

    if (context->TimerPort) {

        struct Message *msg;

        while (msg = (struct Message *)GetMsg(context->TimerPort))

            ReplyMsg(msg);

        DeleteMsgPort(context->TimerPort);
    }

    FreeVec(context);
}


/* ----------------------------------- AddTimerEvent ---------------------------

 Send timer event (wait one second)

*/

void
AddTimerEvent(context)

struct HostContext *context;
{
    // no timer event pending ?

    if (context->IOPending == NULL) {

        if (context->IOPending = (struct timerequest *)AllocVec(sizeof(struct timerequest), MEMF_ANY | MEMF_PUBLIC)) {

            // initialize IORequest

            *context->IOPending = *context->TimeRequest;

            // wait for one second

            context->IOPending->tr_node.io_Command = TR_ADDREQUEST;
            context->IOPending->tr_time.tv_secs    = 1;

            SendIO((struct IORequest *)context->IOPending);
        }
    }
}


/* --------------------------------- Dispatch ----------------------------------

 Dispatch timer even. We do NOT consume this event (by setting apiMsg->api_State
 to API_STATE_CONSUMED) so that other instances of this plug-in using the same
 timer can see the event, too.

*/

ULONG
Dispatch(handle, apiMsg)

struct APIClient  *handle;
struct APIMessage *apiMsg;
{
    static UBYTE *quotes[] = {

        "Cheer up, Brian. You know what they say."
        "Some things in life are bad,",
        "They can really make you mad.",
        "Other things just make you swear and curse.",
        "When you're chewing on life's grissle,",
        "Don't grumble, give a whistle.",
        "And this'll help things turn out for the best,"
        "And...",
        "Always look on the bright side of life",
        "Always look on the bright side of life",
        NULL
    };

    static UWORD next = 0;

    // set status text

    apiMsg->api_Status = quotes[next];

    // activate next string

    if (quotes[++next] == NULL)

        next = 0;

    return(API_ERROR_OK);
}


///
