#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <uv.h>
#include <dbus/dbus.h>
#include <errno.h>
#include <time.h>
#include <uthash/uthash.h>

#include "log.h"
#include "libuv_dbus.h"

#define     TIMER_TIMEOUT                   (200)

#define IS_LEAP_YEAR(year)  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))

static unsigned char g_DayOfMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

typedef struct 
{
    void*           pUserData;
    OnAlarmTimer    pOnAlarmCb;
    unsigned int    alarmId;

    struct tm       setDateTime;
    int             setWeekDay;
    unsigned int    repeatMode;

    struct tm       onDateTime;
    time_t          onTimestamp;
    unsigned int    timerPriority;

    UT_hash_handle  hh;         ///< UT Hash handle
} ALARM_ITEM_DATA, *PALARM_ITEM_DATA;

static uv_timer_t               g_uvTimer;
static unsigned int             g_iAlarmId	= 1;
static struct   tm              g_LocalTime;
static time_t          			g_TimeStamp;
static uv_loop_t*               g_pMainLoop = NULL;
static uv_rwlock_t              g_uvHashRwLock;
static PALARM_ITEM_DATA         g_TimerTbl = NULL;

const char* DumpTimerRepeatModeString(int mode)
{
    switch(mode & 0xFF)
    {
        case REPEAT_MODE_NONE: 				return "NONE";
        case REPEAT_MODE_EVERY_DAY: 		return "EVERY_DAY";
        case REPEAT_MODE_WORKDAY: 			return "WORKDAY";
        case REPEAT_MODE_HOLIDAY:           return ("REPEAT_MODE_HOLIDAY"); 
        case REPEAT_MODE_WEEKEND: 			return "WEEKEND";
        case REPEAT_MODE_WEEKDAY: 			return "WEEKDAY";
        case REPEAT_MODE_EVERY_MONTH_DAY: 	return "EVERY_MONTH_DAY";
        case REPEAT_MODE_EVERY_YEAR_DAY: 	return "EVERY_YEAR_DAY";
        case REPEAT_MODE_EVERY_TIME:        return ("EVERY_TIME"); 
        case REPEAT_MODE_MONTH_LAST_DAY:    return "REPEAT_MODE_MONTH_LAST_DAY";
        default:                            return ("Unknown Mode"); 
    }
}

static int __timestampSort(PALARM_ITEM_DATA p1, PALARM_ITEM_DATA p2)
{
    if(p1->onTimestamp == p2->onTimestamp)
    {
        return (p2->timerPriority - p1->timerPriority);
    }
    else
    {
        return (p1->onTimestamp - p2->onTimestamp);
    }
}

