/* ------------------------------------------------------------------
 * Copyright (C) 1998-2010 PacketVideo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 * -------------------------------------------------------------------
 */

#include "threadsafe_callback_ao.h"
#include "pvlogger.h"
#include "oscl_procstatus.h"

#include "oscl_dll.h"

OSCL_DLL_ENTRY_POINT_DEFAULT()


/////////////////////////////////////////////////////////////////////////////
// Add AO to the scheduler
/////////////////////////////////////////////////////////////////////////////
OSCL_EXPORT_REF void ThreadSafeCallbackAO::ThreadLogon()
{
    if (!IsAdded())
    {
        AddToScheduler();
    }

    iLogger = PVLogger::GetLoggerObject(iLoggerString);
}
////////////////////////////////////////////////////////////////////////////
// Remove AO from the scheduler
////////////////////////////////////////////////////////////////////////////
OSCL_EXPORT_REF void ThreadSafeCallbackAO::ThreadLogoff()
{
    //thread logoff
    if (IsAdded())
    {
        RemoveFromScheduler();
    }

    iLogger = NULL;
}
///////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR
///////////////////////////////////////////////////////////////////////////
OSCL_EXPORT_REF ThreadSafeCallbackAO::ThreadSafeCallbackAO(void *aObserver, uint32 aDepth, const char *aAOname, int32 aPriority)
        : OsclActiveObject(aPriority, aAOname),
        iLogger(NULL)
{


    OsclReturnCode queue_init_status = OsclSuccess;
    OsclProcStatus::eOsclProcError mutex_init_status = OsclProcStatus::SUCCESS_ERROR;
    OsclProcStatus::eOsclProcError sema_init_status = OsclProcStatus::SUCCESS_ERROR;
    int32 err = 0;

    iLoggerString = aAOname;
    iObserver = aObserver;
    Q = NULL;

    OSCL_TRY(err,
             queue_init_status = QueueInit(aDepth); //create the Q
             mutex_init_status = Mutex.Create(); // Create Mutex
             sema_init_status  = RemoteThreadCtrlSema.Create(aDepth);

             ThreadLogon(); // add to scheduler
            );

    if ((err != 0) ||
            (queue_init_status != OsclSuccess) ||
            (mutex_init_status != OsclProcStatus::SUCCESS_ERROR) ||
            (sema_init_status  != OsclProcStatus::SUCCESS_ERROR)
       )
    {
        OSCL_LEAVE(OsclFailure);
    }


    PendForExec(); // make sure to "prime" the callback AO for the first event that arrives

}

/////////////////////////////////////////////////////////////////////////////
// Destructor
/////////////////////////////////////////////////////////////////////////////
OSCL_EXPORT_REF ThreadSafeCallbackAO::~ThreadSafeCallbackAO()
{
    OsclReturnCode queue_deinit_status = OsclSuccess;
    OsclProcStatus::eOsclProcError mutex_close_status = OsclProcStatus::SUCCESS_ERROR;
    OsclProcStatus::eOsclProcError sema_close_status = OsclProcStatus::SUCCESS_ERROR;

    int32 err = 0;

    OSCL_TRY(err,
             queue_deinit_status = QueueDeInit(); // Destroy the queue
             mutex_close_status =  Mutex.Close(); // close the mutex
             sema_close_status  =  RemoteThreadCtrlSema.Close(); // close the semaphore
             ThreadLogoff();
            );

    if ((err != 0) ||
            (queue_deinit_status != OsclSuccess) ||
            (mutex_close_status != OsclProcStatus::SUCCESS_ERROR) ||
            (sema_close_status != OsclProcStatus::SUCCESS_ERROR))
    {
        OSCL_LEAVE(OsclFailure);
    }

    iObserver = NULL;
}

