/*
 * zSyncQueue.c
 * 
 * The Zen API 
 * Copyright (C) 2008-2010 by LMI Technologies Inc.
 * 
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */ 
#include <z/zzSyncQueue.h>
#include <string.h>

ZAPI_EXPORT(zStatus) zSyncQueue_Construct(zSyncQueue* queue, zUIntPtr itemSize, z32u capacity)
{
    zSyncQueue output = zNIL; 
    zSyncQueueClass* obj = zNULL; 
    zStatus exception; 

    zCHECK(zCalloc(&output, 1, sizeof(zSyncQueueClass))); 
    obj = zHANDLE_POINTER(output); 

    zEXCEPT_TRY
    {
        zEXCEPT_CHECK(zMalloc(&obj->buffer, itemSize*capacity)); 
        zEXCEPT_CHECK(zSemaphore_Construct(&obj->canRemove, 0)); 
        zEXCEPT_CHECK(zLock_Construct(&obj->lock)); 

        obj->itemSize = itemSize;         
        obj->capacity = capacity; 

        *queue = output; 
    }
    zEXCEPT_CATCH(&exception)
    {
        zSemaphore_Destroy(obj->canRemove);  
        zLock_Destroy(obj->lock);  
        zFree(obj->buffer); 
        zFree(obj); 

        zEXCEPT_SET(exception); 
    }
    zEXCEPT_FINALLY
    {
        zEXCEPT_CLOSE(); 
    }

    return zOK; 
}

ZAPI_EXPORT(zStatus) zSyncQueue_Destroy(zSyncQueue queue)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 

    if (!zIS_NULL(obj))
    {
        zCHECK(zSemaphore_Destroy(obj->canRemove)); 
        zCHECK(zLock_Destroy(obj->lock)); 
        zCHECK(zFree(obj->buffer)); 
        zCHECK(zFree(obj)); 
    }

    return zOK; 
}

ZAPI_EXPORT(zStatus) zSyncQueue_SetDropHandler(zSyncQueue queue, zSyncQueueDropCallback onDrop, zHandle context)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 

    zCHECK(zLock_Enter(obj->lock)); 
    obj->dropCallback = onDrop; 
    obj->dropCallbackContext = context; 
    zCHECK(zLock_Exit(obj->lock)); 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zSyncQueue_Add(zSyncQueue queue, const void* item)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 
    zByte* buffer;
    z32u tail; 
    z32u head; 
    zBool added = zFALSE; 
    zStatus dropStatus = zOK; 

    zCHECK(zLock_Enter(obj->lock)); 

    if (obj->count != obj->capacity)
    {
        buffer = obj->buffer; 
        tail = (obj->head + obj->count) % obj->capacity; 
        memcpy(&buffer[tail*obj->itemSize], item, obj->itemSize); 
        obj->count++; 
        added = zTRUE; 
    }
    else
    {
        buffer = obj->buffer; 
        head = (obj->head) % obj->capacity; 

        if (obj->dropCallback)
        {
            dropStatus = obj->dropCallback(obj->dropCallbackContext, queue, &buffer[head*obj->itemSize]); 
        }

        if (zSUCCESS(dropStatus))
        {
            obj->dropCount++; 
            obj->head = (obj->head + 1) % obj->capacity; 
            tail = (obj->head + obj->count-1) % obj->capacity; 
            memcpy(&buffer[tail*obj->itemSize], item, obj->itemSize); 
        }
    }

    zCHECK(zLock_Exit(obj->lock)); 

    if (added)
    {
        zCHECK(zSemaphore_Post(obj->canRemove)); 
    }

    return dropStatus; 
}

ZAPI_EXPORT(zStatus) zSyncQueue_Remove(zSyncQueue queue, void* item, z64u timeout)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 
    zByte* buffer; 

    zCHECK(zSemaphore_Wait(obj->canRemove, timeout)); 

    zCHECK(zLock_Enter(obj->lock)); 

    buffer = obj->buffer; 
    memcpy(item, &buffer[obj->head*obj->itemSize], obj->itemSize); 
    obj->head = (obj->head + 1) % obj->capacity; 
    obj->count--; 
    
    zCHECK(zLock_Exit(obj->lock)); 

    return zOK; 
} 

ZAPI_EXPORT(zStatus) zSyncQueue_Item(zSyncQueue queue, z32u index, void* item)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 
    zStatus status = zOK; 
    zByte* buffer; 

    zCHECK(zLock_Enter(obj->lock)); 

    buffer = obj->buffer; 
    if (index >= obj->count)
    {
        status = zERROR_PARAMETER; 
    }
    else
    {
        memcpy(item, &buffer[((obj->head + index) % obj->capacity)*obj->itemSize], obj->itemSize); 
    }

    zCHECK(zLock_Exit(obj->lock)); 

    return status; 
}

ZAPI_EXPORT(zUIntPtr) zSyncQueue_ItemSize(zSyncQueue queue)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 
    zUIntPtr value; 

    zCHECK(zLock_Enter(obj->lock)); 
    value = obj->itemSize; 
    zCHECK(zLock_Exit(obj->lock)); 

    return value; 
}

ZAPI_EXPORT(z32u) zSyncQueue_Count(zSyncQueue queue)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 
    z32u value; 

    zCHECK(zLock_Enter(obj->lock)); 
    value = obj->count; 
    zCHECK(zLock_Exit(obj->lock)); 

    return value; 
}

ZAPI_EXPORT(z32u) zSyncQueue_Capacity(zSyncQueue queue)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 
    z32u value; 

    zCHECK(zLock_Enter(obj->lock)); 
    value = obj->capacity; 
    zCHECK(zLock_Exit(obj->lock)); 

    return value; 
}

ZAPI_EXPORT(z64u) zSyncQueue_DropCount(zSyncQueue queue)
{
    zSyncQueueClass* obj = zHANDLE_POINTER(queue); 
    z64u value; 

    zCHECK(zLock_Enter(obj->lock)); 
    value = obj->dropCount; 
    zCHECK(zLock_Exit(obj->lock)); 

    return value; 
}
