/*===========================================================================
                              OEMSMS.c

  OEM REFERENCE SMS SUPPORT ROUTINES
  
  This file contains a reference implementation of SMS support.
    
        Copyright © 1999-2003 QUALCOMM Incorporated.
               All Rights Reserved.
            QUALCOMM Proprietary/GTDR
============================================================================*/
/*===========================================================================
                      IMPORTED DECLARATIONS
===========================================================================*/
#include "OEMFeatures.h"
#include "OEMHeap.h"
#include "OEMCriticalSection.h"
#include "OEMSMS.h"
#include "OEMSMS_priv.h"
#include "AEEStdLib.h"
#include "AEE_OEMDispatch.h"
#include "AEESMS.h"

/*===========================================================================
                 PRIVATE CONSTANTS
===========================================================================*/
#define CES_FREE        0
#define CES_PARSING     1
#define CES_READY       2
#define CES_DISPATCHING 3

/*===========================================================================
                 PRIVATE DATA DECLARATIONS
===========================================================================*/

struct _OEMSMSMgr
{
   uint32                        nRefs;
   AEECallback                   *pcbOnMTMessage;
   SMSCacheEntry *               pSMSCache;      // Points to the cache to store incoming messages
};

// The number of entries in the cache for Mobile terminated SMS. 25 value is chosen
// keeping in mind that inbox of native client have limit of 20 messages.
#define MAX_SMS_CACHE   25

/*===========================================================================
                 PRIVATE METHOD DECLARATIONS
===========================================================================*/
static int OEMSMS_Delete( OEMSMSMgr *pme );
static void OEMSMS_CancelListener( AEECallback *pcb );
static void OEMSMS_ReleaseMessage( AEECallback *pcb );

/*===========================================================================
                 PRIVATE DATA DEFINITIONS
===========================================================================*/
static OEMSMSMgr *gpSMS;
static OEMCriticalSection gSMSCriticalSection;
static uint32 gMsgId;
/*===========================================================================
                 PUBLIC METHOD DEFINITIONS
===========================================================================*/

/*===========================================================================
Manages singleton instance of gpSMS. Initializes gpSMS for first time and 
increases its reference count subsequently.
===========================================================================*/
int OEMSMS_New( OEMSMSMgr **ppme )
{
   int i;
   SMSCacheEntry *pce;

   *ppme = 0;

   FARF(SMS, ("==>  OEMSMS_New"));

   if (gpSMS) 
   {
      ++gpSMS->nRefs;
   } 
   else 
   {
      gpSMS = (OEMSMSMgr *)sys_malloc( sizeof(OEMSMSMgr) + (sizeof(SMSCacheEntry) * MAX_SMS_CACHE) );
      
      if (!gpSMS) 
      {
         return ENOMEMORY;
      }

      gpSMS->nRefs = 1;
      
      // Initialize the cache
      pce = gpSMS->pSMSCache = (SMSCacheEntry *)(gpSMS + 1);

      // Mark all the cache entries as free
      for(i = 0; i < MAX_SMS_CACHE; i++, pce++) 
      {
         pce->wStatus = CES_FREE;
      }

      OEMSMS_Init();
   }
   
   *ppme = gpSMS;

   return SUCCESS;
}

/*===========================================================================
Inrecases reference count of gpSMS
===========================================================================*/
uint32 OEMSMS_AddRef( OEMSMSMgr *pme )
{
   FARF(SMS, ("==>  OEMSMS_AddRef"));

   return ++pme->nRefs;
}

/*===========================================================================
Decreases reference count of gpSMS. Frees object when reference count reaches
zero
===========================================================================*/
uint32 OEMSMS_Release( OEMSMSMgr *pme )
{
   FARF(SMS, ("==>  OEMSMS_Release"));

   if( --pme->nRefs ) 
      return pme->nRefs;

   OEMSMS_Delete(pme);
   return 0;
}

/*===========================================================================
Called by AEESMS to register callback to be invoked on mobile terminated
messages
===========================================================================*/
void OEMSMS_OnMTMessage( OEMSMSMgr *pme, AEECallback *pcb )
{
   FARF(SMS, ("==>  OEMSMS_OnMTMessage"));

   // Make sure that its not there in any other list
   CALLBACK_Cancel(pcb);

   pcb->pfnCancel   = OEMSMS_CancelListener;
   pcb->pCancelData = (void *)pme;
   pcb->pmc         = 0;

   if( pme->pcbOnMTMessage ) 
   {
      pme->pcbOnMTMessage->pmc = (void *)pcb;
   }
   pcb->pNext           = pme->pcbOnMTMessage;
   pme->pcbOnMTMessage  = pcb;
}