static int __getNextOnTimestamp(PALARM_ITEM_DATA pInfo)
{
    int ret;
    struct  tm  setTime;
    time_t timestamp;

    if(pInfo == NULL)
    {
        return (-ERR_INPUT_PARAMS);
    }

    if(pInfo->repeatMode == REPEAT_MODE_NONE)
    {
        pInfo->onTimestamp  = 0;
        return (-ERR_INPUT_PARAMS);
    }
    
    timestamp           = pInfo->onTimestamp + 24 * 3600;
    pInfo->onTimestamp  = timestamp; 
    localtime_r(&timestamp, &setTime);

    switch(pInfo->repeatMode)
    {
        case REPEAT_MODE_EVERY_DAY:
            localtime_r(&timestamp, &pInfo->onDateTime);
            break;

        case REPEAT_MODE_WORKDAY:
            do
            {
                ret = CurrentIsWorkDay(setTime.tm_year, setTime.tm_yday);

                if(ret == 0)
                {
                    timestamp  = mktime(&setTime) + 24 * 3600;

                    localtime_r(&timestamp, &setTime);
                }
            } while(ret == 0);

            if(ret < 0)
            {
                pInfo->onTimestamp  = 0;
                pInfo->onDateTime.tm_year  = -1;
                pInfo->onDateTime.tm_mon   = -1;
                pInfo->onDateTime.tm_mday  = -1;
            }
            else
            {
                pInfo->onDateTime.tm_year   = setTime.tm_year;
                pInfo->onDateTime.tm_mon    = setTime.tm_mon;
                pInfo->onDateTime.tm_mday   = setTime.tm_mday;
                pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            }
            break;

        case REPEAT_MODE_HOLIDAY:
            do
            {
                ret = CurrentIsWorkDay(setTime.tm_year, setTime.tm_yday);

                if(ret == 0)
                {
                    timestamp  = mktime(&setTime) + 24 * 3600;

                    localtime_r(&timestamp, &setTime);
                }
            } while(ret == 1);

            if(ret < 0)
            {
                pInfo->onTimestamp          = 0;
                pInfo->onDateTime.tm_year   = -1;
                pInfo->onDateTime.tm_mon    = -1;
                pInfo->onDateTime.tm_mday   = -1;
            }
            else
            {
                pInfo->onDateTime.tm_year   = setTime.tm_year;
                pInfo->onDateTime.tm_mon    = setTime.tm_mon;
                pInfo->onDateTime.tm_mday   = setTime.tm_mday;
                pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            }
            break;

        case REPEAT_MODE_WEEKEND:
            while(setTime.tm_wday != 0 && setTime.tm_wday != 6)
            {
                timestamp  = mktime(&setTime) + 24 * 3600;
                localtime_r(&timestamp, &setTime);
            }

            pInfo->onDateTime.tm_year   = setTime.tm_year;
            pInfo->onDateTime.tm_mon    = setTime.tm_mon;
            pInfo->onDateTime.tm_mday   = setTime.tm_mday;
            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            break;

        case REPEAT_MODE_WEEKDAY: 
            if(pInfo->setDateTime.tm_wday == 0)
            {
                pInfo->setDateTime.tm_wday = 1 << 0;
            }
            else if(pInfo->setDateTime.tm_wday & (1 << 7))
            {
                pInfo->setDateTime.tm_wday = 1 << 0;
            }
            
            while(((1 << setTime.tm_wday) & pInfo->setDateTime.tm_wday) == 0)
            {
                timestamp  = mktime(&setTime) + 24 * 3600;
                localtime_r(&timestamp, &setTime);
            }

            pInfo->onDateTime.tm_year   = setTime.tm_year;
            pInfo->onDateTime.tm_mon    = setTime.tm_mon;
            pInfo->onDateTime.tm_mday   = setTime.tm_mday;
            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            
            break;

        case REPEAT_MODE_EVERY_TIME:
            timestamp   = mktime(&g_LocalTime);

            if(pInfo->setDateTime.tm_hour > 0)
            {
                timestamp   += pInfo->setDateTime.tm_hour * 3600;
            }

            if(pInfo->setDateTime.tm_min > 0)
            {
                timestamp   += pInfo->setDateTime.tm_min * 60;
            }

            if(pInfo->setDateTime.tm_sec > 0)
            {
                timestamp   += pInfo->setDateTime.tm_sec;
            }

            localtime_r(&timestamp, &pInfo->onDateTime);
            pInfo->onTimestamp          = timestamp;
            break;

        case REPEAT_MODE_MONTH_LAST_DAY:
            if(pInfo->onDateTime.tm_mon < 11)
            {
                pInfo->onDateTime.tm_mon++;
            }
            else
            {
                pInfo->onDateTime.tm_mon    = 0;
                pInfo->onDateTime.tm_year++;
            }

            pInfo->onDateTime.tm_mday   = g_DayOfMonth[pInfo->onDateTime.tm_mon];
            if(IS_LEAP_YEAR(pInfo->onDateTime.tm_year) && (pInfo->onDateTime.tm_mon == 1))
            {
                pInfo->onDateTime.tm_mday += 1;
            }

            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            break;
    }

    return (0);
}

