/*
 * Copyright (C) 2010 NXP Semiconductors
 *
 * 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.
 */

/**
 * \file phOsalNfc_Timer.c
 * \brief OSAL Timer Implementation for linux
 *
 * Project: Trusted NFC Linux Light
 *
 * $Date: 03 aug 2009
 * $Author: Jérémie Corbier
 * $Revision: 1.0
 *
 */

#include <stdlib.h>
#include <signal.h>
#include <time.h>

#include <phOsalNfc.h>
#include <phOsalNfc_Timer.h>
#include <stdio.h>

#include <phDal4Nfc_messageQueueLib.h>

#define NSECS 1000000
#define MAX_NO_TIMERS 16

/*!
 * \struct phOsalNfc_Timer
 * Internal OSAL timer structure
 */
struct phOsalNfc_Timer
{
   timer_t handle;         /*!< System timer handle. */
   ppCallBck_t callback;   /*!< Callback to be called when timer expires. */
   void* pContext;         /*!< Callback context. */
#ifdef NXP_MESSAGING
   void *ptr;
#endif
   int nIsStopped;
};

static struct phOsalNfc_Timer timers[MAX_NO_TIMERS] =
{
   {0, NULL, NULL
#ifdef NXP_MESSAGING
     , NULL
#endif
     , 0
   },
};

#ifdef NXP_MESSAGING
extern int nDeferedCallMessageQueueId;

void phOsalNfc_Timer_DeferredCall(void *params)
{
   phOsalNfc_Timer_Msg_t *timer_msg;

   if(params == NULL)
      return;

   timer_msg = (phOsalNfc_Timer_Msg_t *)params;

   if((timer_msg != NULL) && (timer_msg->pCallBck != NULL))
      timer_msg->pCallBck(timer_msg->TimerId, timer_msg->pContext);

   if ((timer_msg->TimerId >= MAX_NO_TIMERS) || (timer_msg->TimerId < 0))
   {
      printf("Bad TimerId=%d, should be <= to %d\n", timer_msg->TimerId, MAX_NO_TIMERS);
   }
   else
   {
      if(timers[timer_msg->TimerId].ptr != NULL)
      {
         phOsalNfc_FreeMemory(timers[timer_msg->TimerId].ptr);
         timers[timer_msg->TimerId].ptr = NULL;
      }
   }
   phOsalNfc_FreeMemory(timer_msg);
}
#endif

/*!
 * \brief System timer callback.
 *        This callback is called by Linux whenever one the timers expires.  It
 *        calls the corresponding registered callback.
 *
 * \param sv structure storing the expired timer ID.
 */
static void phOsalNfc_Timer_Expired(union sigval sv)
{
   uint32_t timerid = (uint32_t)(sv.sival_int);

   if((timerid < MAX_NO_TIMERS)&&(timers[timerid].nIsStopped == 1))
   {
      //printf("phOsalNfc_Timer_Expired : Expired but already stopped TimerId=%d\n", timerid);
      return;
   }

   if(timerid < MAX_NO_TIMERS)
   {
#ifndef CYCLIC_TIMER
      phOsalNfc_Timer_Stop(timerid);
#else

#endif
#ifdef NXP_MESSAGING
      phOsalNfc_Timer_Msg_t *timer_msg;
      phOsalNfc_DeferedCalldInfo_t *osal_defer_msg;
      phDal4Nfc_Message_Wrapper_t wrapper;

      timer_msg = phOsalNfc_GetMemory(sizeof(phOsalNfc_Timer_Msg_t));
      if(timer_msg == NULL)
         phOsalNfc_RaiseException(phOsalNfc_e_NoMemory, 0);

      osal_defer_msg = phOsalNfc_GetMemory(sizeof(phOsalNfc_DeferedCalldInfo_t));
      if(osal_defer_msg == NULL)
      {
         phOsalNfc_FreeMemory(timer_msg);
         phOsalNfc_RaiseException(phOsalNfc_e_NoMemory, 0);
      }

      timer_msg->TimerId = timerid;
      timer_msg->pCallBck = timers[timerid].callback;
      timer_msg->pContext = timers[timerid].pContext;

      osal_defer_msg->pCallback = phOsalNfc_Timer_DeferredCall;
      osal_defer_msg->pParameter = timer_msg;

      wrapper.mtype = 1;
      wrapper.msg.eMsgType = PH_OSALNFC_TIMER_MSG;
      wrapper.msg.pMsgData = osal_defer_msg;
      wrapper.msg.Size = sizeof(phOsalNfc_DeferedCalldInfo_t);

      timers[timerid].ptr = osal_defer_msg;

      phDal4Nfc_msgsnd(nDeferedCallMessageQueueId, (void *)&wrapper,
         sizeof(phOsalNfc_Message_t), 0);
#else
      (timers[timerid].callback)(timerid, timers[timerid].pContext);
#endif
   }
}

