/** 
 * @file    kEvent.cpp
 *
 * @internal
 * Copyright (C) 2012-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */
#include <kApi/Utils/kEvent.h>
#include <kApi/Data/kList.h>

kBeginClassEx(k, kEvent)
    kAddPrivateVMethod(kEvent, kObject, VRelease)
    kAddPrivateVMethod(kEvent, kObject, VInitClone)
kEndClassEx()

kFx(kStatus) kEvent_Construct(kEvent* evnt, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status; 

    kCheck(kAlloc_GetObject(alloc, kTypeOf(kEvent), evnt)); 

    if (!kSuccess(status = xkEvent_Init(*evnt, kTypeOf(kEvent), alloc)))
    {
        kAlloc_FreeRef(alloc, evnt); 
    }

    return status; 
}

kFx(kStatus) xkEvent_Init(kEvent evnt, kType type, kAlloc allocator)
{
    kObjR(kEvent, evnt); 
    kStatus status = kOK; 

    kCheck(kObject_Init(evnt, type, allocator)); 

    obj->listeners = kNULL;
    obj->notifyIt = kNULL;

    kTry
    {
        kTest(kList_Construct(&obj->listeners, kTypeOf(kCallback), 0, allocator)); 
    }
    kCatch(&status); 
    {
        xkEvent_VRelease(evnt); 
        kEndCatch(status); 
    }

    return kOK; 
}

kFx(kStatus) xkEvent_VInitClone(kEvent evnt, kEvent source, kAlloc allocator)
{
    kObjR(kEvent, evnt); 
    kStatus status = kOK; 

    kCheck(xkEvent_Init(evnt, kObject_Type(source), allocator)); 

    kTry
    {
        kTest(kList_Assign(obj->listeners, kEvent_Listeners(source))); 
    }
    kCatch(&status); 
    {
        xkEvent_VRelease(evnt); 
        kEndCatch(status); 
    }

    return kOK; 
}

kFx(kStatus) xkEvent_VRelease(kEvent evnt)
{
    kObj(kEvent, evnt); 

    kCheck(kObject_Destroy(obj->listeners)); 

    kCheck(kObject_VRelease(evnt)); 

    return kOK; 
}

kFx(kStatus) kEvent_Add(kEvent evnt, kCallbackFx function, kPointer receiver)
{
    kObj(kEvent, evnt); 
    kCallback entry; 

    entry.function = function; 
    entry.receiver = receiver; 

    kCheck(kList_AddT(obj->listeners, &entry, kNULL)); 

    return kOK; 
}

kFx(kStatus) kEvent_Remove(kEvent evnt, kCallbackFx function, kPointer receiver)
{
    kObj(kEvent, evnt); 
    kListItem it = kList_First(obj->listeners); 

    while (!kIsNull(it))
    {
        kCallback* entry = kList_AtT(obj->listeners, it, kCallback); 
        
        if ((entry->function == function) && (entry->receiver == receiver))
        {
            //adjust notification iterator, if necessary
            if (it == obj->notifyIt)
            {
                obj->notifyIt = kList_Next(obj->listeners, obj->notifyIt); 
            }

            return kList_Remove(obj->listeners, it); 
        }

        it = kList_Next(obj->listeners, it); 
    }

    return kERROR_NOT_FOUND; 
}

kFx(kStatus) kEvent_Clear(kEvent evnt)
{
    kObj(kEvent, evnt); 

    kCheck(kList_Clear(obj->listeners)); 
    obj->notifyIt = kNULL; 

    return kOK; 
}

kFx(kStatus) kEvent_Notify(kEvent evnt, kPointer sender, void* args)
{
    kObj(kEvent, evnt); 
    kStatus status = kOK; 
    kStatus callStatus; 

    //break notification cycles
    if (!kIsNull(obj->notifyIt))
    {
        return kOK; 
    }

    //share the event, to prevent self-destruction during notification callbacks
    kCheck(kObject_Share(evnt)); 

    obj->notifyIt = kList_First(obj->listeners); 

    while (!kIsNull(obj->notifyIt))
    {
        kCallback entry = kList_AsT(obj->listeners, obj->notifyIt, kCallback); 

        obj->notifyIt = kList_Next(obj->listeners, obj->notifyIt); 

        callStatus = entry.function(entry.receiver, sender, args); 
        status = kSuccess(status) ? callStatus : status; 
    }

    obj->notifyIt = kNULL; 
    kObject_Destroy(evnt); 

    return status; 
}