/////////////////////////////////////////////////////////////////////////////
// Run Routine
/////////////////////////////////////////////////////////////////////////////
OSCL_EXPORT_REF void ThreadSafeCallbackAO::Run()
{
    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "ThreadSafeCallbackAO::Run() In"));


    OsclAny *P; // parameter to dequeue
    OsclReturnCode status;


    // get the event parameters and status of de-queuing
    P = DeQueue(status);


    if (status == OsclSuccess)
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "ThreadSafeCallbackAO::Run() - Calling Process Event"));
        ProcessEvent(P);
    }
    else
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "ThreadSafeCallbackAO::Run() - could not dequeue event data"));
    }



    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "ThreadSafeCallbackAO::Run() Out"));
}


/////////////////////////////////////////////////////////////////////////////////////
//////// ReceiveEvent
/////////////////////////////////////////////////////////////////////////////////////
/////// NOTE: THIS METHOD IS EXECUTED IN THE REMOTE THREAD CONTEXT
//////////////AS PART OF THE CALLBACK. IT QUEUES THE EVENT PARAMETERS !!!!!!!!!!!!
/////////////////////////////////////////////////////////////////////////////////////
///////////// GENERIC API requires only one parameter (osclany pointer to "event data")
///////////////
////////////// BEFORE CALLING THE GENERIC API, YOU MUST SETUP (COPY IF NECESSARY)
///////////////// CALLBACK EVENT PARAMETERS AND PROVIDE An OSCLANY POINTER TO THEM
/////////////////////////////////////////////////////////////////////////////////////
///////////////YOU MUST NOT USE LOGGER IN THE CALLBACK (IN THE REMOTE THREAD CONTEXT)
///////////////
//////////////// EXAMPLE CODE (For CALLING THE GENERIC API):

// typedef struct event_data{
//      int d1;
//      char *d2;
//      uint32 d3;
//      } event_data;

//int ActualCallbackAPI(int p1, char * p2, uint32 p3)
//{
//      // pack all parameters
//      event_data *pED = malloc(sizeof(event_data));
//      pED->d1 = p1;
//      pED->d2 = p2;
//      pED->d3 = p3;
//      OsclAny *P = (OsclAny *) pED:
//          // call the generic callback API:
//      PVMFStatus stat = ReceiveEvent(P);
//      // check the status
//      if(stat == PVMFSuccess)
//          return OK;
//      else
//          return ERROR;
//}
//



OSCL_EXPORT_REF OsclReturnCode ThreadSafeCallbackAO::ReceiveEvent(OsclAny *EventData)
{

    OsclReturnCode status;
    OsclProcStatus::eOsclProcError sema_status;

    // Wait on the remote thread control semaphore. If the queue is full, must block and wait
    // for the AO to dequeue some previous event. If the queue is not full, proceed
    sema_status = RemoteThreadCtrlSema.Wait();
    if (sema_status != OsclProcStatus::SUCCESS_ERROR)
        return OsclFailure;


    status = Queue(EventData);

    return status;
}

OSCL_EXPORT_REF OsclReturnCode ThreadSafeCallbackAO::ProcessEvent(OsclAny *EventData)
{
    OSCL_UNUSED_ARG(EventData);
// DO NOTHING. OVERLOAD THIS METHOD IN THE DERIVED CLASS TO DO SOMETHING MEANINGFUL
    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "ThreadSafeCallbackAO::ProcessEvent() In and Out"));
    return OsclSuccess;
}
///////////////////////////////////////////////////////////////////////////////
//// QUEUE OPERATIONS
///////////////////////////////////////////////////////////////////////////////


/////CREATE QUEUE AND INIT ///////////////////