static int __getOnTimestamp(PALARM_ITEM_DATA pInfo)
{
    int ret = 0;
    struct  tm  setTime;
    time_t  timestamp;

    if(pInfo == NULL)
    {
        return (-ERR_INPUT_PARAMS);
    }
    
    if(pInfo->setDateTime.tm_hour == -1)
    {
        pInfo->onDateTime.tm_hour   = g_LocalTime.tm_hour;
    }
    else
    {
        pInfo->onDateTime.tm_hour   = pInfo->setDateTime.tm_hour;
    }
    
    if(pInfo->setDateTime.tm_min == -1)
    {
        pInfo->onDateTime.tm_min    = g_LocalTime.tm_min;
    }
    else
    {
        pInfo->onDateTime.tm_min    = pInfo->setDateTime.tm_min;
    }
    
    if(pInfo->setDateTime.tm_sec == -1)
    {
        pInfo->onDateTime.tm_sec    = g_LocalTime.tm_sec;
    }
    else
    {
        pInfo->onDateTime.tm_sec    = pInfo->setDateTime.tm_sec;
    }

    switch(pInfo->repeatMode)
    {
        case REPEAT_MODE_EVERY_MONTH_DAY:
            pInfo->setDateTime.tm_mon   = -1;
            pInfo->setDateTime.tm_year  = -1;
        case REPEAT_MODE_EVERY_YEAR_DAY:
            pInfo->setDateTime.tm_year  = -1;
        case REPEAT_MODE_NONE:
            if(pInfo->setDateTime.tm_year == -1)
            {
                pInfo->onDateTime.tm_year   = g_LocalTime.tm_year;
            }
            else
            {
                pInfo->onDateTime.tm_year   = pInfo->setDateTime.tm_year;
            }
            
            if(pInfo->setDateTime.tm_mon == -1)
            {
                pInfo->onDateTime.tm_mon    = g_LocalTime.tm_mon;
            }
            else
            {
                pInfo->onDateTime.tm_mon    = pInfo->setDateTime.tm_mon;
            }
            
            if(pInfo->setDateTime.tm_mday == -1)
            {
                pInfo->onDateTime.tm_mday   = g_LocalTime.tm_mday;
            }
            else
            {
                pInfo->onDateTime.tm_mday   = pInfo->setDateTime.tm_mday;
            }
            break;

        case REPEAT_MODE_EVERY_DAY:
        case REPEAT_MODE_WORKDAY:
        case REPEAT_MODE_WEEKEND:
        case REPEAT_MODE_WEEKDAY:
        case REPEAT_MODE_EVERY_TIME:
        case REPEAT_MODE_HOLIDAY:
            pInfo->onDateTime.tm_year   = g_LocalTime.tm_year;
            pInfo->onDateTime.tm_mon    = g_LocalTime.tm_mon;
            pInfo->onDateTime.tm_mday   = g_LocalTime.tm_mday;
            break;
        case REPEAT_MODE_MONTH_LAST_DAY:
            pInfo->onDateTime.tm_year   = g_LocalTime.tm_year;
            pInfo->onDateTime.tm_mon    = g_LocalTime.tm_mon;
            pInfo->onDateTime.tm_mday   = g_DayOfMonth[g_LocalTime.tm_mon];
            if(IS_LEAP_YEAR(g_LocalTime.tm_year) && (g_LocalTime.tm_mon == 1))
            {
                pInfo->onDateTime.tm_mday += 1;
            }
            break;
    }
    
    pInfo->onDateTime.tm_wday   = g_LocalTime.tm_wday;
    pInfo->onDateTime.tm_yday   = g_LocalTime.tm_yday;
    pInfo->onTimestamp          = mktime(&pInfo->onDateTime);

    if(pInfo->repeatMode == REPEAT_MODE_NONE)
    {
        return (0);
    }
    
    memcpy(&setTime, &g_LocalTime, sizeof(struct tm));

    if(mktime(&setTime) > (pInfo->onTimestamp + 1))
    {
        if(pInfo->repeatMode == REPEAT_MODE_EVERY_MONTH_DAY)
        {
            if(pInfo->onDateTime.tm_mon < 11)
            {
                pInfo->onDateTime.tm_mon++;
            }
            else
            {
                pInfo->onDateTime.tm_mon    = 0;
                pInfo->onDateTime.tm_year++;
            }

            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            return (0);
        }
        else if(pInfo->repeatMode == REPEAT_MODE_EVERY_YEAR_DAY)
        {
            pInfo->onDateTime.tm_year++;
            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            return (0);
        }
        else if(pInfo->repeatMode == REPEAT_MODE_MONTH_LAST_DAY)
        {
            if(pInfo->onDateTime.tm_mon < 11)
            {
                pInfo->onDateTime.tm_mon++;
            }
            else
            {
                pInfo->onDateTime.tm_mon    = 0;
                pInfo->onDateTime.tm_year++;
            }

            pInfo->onDateTime.tm_mday   = g_DayOfMonth[pInfo->onDateTime.tm_mon];
            if(IS_LEAP_YEAR(pInfo->onDateTime.tm_year) && (pInfo->onDateTime.tm_mon == 1))
            {
                pInfo->onDateTime.tm_mday += 1;
            }

            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            return (0);
        }
        else
        {
            timestamp  = mktime(&setTime) + 24 * 3600;
            localtime_r(&timestamp, &setTime);
        }
    }
    
    switch(pInfo->repeatMode)
    {
        case REPEAT_MODE_EVERY_DAY:
            pInfo->onDateTime.tm_year   = setTime.tm_year;
            pInfo->onDateTime.tm_mon    = setTime.tm_mon;
            pInfo->onDateTime.tm_mday   = setTime.tm_mday;
            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            break;

        case REPEAT_MODE_WORKDAY:
            do
            {
                ret = CurrentIsWorkDay(setTime.tm_year, setTime.tm_yday);

                if(ret == 0)
                {
                    timestamp  = mktime(&setTime) + 24 * 3600;

                    localtime_r(&timestamp, &setTime);
                }
            } while(ret == 0);

            if(ret < 0)
            {
                pInfo->onTimestamp  = 0;
                pInfo->onDateTime.tm_year  = -1;
                pInfo->onDateTime.tm_mon   = -1;
                pInfo->onDateTime.tm_mday  = -1;
            }
            else
            {
                pInfo->onDateTime.tm_year   = setTime.tm_year;
                pInfo->onDateTime.tm_mon    = setTime.tm_mon;
                pInfo->onDateTime.tm_mday   = setTime.tm_mday;
                pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            }
            break;

        case REPEAT_MODE_HOLIDAY:
            do
            {
                ret = CurrentIsWorkDay(setTime.tm_year, setTime.tm_yday);

                if(ret == 0)
                {
                    timestamp  = mktime(&setTime) + 24 * 3600;

                    localtime_r(&timestamp, &setTime);
                }
            } while(ret == 1);

            if(ret < 0)
            {
                pInfo->onTimestamp          = 0;
                pInfo->onDateTime.tm_year   = -1;
                pInfo->onDateTime.tm_mon    = -1;
                pInfo->onDateTime.tm_mday   = -1;
            }
            else
            {
                pInfo->onDateTime.tm_year   = setTime.tm_year;
                pInfo->onDateTime.tm_mon    = setTime.tm_mon;
                pInfo->onDateTime.tm_mday   = setTime.tm_mday;
                pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            }
            break;

        case REPEAT_MODE_WEEKEND:
            while(setTime.tm_wday != 0 && setTime.tm_wday != 6)
            {
                timestamp  = mktime(&setTime) + 24 * 3600;
                localtime_r(&timestamp, &setTime);
            }

            pInfo->onDateTime.tm_year   = setTime.tm_year;
            pInfo->onDateTime.tm_mon    = setTime.tm_mon;
            pInfo->onDateTime.tm_mday   = setTime.tm_mday;
            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            break;

        case REPEAT_MODE_WEEKDAY: 
            if(pInfo->setDateTime.tm_wday == 0)
            {
                pInfo->setDateTime.tm_wday = 1 << 0;
            }
            else if(pInfo->setDateTime.tm_wday & (1 << 7))
            {
                pInfo->setDateTime.tm_wday = 1 << 0;
            }
            
            while(((1 << setTime.tm_wday) & pInfo->setDateTime.tm_wday) == 0)
            {
                timestamp  = mktime(&setTime) + 24 * 3600;
                localtime_r(&timestamp, &setTime);
            }

            pInfo->onDateTime.tm_year   = setTime.tm_year;
            pInfo->onDateTime.tm_mon    = setTime.tm_mon;
            pInfo->onDateTime.tm_mday   = setTime.tm_mday;
            pInfo->onTimestamp          = mktime(&pInfo->onDateTime);
            
            break;

        case REPEAT_MODE_EVERY_TIME:
            timestamp   = mktime(&g_LocalTime);

            if(pInfo->setDateTime.tm_hour > 0)
            {
                timestamp   += pInfo->setDateTime.tm_hour * 3600;
            }

            if(pInfo->setDateTime.tm_min > 0)
            {
                timestamp   += pInfo->setDateTime.tm_min * 60;
            }

            if(pInfo->setDateTime.tm_sec > 0)
            {
                timestamp   += pInfo->setDateTime.tm_sec;
            }

            localtime_r(&timestamp, &pInfo->onDateTime);
            pInfo->onTimestamp          = timestamp;
            break;
    }

    return (0);
}