/*===========================================================================
Finds a free cache entry.
===========================================================================*/
SMSCacheEntry *OEMSMSCache_GetEntry( void )
{
   OEMSMSMgr *pme = gpSMS;
   SMSCacheEntry *pce;
   SMSCacheEntry *pcoe = NULL;
   int i;

   FARF(SMS, ("==>  OEMSMSCache_GetEntry"));

   if( !gpSMS ) 
   {
      return NULL;
   }

   // Search for an entry in CES_FREE state. Also scout for oldest entry in
   // CES_DISPATCHING state.

   OEMCriticalSection_Enter(&gSMSCriticalSection);

   for(pce = pme->pSMSCache, i = 0; i < MAX_SMS_CACHE; i++, pce++)
   {
      if(pce->wStatus == CES_FREE)
      {
         pce->wStatus = CES_PARSING;
         break;
      }

      if(pce->wStatus == CES_DISPATCHING)
      {
         if (!pcoe)
         {
            pcoe = pce; // First entry in dispatching state
         }
         else
         {
            // Change pcoe if current pce is pointing to older entry
            if (pce->dwTimeStamp < pcoe->dwTimeStamp)
               pcoe = pce;
         }
      }
   }

   OEMCriticalSection_Leave(&gSMSCriticalSection);

   if (i >= MAX_SMS_CACHE)
   {
      if (!pcoe)
      {
         // Out of luck. This should happen only if more than MAX_SMS_CACHE messages 
         // arrive and we do not get a chance to process them
         FARF(SMS,("SMS (No Entries)"));
         return NULL;
      }
      else
      {
         // Use the oldest SMS cache entry
         pcoe->wStatus = CES_PARSING;
         pce = pcoe;
      }
   }

   MEMSET(pce,0,sizeof(SMSCacheEntry));
   pce->dwTransactionId = gMsgId;
   gMsgId = (gMsgId == 0xffffffff ? 0 : gMsgId + 1);
   return pce;
}

/*===========================================================================
Releases a cache entry into free pool.
===========================================================================*/
void OEMSMSCache_Release( SMSCacheEntry *pce )
{   
   FARF(SMS, ("==>  OEMSMSCache_Release"));
   pce->wStatus = CES_FREE;
}

/*===========================================================================
Called on arrival of MT message to invoke callbacks registered
===========================================================================*/
void OEMSMSCache_Ready( SMSCacheEntry *pce )
{
   FARF(SMS, ("==>  OEMSMSCache_Ready"));  
   
   // Change the status of message to CES_READY
   pce->wStatus = CES_READY;
   if( gpSMS )
   {
      while (gpSMS->pcbOnMTMessage)
      { 
         FARF(SMS, ("Resume Callback"));  
         AEE_SVC_RESUME( gpSMS->pcbOnMTMessage );
      }
   }
}

/*===========================================================================
Checks if OEMSMSMgr isn initialized
===========================================================================*/
boolean OEMSMS_IsInitialized(void)
{   
   FARF(SMS, ("==>  OEMSMS_IsInitialized"));
   return (gpSMS != NULL);
}

/*===========================================================================
Called by AEESMS to get transaction ID and teleservice ID
===========================================================================*/
int OEMSMS_GetNewMsgInfo( OEMSMSMgr *pme, uint32 *pdwTransactionId, int * pts )
{
   SMSCacheEntry *   pce;
   int nErr = EFAILED;
   int i;
   
   FARF(SMS, ("==>  OEMSMS_GetNewMsgInfo"));

   if (!pdwTransactionId || !pts)
      return EFAILED;

   OEMCriticalSection_Enter(&gSMSCriticalSection);

   for(pce = pme->pSMSCache, i = 0; i < MAX_SMS_CACHE; i++, pce++)
   {
      if(pce->wStatus == CES_READY)
      {
         *pdwTransactionId = pce->dwTransactionId;
         *pts = pce->ts;
         pce->wStatus = CES_DISPATCHING;
         FARF(SMS,("SMS Dispatching"));
         nErr = SUCCESS;
         break;
      }
   }

   OEMCriticalSection_Leave(&gSMSCriticalSection);

   return nErr;
}

