/**@@@+++@@@@******************************************************************
**
** Microsoft Windows Media
** Copyright (C) Microsoft Corporation. All rights reserved.
**
***@@@---@@@@******************************************************************
*/

//#include <stdlib.h>
#include <time.h>
#define NO_DRM_CRT 1
#include <drmcommon.h>
#include <oemimpl.h>

#define C_SECONDS_FROM_1601_TO_1970  DRM_UI64Mul(DRM_UI64(1164447360), DRM_UI64(10))
#define C_TICS_PER_SECOND 10000000

/* convert a 32-bit time_t to a 64-bit DRMFILETIME 
   only valid for 1970-2038 AD */

static void _time_t_To_FILETIME (IN  time_t         time_tA, 
                                OUT DRMFILETIME   *pfiletime)
{
    DRM_UINT64 tics;

    /* convert a time_t (seconds since 1970) to seconds-since-1601 */ 

    tics = DRM_UI64Add(DRM_UI64(time_tA),C_SECONDS_FROM_1601_TO_1970);

    /* convert seconds to 100-nanosecond tics */

    tics = DRM_UI64Mul( tics, DRM_UI64(C_TICS_PER_SECOND));
    
    UI64_TO_FILETIME( tics, *pfiletime );
}

static DRM_VOID _struct_tm_To_SYSTEMTIME (IN struct tm       *ptm,
                                         OUT DRMSYSTEMTIME  *psystime)
{
    psystime->wYear         = (DRM_WORD) ptm->tm_year + 1900;
    psystime->wMonth        = (DRM_WORD) ptm->tm_mon  + 1;
    psystime->wDay          = (DRM_WORD) ptm->tm_mday;
    psystime->wHour         = (DRM_WORD) ptm->tm_hour;
    psystime->wMinute       = (DRM_WORD) ptm->tm_min;
    psystime->wSecond       = (DRM_WORD) ptm->tm_sec;
    psystime->wDayOfWeek    = (DRM_WORD) ptm->tm_wday;
    psystime->wMilliseconds = 0;
}

static DRM_VOID _SYSTEMTIME_To_struct_tm (IN  const DRMSYSTEMTIME  *psystime,
                                         OUT struct tm            *ptm)
{
    ptm->tm_year = psystime->wYear  - 1900; /* current minus 1900 */
    ptm->tm_mon  = psystime->wMonth - 1;    /* (0-11) */
    ptm->tm_mday = psystime->wDay;          /* (1-31) */
    ptm->tm_hour = psystime->wHour;         /* (0-23) */
    ptm->tm_min  = psystime->wMinute;       /* (0-59) */
    ptm->tm_sec  = psystime->wSecond;       /* (0-59) */
    ptm->tm_wday = psystime->wDayOfWeek;    /* (0-6)  */
}

static DRM_BOOL _SystemTimeToTime_t(IN const DRMSYSTEMTIME *psystime,
                                    OUT      time_t        *ptimet)
{
    struct tm  tmA   = {0};
    struct tm  tmB   = {0};
    time_t     timeA = 0;
    time_t     timeB = 0;

    /* internal function, no need to test ptimet == NULL */
    
    if( ( psystime->wYear  <  1601 || psystime->wYear > 2032 ) ||
        ( psystime->wMonth == 0    || psystime->wMonth > 12 ) ||
        ( psystime->wDay   == 0    || psystime->wMonth > 31 ) ||
        ( psystime->wHour   > 23 ) ||
        ( psystime->wMinute > 59 ) ||
        ( psystime->wSecond > 59 ) )
    {
        return FALSE;
    }
        
    _SYSTEMTIME_To_struct_tm (psystime, &tmA);

    /* The mktime function needs some care.  The tm struct we give it is in the TZ we want but maketime feels the need
       to adjust for the current machine time zone.  To work around this we have to figure out through more calls to
       gmtime and mktime what that TZ offset is so we can later remove it from the original result of mktime */
    timeA = mktime (&tmA);
#if !defined( __arm ) && !defined(__unix__)
    MEMCPY( &tmB, gmtime( &timeA ), SIZEOF(tmB) );
#else
    MEMCPY( &tmB, localtime( &timeA ), SIZEOF(tmB) );
#endif
    timeB = mktime (&tmB);
   
    if( timeB > timeA )
    {
        timeA -= (timeB - timeA);
    }
    else
    {
        timeA += (timeA - timeB );
    }

    *ptimet = timeA;
    return TRUE;
}