static void __timerout200msCb(uv_timer_t *pTimer)
{
    PALARM_ITEM_DATA pItem = NULL, pTemp = NULL;

    // upgrade current time and timestamp
    g_TimeStamp     = time((time_t*)NULL);
    localtime_r(&g_TimeStamp, &g_LocalTime);

    uv_rwlock_wrlock(&g_uvHashRwLock);
    HASH_ITER(hh, g_TimerTbl, pItem, pTemp)
    { 
        // cleanup out of time more than 10s timer
        if(g_TimeStamp - pItem->onTimestamp > 10)
        {
            LOG_EX(LOG_Warn, "Remove out of time timer: %u, %ld, %ld\n", pItem->alarmId, g_TimeStamp, pItem->onTimestamp);
            HASH_DEL(g_TimerTbl, pItem);
            free(pItem);

            continue;
        }

        // timer not on time
        if(pItem->onTimestamp != g_TimeStamp)
        {
            break;
        }

        // timer on time, call callback
        if(pItem->pOnAlarmCb)
        {
            pItem->pOnAlarmCb(pItem->alarmId, g_TimeStamp, pItem->pUserData);
        }
        
        //LOG_EX(LOG_Debug, "Timer %d Alarming..................\n", pItem->alarmId);

        // cleanup not repeat timer
        if(pItem->repeatMode == REPEAT_MODE_NONE)
        {
            HASH_DEL(g_TimerTbl, pItem);
            free(pItem);
        }
        else
        {
            // calc next on time
            int ret = __getNextOnTimestamp(pItem);

            if(ret != 0 || pItem->onTimestamp == 0)
            {
                // some error, remove it
                LOG_EX(LOG_Error, "Timer %d repeat error: ret = %d, timestamp = %u\n", pItem->alarmId, ret, pItem->onTimestamp);
                HASH_DEL(g_TimerTbl, pItem);
                free(pItem);
            }
            else
            {
                // resort table by upgrade timestamp
                HASH_SORT(g_TimerTbl, __timestampSort);

                // show log
                LOG_EX(LOG_Debug, "Readd Timer: %u at [%04u-%02u-%02u %02u:%02u:%02u], repMode = %s, Timestamp = %u\n", 
                    pItem->alarmId,
                    pItem->onDateTime.tm_year + 1900,
                    pItem->onDateTime.tm_mon + 1, 
                    pItem->onDateTime.tm_mday,
                    pItem->onDateTime.tm_hour,
                    pItem->onDateTime.tm_min,
                    pItem->onDateTime.tm_sec,
                    DumpTimerRepeatModeString(pItem->repeatMode),
                    pItem->onTimestamp);
            }
        }
    }
    uv_rwlock_wrunlock(&g_uvHashRwLock);
}