/*===========================================================================
Called to get message payload
===========================================================================*/
int OEMSMS_GetMsgPayload( OEMSMSMgr *pme, uint32 dwTransactionId, OEMMsgPayload * pMsgPayload )
{
   SMSCacheEntry *   pce;
   int nErr = EFAILED;
   int i;

   FARF(SMS, ("==>  OEMSMS_GetMsgPayload"));

   if (!pMsgPayload)
      return EFAILED;

   OEMCriticalSection_Enter(&gSMSCriticalSection);

   for(pce = pme->pSMSCache, i = 0; i < MAX_SMS_CACHE; i++, pce++)
   {
      if(pce->dwTransactionId == dwTransactionId)
      {
         nErr = SUCCESS;
         break;
      }
   }

   OEMCriticalSection_Leave(&gSMSCriticalSection);

   if (!nErr)
   {
      if (!pMsgPayload->nBytes || pMsgPayload->nBytes < pce->nText)
      {
         FARF(SMS, ("Message ID = %d, Size= %d", pce->dwTransactionId, pce->nText));
         pMsgPayload->nBytes = pce->nText;
      }
      else
      {
         FARF(SMS, ("Message ID = %d, Encoding = %d, Size = %d", pce->dwTransactionId, pce->nEncoding, pce->nText));
         FARF(SMS, ("Text = %s", pce->pText));
         pMsgPayload->nEncoding = pce->nEncoding;
         pMsgPayload->nBytes = pce->nText;
         pMsgPayload->nPadding = 0;
         MEMCPY(pMsgPayload->data, pce->pText, pce->nText);
      }
   }
   return nErr;
}


/*===========================================================================
Called to get message data
===========================================================================*/
int OEMSMS_GetMsgData(OEMSMSMgr *pme, uint32 dwTransactionId, OEMMsgData * pOEMMsgData)
{
   SMSCacheEntry *   pce;
   int nErr = EFAILED;
   int i;
   
   FARF(SMS, ("==>  OEMSMS_GetMsgData"));

   if (!pOEMMsgData)
      return nErr;

   MEMSET(pOEMMsgData, 0, sizeof(pOEMMsgData));

   OEMCriticalSection_Enter(&gSMSCriticalSection);

   for(pce = pme->pSMSCache, i = 0; i < MAX_SMS_CACHE; i++, pce++){
      if(pce->dwTransactionId == dwTransactionId)
      {
         nErr = SUCCESS;
         break;
      }
   }

   OEMCriticalSection_Leave(&gSMSCriticalSection);

   if (!nErr)
   {
      pOEMMsgData->dwLang = pce->dwLang;
      pOEMMsgData->dwTimeStamp = pce->dwTimeStamp;
      pOEMMsgData->nNumMsgs = pce->nNumMsgs;
      pOEMMsgData->nPriority = pce->nPriority;
      pOEMMsgData->nPrivacy = pce->nPrivacy;
      STRCPY(pOEMMsgData->szCallback, pce->szCallback);
      STRCPY(pOEMMsgData->szMOFromMTTo, pce->szFrom);
      pOEMMsgData->ts = pce->ts;
      pOEMMsgData->bRequestDLAck = pce->bRequestDLAck;
      pOEMMsgData->bRequestUserAck = pce->bRequestUserAck;
      pOEMMsgData->bRequestReadAck = pce->bRequestReadAck;
      pOEMMsgData->bIsDLAck = pce->bIsDLAck;
      pOEMMsgData->bIsUserAck = pce->bIsUserAck;
      pOEMMsgData->bIsReadAck = pce->bIsReadAck;
      pOEMMsgData->dwTag = pce->dwTag;
      pOEMMsgData->dwMsgID = pce->dwMsgID;
      pOEMMsgData->bIsSCAddress = pce->bIsSCAddress;
      OEMSMS_GetUDHData(pce, &pOEMMsgData->nBytes, pOEMMsgData->data);
   }
   return nErr;
}

/*===========================================================================

===========================================================================*/
void OEMSMS_SendMessage( OEMSMSMgr *pme, OEMMsgData * pMsgData, 
                        OEMMsgPayload * pMsgPayload, uint32 *pdwStatus, AEECallback *pcb )
{
   void *hMsg;
   int  nErr = SUCCESS;
   
   FARF(SMS, ("==>  OEMSMS_SendMessage"));

   hMsg = OEMSMS_NewMessage( pMsgData, pMsgPayload );

   if (hMsg) 
   {   
      CALLBACK_Cancel(pcb);
      pcb->pCancelData = hMsg;
      pcb->pfnCancel   = OEMSMS_ReleaseMessage;
      OEMSMS_SendSMS( hMsg, pcb, pdwStatus );
   }
   else
      nErr = ENOMEMORY;

   if( SUCCESS != nErr ) 
   {
      AEESMS_SETERROR(*pdwStatus, AEESMS_ERRORTYPE_TEMP, AEESMS_ERROR_NO_MEMORY);
      AEE_SVC_RESUME( pcb );
   }
}