DRM_BOOL DRM_API OEM_SystemTimeToFileTime (IN const DRMSYSTEMTIME *psystime,
                                           OUT      DRMFILETIME   *pfiletime)
{
    DRM_UINT64 ui64;
    time_t     timeA = 0;

    if (!_SystemTimeToTime_t(psystime, &timeA))
    {
        return FALSE;
    }

    _time_t_To_FILETIME (timeA, pfiletime);
    
    FILETIME_TO_UI64( *pfiletime, ui64 );
    DRM_UI64Add( ui64, DRM_UI64(psystime->wMilliseconds * 10000) );
    UI64_TO_FILETIME( ui64, *pfiletime );
    
    return TRUE;
}

DRM_BOOL DRM_API OEM_FileTimeToSystemTime (IN const DRMFILETIME *pfiletime,
                                           OUT DRMSYSTEMTIME    *psystime)
{
    DRM_UINT64 tics;
    DRM_UINT64 ui64;
    struct tm *ptm   = NULL;
    time_t     timeA = 0;
    DRM_BOOL   fOK   = FALSE;
    DRM_DWORD  cMilliseconds = 0;

    if ( pfiletime == NULL  ||  psystime == NULL )
    {
        return FALSE;
    }

    FILETIME_TO_UI64( *pfiletime, ui64 );
    FILETIME_TO_UI64( *pfiletime, tics );

    cMilliseconds  = DRM_I64ToUI32(DRM_UI2I64(DRM_UI64Mod( ui64, DRM_UI64( C_TICS_PER_SECOND ) )));
    cMilliseconds /= 10000;
    
    /* convert to seconds */
    
    tics = DRM_UI64Div( tics, DRM_UI64(C_TICS_PER_SECOND) );
    
    /* change base to ANSI time's */
    
    tics = DRM_UI64Sub( tics, C_SECONDS_FROM_1601_TO_1970);
        
    timeA = (time_t) DRM_I64ToUI32(DRM_UI2I64(tics));
    
#if !defined( __arm ) && !defined(__unix__)
    if ((ptm = gmtime (&timeA)) != NULL)
#else
    if ((ptm = localtime (&timeA)) != NULL)
#endif
    {
        _struct_tm_To_SYSTEMTIME (ptm, psystime);
        psystime->wMilliseconds = (DRM_WORD) cMilliseconds;
        fOK = TRUE;
    }

    return fOK;
}

DRM_VOID DRM_API OEM_GetDeviceTime (OUT DRMFILETIME *pfiletime)
{
    time_t timeNow = 0;    

    if ( pfiletime != NULL )
    {
        time (&timeNow);
        _time_t_To_FILETIME (timeNow, pfiletime);
    }
}

DRM_VOID DRM_API OEM_GetDeviceDateTime (OUT DRMSYSTEMTIME* psystime)
{
    time_t     timeNow = 0;
    struct tm *ptm = NULL;

    if ( psystime != NULL )
    {
        time (&timeNow);
        
#if !defined( __arm ) && !defined(__unix__)
        if ((ptm = gmtime (&timeNow)) != NULL)
#else
        if ((ptm = localtime (&timeNow)) != NULL)
#endif
        {
            _struct_tm_To_SYSTEMTIME (ptm, psystime);
        }
    }
}

DRM_VOID DRM_API OEM_SetSystemTime(IN DRMSYSTEMTIME* psystime)
{
    struct tm *plocaltime = NULL;
    time_t     timeA      = 0;

    /* convert it to time_t structure  */
    if (psystime != NULL  
     && _SystemTimeToTime_t(psystime, &timeA))
    {
        /* convert gmt time from time_t to local time in struct tm */
        plocaltime = localtime(&timeA);

#if !defined( __TMS320C55X__ ) && !defined( __arm ) && !defined(WINNT) && !defined(__unix__)
        /*OEMNOTE: this function is not in ANSI C.  Setting the system time is now entirely OS-dependent and there is no
        **         standard ANSI C way to do it.  Porting kit users should replace this with a non-portable API call 
        **         _setsystem() expects a local time 
        */
        
		_setsystime (plocaltime, 0);
		
#endif    
    }
}

DRM_DWORD DRM_API OEM_GetTickCount()
{
    return clock();
}