int AlarmTimerInit(uv_loop_t* pLoop)
{
    g_pMainLoop = pLoop;
    uv_rwlock_init(&g_uvHashRwLock);
    uv_timer_init(g_pMainLoop, &g_uvTimer);

    g_TimeStamp     = time((time_t*)NULL);
    localtime_r(&g_TimeStamp, &g_LocalTime);

    g_iAlarmId = 1;

    uv_timer_start(&g_uvTimer,  __timerout200msCb, 0,  TIMER_TIMEOUT);
}

int AlarmTimerCleanup(void)
{
    uv_timer_stop(&g_uvTimer);
    uv_rwlock_destroy(&g_uvHashRwLock);

    if(g_pMainLoop != NULL)
    {
        AlarmTimerInit(g_pMainLoop);
    }
}

int AlarmTimerRemove(unsigned int tmId)
{
	PALARM_ITEM_DATA    pItem = NULL;

    uv_rwlock_rdlock(&g_uvHashRwLock);
    HASH_FIND_INT(g_TimerTbl, &tmId, pItem);
    uv_rwlock_rdunlock(&g_uvHashRwLock);

    if(pItem == NULL)
    {
        LOG_EX(LOG_Error, "Can't find item: %u\n", tmId);
        return (-ERR_NO_ITEMS);
    }

    uv_rwlock_wrlock(&g_uvHashRwLock);
    HASH_DEL(g_TimerTbl, pItem);
    uv_rwlock_wrunlock(&g_uvHashRwLock);
    free(pItem);

    return (tmId);
}