/*===========================================================================

===========================================================================*/
void OEMSMS_SetClientStatus( OEMSMSMgr *pme, uint32 cs, uint32 mt )
{   
   FARF(SMS, ("==>  OEMSMS_SetClientStatus"));
   OEMSMS_SetMsgAckStatus(cs, mt);
}

/*===========================================================================
Called to free SMSCacheEntry associated with the given transaction Id
===========================================================================*/
void OEMSMS_FreeMsg( OEMSMSMgr *pme, uint32 dwTransactionId)
{
   SMSCacheEntry *   pce;
   int i;

   FARF(SMS, ("==>  OEMSMS_FreeMsg"));

   OEMCriticalSection_Enter(&gSMSCriticalSection);

   for(pce = pme->pSMSCache, i = 0; i < MAX_SMS_CACHE; i++, pce++)
   {
      if(pce->dwTransactionId == dwTransactionId)
      {
         OEMSMSCache_Release(pce);
         break;
      }
   }

   OEMCriticalSection_Leave(&gSMSCriticalSection);
}

/*===========================================================================
                 PRIVATE METHOD DEFINITIONS
===========================================================================*/

/*===========================================================================

===========================================================================*/
static int OEMSMS_Delete( OEMSMSMgr *pme )
{   
   FARF(SMS, ("==>  OEMSMS_Delete"));

   if( pme == gpSMS ) {

      OEMSMS_Cleanup();
      gpSMS = (OEMSMSMgr *)0;
   }

   sys_free( pme );
   return SUCCESS;
}

/*===========================================================================

===========================================================================*/
static void OEMSMS_CancelListener( AEECallback *pcb )
{
   OEMSMSMgr *pme = (OEMSMSMgr *)pcb->pCancelData;
   AEECallback *pPrev = (AEECallback *)pcb->pmc;
   
   FARF(SMS, ("==>  OEMSMS_CancelListener"));

   //Verify that it is me set as the cancel function.
   //Cross-task cancellations need this check.
   if(pcb->pfnCancel != OEMSMS_CancelListener)
      return;
   
   if( pPrev ) {
      pPrev->pNext = pcb->pNext;
   }
   else {
      pme->pcbOnMTMessage = pcb->pNext;
   }
   if( pcb->pNext ) {
      pcb->pNext->pmc = (void *)pPrev;
   }

   pcb->pCancelData = 0;
   pcb->pfnCancel   = 0;
}


/*===========================================================================

===========================================================================*/
static void OEMSMS_ReleaseMessage( AEECallback *pcb )
{
   FARF(SMS, ("==>  OEMSMS_ReleaseMessage"));

  //Verify that it is me set as the cancel function.
   //Cross-task cancellations need this check.
   if(pcb->pfnCancel != OEMSMS_ReleaseMessage)
      return;
   
   OEMSMS_DeleteMessage( (OEMMsg*)pcb->pCancelData );

   pcb->pCancelData = 0;
   pcb->pfnCancel   = 0;
}



/*===========================================================================

  This returns the correct ascii char for a DIGIT_MODE_4_BIT char              

===========================================================================*/
char GetDigit(byte ch)
{
   switch(ch){
      case 0:
      case 10:
         return('0');
      case 11:
         return('*');
      case 12:
         return('#');
      default:
         if(ch >= 1 && ch <= 9){
            return( (char)('0' + ch));
         }
         break;
   }
   return(' ');
}

/*===========================================================================

  Converts a number character to its DTMF value

===========================================================================*/
byte GetDTMF (char ch) 
{
   switch (ch) {
      case '0':
         return 0x0a;
      case '*':
         return 0x0b;
      case '#':
         return 0x0c;
      default:
         if (ch >= '1' && ch <= '9') {
            return(byte)(ch - '0');
         }
   }
   return 0x00;
}

/*===========================================================================

  Converts an ASCII string to a DTMF string

===========================================================================*/
uint32 GetASCII2DTMF (char * pcBuf, byte * pBuf, uint32 dwLen) 
{
   uint32   i,j;
   byte     d;

   for (i = j = 0; i < dwLen; i++) 
   {
      if ((d = GetDTMF(pcBuf[i])) != 0) 
      {  
         // skip if char is not recognized
         pBuf[j] = d;  // change each ASCII digit to DTMF 
         j++;
      }
   }

   return(uint32)j;
}