static void phOsalNfc_Timer_Dummy_Cb(uint32_t timerid, void *pContext) {}

/*!
 * \brief Creates a new timer.
 *        This function checks whether there is an available timer slot.  If
 *        this is the case, then it reserves it for future usage and returns its
 *        ID.
 *
 * \return a valid timer ID or PH_OSALNFC_INVALID_TIMER_ID if an error occured.
 */
uint32_t phOsalNfc_Timer_Create(void)
{
   uint32_t timerid;
   struct sigevent se;

   se.sigev_notify = SIGEV_THREAD;
   se.sigev_notify_function = phOsalNfc_Timer_Expired;
   se.sigev_notify_attributes = NULL;

   /* Look for available timer slot */
   for(timerid = 0; timerid < MAX_NO_TIMERS; timerid++)
      if(timers[timerid].callback == NULL)
         break;
   if(timerid == MAX_NO_TIMERS)
      return PH_OSALNFC_INVALID_TIMER_ID;

   se.sigev_value.sival_int = (int)timerid;

   /* Create POSIX timer */
   if(timer_create(CLOCK_REALTIME, &se, &(timers[timerid].handle)) == -1)
      return PH_OSALNFC_INVALID_TIMER_ID;
   timers[timerid].callback = phOsalNfc_Timer_Dummy_Cb;
#ifdef NXP_MESSAGING
   timers[timerid].ptr = NULL;
#endif

   return timerid;
}

/*!
 * \brief Starts a timer.
 *        This function starts the timer \a TimerId with an expiration time of
 *        \a RegTimeCnt milliseconds.  Each time it expires, \a
 *        Application_callback is called.
 *
 * \param TimerId a valid timer ID.
 * \param RegTimeCnt expiration time in milliseconds.
 * \param Application_callback callback to be called when timer expires.
 */
void phOsalNfc_Timer_Start(uint32_t TimerId,
                           uint32_t RegTimeCnt,
                           ppCallBck_t  Application_callback,
                           void *pContext)
{
   struct itimerspec its;

   if(TimerId >= MAX_NO_TIMERS)
      return;
   if(Application_callback == NULL)
      return;
   if(timers[TimerId].callback == NULL)
      return;

   its.it_interval.tv_sec  = 0;
   its.it_interval.tv_nsec = 0;
   its.it_value.tv_sec     = RegTimeCnt / 1000;
   its.it_value.tv_nsec    = 1000000 * (RegTimeCnt % 1000);
   if(its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0)
   {
     // this would inadvertently stop the timer
     its.it_value.tv_nsec = 1;
   }

   timers[TimerId].callback = Application_callback;
   timers[TimerId].pContext = pContext;
   timers[TimerId].nIsStopped = 0;

   timer_settime(timers[TimerId].handle, 0, &its, NULL);
}

/*!
 * \brief Stops a timer.
 *        This function stops an already started timer.
 *
 * \param TimerId a valid timer ID.
 */
void phOsalNfc_Timer_Stop(uint32_t TimerId)
{
   struct itimerspec its = {{0, 0}, {0, 0}};

   if(TimerId >= MAX_NO_TIMERS)
      return;
   if(timers[TimerId].callback == NULL)
      return;
   if(timers[TimerId].nIsStopped == 1)
      return;

   timers[TimerId].nIsStopped = 1;
   timer_settime(timers[TimerId].handle, 0, &its, NULL);
}

/*!
 * \brief Deletes a timer.
 *        This function deletes a timer.
 *
 * \param TimerId a valid timer ID.
 */
void phOsalNfc_Timer_Delete(uint32_t TimerId)
{
   if(TimerId >= MAX_NO_TIMERS)
      return;
   if(timers[TimerId].callback == NULL)
      return;

   timer_delete(timers[TimerId].handle);

   timers[TimerId].callback = NULL;
   timers[TimerId].pContext = NULL;
}