OSCL_EXPORT_REF OsclReturnCode ThreadSafeCallbackAO::QueueInit(uint32 aMaxQueueDepth)
{

    if (aMaxQueueDepth < 1)
    {
        Q = NULL;
        return OsclErrArgument;
    }

    // create Q as simple array
    Q = (QueueT *) oscl_malloc(sizeof(QueueT));
    if (Q == NULL)
    {
        return OsclErrNoMemory;
    }

    Q->MaxNumElements = aMaxQueueDepth;
    Q->NumElem = 0;
    Q->index_in = 0;
    Q->index_out = 0;
    Q->pFirst = NULL;
    // create the Q elements
    Q->pFirst = (QElement *) oscl_malloc((Q->MaxNumElements) * oscl_mem_aligned_size(sizeof(QElement)));
    if (Q->pFirst == NULL)
    {
        Q = NULL;
        return OsclErrNoMemory;
    }

    // zero out the Q
    oscl_memset(Q->pFirst, 0, (Q->MaxNumElements) * oscl_mem_aligned_size(sizeof(QElement)));


    return OsclSuccess;
}

///////////// DESTROY QUEUE ///////////////////////
OSCL_EXPORT_REF OsclReturnCode ThreadSafeCallbackAO::QueueDeInit()
{
    OsclReturnCode status = OsclSuccess;

    // free the Q elements
    if (Q->pFirst != NULL)
        oscl_free(Q->pFirst);

    Q->pFirst = NULL;
    // free the Q structure
    if (Q != NULL)
        oscl_free(Q);

    Q = NULL;

    return status;
}

/////////////QUEUE ONE ELEMENT //////////////////////
OSCL_EXPORT_REF OsclReturnCode ThreadSafeCallbackAO::Queue(OsclAny *pData)
{


    // NOTE: assumption is made that a semaphore (external to this method) will prevent
    // queueing an event if the queue is already full

    // protect queue access
    Mutex.Lock();

    if (Q->NumElem >= Q->MaxNumElements)
    {
        Mutex.Unlock();
        RemoteThreadCtrlSema.Signal(); // signal the sema to maintain sema count consistency in case
        // of error (inability to queue)
        return OsclFailure;
    }

    (Q->pFirst[Q->index_in]).pData = pData;

    Q->index_in++;
    // roll-over the index to 0 if it reaches the end.
    if (Q->index_in == Q->MaxNumElements)
        Q->index_in = 0;

    Q->NumElem++;

    // check if AO needs to be scheduled (i.e. check if this is the first event the queue after the queue was empty)
    if (GetQueueNumElem() == 1)
    {
        PendComplete(OSCL_REQUEST_ERR_NONE);
    }

    Mutex.Unlock();

    return OsclSuccess;
}

///////////DE-QUEUE ONE ELEMENT /////////////////////
OSCL_EXPORT_REF OsclAny* ThreadSafeCallbackAO::DeQueue(OsclReturnCode &stat)
{
    OsclAny *pData;
    OsclProcStatus::eOsclProcError sema_status;

    stat = OsclSuccess;

    // Protect the queue while accessing it:
    Mutex.Lock();

    if (Q->NumElem == 0)
    {
        // nothing to de-queue
        stat = OsclFailure;
        Mutex.Unlock();

        return NULL;
    }

    pData = (Q->pFirst[Q->index_out]).pData;

    Q->index_out++;
    // roll-over the index
    if (Q->index_out == Q->MaxNumElements)
        Q->index_out = 0;

    Q->NumElem--;

    // check if there is need to reschedule
    if ((Q->NumElem) > 0)
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "ThreadSafeCallbackAO::Run() - More events, call RunIfNotReady()"));
        RunIfNotReady();
    }
    else
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "ThreadSafeCallbackAO::Run() - No more events, call PendForExec()"));
        PendForExec();
    }

    //release queue access
    Mutex.Unlock();

    // Signal the semaphore that controls the remote thread.
    // The remote thread might be blocked and waiting for an event to be processed in case the event queue is full
    sema_status = RemoteThreadCtrlSema.Signal();
    if (sema_status != OsclProcStatus::SUCCESS_ERROR)
    {
        stat = OsclFailure;
        return NULL;
    }

    return pData;
}

////////// GET CURRENT QUEUE SIZE//////////////
OSCL_EXPORT_REF uint32 ThreadSafeCallbackAO::GetQueueNumElem()
{
    return Q->NumElem;
}