unsigned int AlarmTimerAdd(int      year, 
                           int      month,
                           int      day,
                           int      hour,
                           int      minute,
                           int      second,
                           int      weekDay,
                           int      repMode, 
                           OnAlarmTimer    pOnTimerCb, 
                           int      priority,
                           void     *pUserData, 
                           int      *pError)
{
    int et;
    PALARM_ITEM_DATA    pAlarmData  = NULL;

    if(pOnTimerCb == NULL)
    {
        LOG_EX(LOG_Error, "Input Params Error: pOnTimerCb = %p\n", pOnTimerCb);
        if(pError)
        {
            *pError = -ERR_INPUT_PARAMS;
        }

        return (0xFFFFFFFF);
    }

    g_TimeStamp     = time((time_t*)NULL);
    localtime_r(&g_TimeStamp, &g_LocalTime);
    
    pAlarmData  = (PALARM_ITEM_DATA)malloc(sizeof(ALARM_ITEM_DATA));

    if(pAlarmData == NULL)
    {
        LOG_EX(LOG_Error, "Malloc Memory Error\n");

        if(pError)
        {
            *pError = -ERR_MALLOC_MEMORY;
        }

        return (0xFFFFFFFF);
    }

    memset(pAlarmData, 0, sizeof(ALARM_ITEM_DATA));

    // save input params
    pAlarmData->setDateTime.tm_year     = year;
    pAlarmData->setDateTime.tm_mon      = month;
    pAlarmData->setDateTime.tm_mday     = day;
    pAlarmData->setDateTime.tm_hour     = hour;
    pAlarmData->setDateTime.tm_min      = minute;
    pAlarmData->setDateTime.tm_sec      = second;
    pAlarmData->setDateTime.tm_wday     = weekDay;

    pAlarmData->repeatMode              = repMode;
    pAlarmData->pOnAlarmCb              = pOnTimerCb;
    pAlarmData->pUserData               = pUserData;
    pAlarmData->timerPriority           = priority;

    // get timer on time
    __getOnTimestamp(pAlarmData);
    
    // check on time
    et  = pAlarmData->onTimestamp - mktime(&g_LocalTime);

    if(et < -1 || pAlarmData->onTimestamp == 0)
    {
        LOG_EX(LOG_Debug, "Add Timer Error: [%04u-%02u-%02u %02u:%02u:%02u], repMode = %s(%u), %d, %u/%u\n", 
                pAlarmData->setDateTime.tm_year + 1900,
                pAlarmData->setDateTime.tm_mon + 1, 
                pAlarmData->setDateTime.tm_mday,
                pAlarmData->setDateTime.tm_hour,
                pAlarmData->setDateTime.tm_min,
                pAlarmData->setDateTime.tm_sec,
                DumpTimerRepeatModeString(repMode), repMode,
                et, pAlarmData->onTimestamp, mktime(&g_LocalTime));
        
        if(pError)
        {
            *pError = -ERR_INPUT_PARAMS;
        }

        return (0xFFFFFFFF);
    }
    
    if(pError)
    {
        *pError = 0;
    }

    // upgrade time global id
    pAlarmData->alarmId	    = __sync_fetch_and_add(&g_iAlarmId, 1);

    // save new timer to hash table, and sort it by timestamp
    uv_rwlock_wrlock(&g_uvHashRwLock);
    HASH_ADD_INT(g_TimerTbl, alarmId, pAlarmData);
    HASH_SORT(g_TimerTbl, __timestampSort);
    uv_rwlock_wrunlock(&g_uvHashRwLock);

    LOG_EX(LOG_Debug, "Add: %u [%04u-%02u-%02u %02u:%02u:%02u] at [%04u-%02u-%02u %02u:%02u:%02u], repMode = %s, priority = %d, Timestamp = %u\n", 
                pAlarmData->alarmId,
                (pAlarmData->setDateTime.tm_year    == -1) 	? 1900 	: pAlarmData->setDateTime.tm_year + 1900,
                (pAlarmData->setDateTime.tm_mon     == -1) 	? 0		: pAlarmData->setDateTime.tm_mon + 1, 
                (pAlarmData->setDateTime.tm_mday    == -1) 	? 0		: pAlarmData->setDateTime.tm_mday,
                (pAlarmData->setDateTime.tm_hour    == -1) 	? 0		: pAlarmData->setDateTime.tm_hour,
                (pAlarmData->setDateTime.tm_min     == -1)  ? 0		: pAlarmData->setDateTime.tm_min,
                (pAlarmData->setDateTime.tm_sec     == -1) 	? 0		: pAlarmData->setDateTime.tm_sec,
                pAlarmData->onDateTime.tm_year + 1900,
                pAlarmData->onDateTime.tm_mon + 1, 
                pAlarmData->onDateTime.tm_mday,
                pAlarmData->onDateTime.tm_hour,
                pAlarmData->onDateTime.tm_min,
                pAlarmData->onDateTime.tm_sec,
                DumpTimerRepeatModeString(repMode),
                pAlarmData->timerPriority,
                pAlarmData->onTimestamp);

    return (pAlarmData->alarmId);
}


