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

#include <drmcommon.h>
#include <drmbase64.h>
#include <drmcrt.h>
#include <drmutilities.h>
#include <drmprofile.h>

#if DRM_SUPPORT_PROFILING

DRM_PROFILE_CONTEXT g_contextPRO = { 0, 0, 0, 0, { 0 } };

const DRM_WCHAR *const g_pwszEnteringFunction = (const DRM_WCHAR *const)
{ ONE_WCHAR('E', '\0'), ONE_WCHAR('n', '\0'), ONE_WCHAR('t', '\0'), ONE_WCHAR('e', '\0'),
  ONE_WCHAR('r', '\0'), ONE_WCHAR('i', '\0'), ONE_WCHAR('n', '\0'), ONE_WCHAR('g', '\0'), 
  ONE_WCHAR(' ', '\0'), ONE_WCHAR('F', '\0'), ONE_WCHAR('u', '\0'), ONE_WCHAR('n', '\0')
  ONE_WCHAR('c', '\0'), ONE_WCHAR('t', '\0'), ONE_WCHAR('i', '\0'), ONE_WCHAR('o', '\0')
  ONE_WCHAR('n', '\0'), ONE_WCHAR('\0', '\0') };
const DRM_WCHAR *const g_pwszLeavingFunction  = (const DRM_WCHAR *const)
{ ONE_WCHAR('L', '\0'), ONE_WCHAR('e', '\0'), ONE_WCHAR('a', '\0'), ONE_WCHAR('v', '\0'),
  ONE_WCHAR('i', '\0'), ONE_WCHAR('n', '\0'), ONE_WCHAR('g', '\0'), 
  ONE_WCHAR(' ', '\0'), ONE_WCHAR('F', '\0'), ONE_WCHAR('u', '\0'), ONE_WCHAR('n', '\0')
  ONE_WCHAR('c', '\0'), ONE_WCHAR('t', '\0'), ONE_WCHAR('i', '\0'), ONE_WCHAR('o', '\0')
  ONE_WCHAR('n', '\0'), ONE_WCHAR('\0', '\0') };


#if !DRM_PROFILE_USETICKCOUNT
/* calculate the interval of time between  */

static void _CalculateInterval(DRMSYSTEMTIME *f_psystemtimeEnter,
                               DRMSYSTEMTIME *f_psystemtimeExit)
{
    DRM_WORD wHour         = 0;
    DRM_WORD wMinute       = 0;
    DRM_WORD wSecond       = 0;
    DRM_WORD wMilliseconds = 0;

    if (f_psystemtimeExit->wHour > f_psystemtimeEnter->wHour)
    {
        wMinute = 60 * (f_psystemtimeExit->wHour - f_psystemtimeEnter->wHour);
    }

    if (f_psystemtimeExit->wMinute > f_psystemtimeEnter->wMinute)
    {
        wMinute += (f_psystemtimeExit->wMinute - f_psystemtimeEnter->wMinute);
    }
    else
    {
        wMinute -= (f_psystemtimeEnter->wMinute - f_psystemtimeExit->wMinute);
    }

    if (f_psystemtimeExit->wSecond >= f_psystemtimeEnter->wSecond)
    {
        wSecond = (f_psystemtimeExit->wSecond - f_psystemtimeEnter->wSecond);
    }
    else
    {
        wMinute--;
        wSecond = ((f_psystemtimeExit->wSecond + 60) - f_psystemtimeEnter->wSecond);
    }

    if (f_psystemtimeExit->wMilliseconds >= f_psystemtimeEnter->wMilliseconds)
    {
        wMilliseconds = (f_psystemtimeExit->wMilliseconds - f_psystemtimeEnter->wMilliseconds);
    }
    else
    {
        wSecond--;
        wSecond = ((f_psystemtimeExit->wMilliseconds + 1000) - f_psystemtimeEnter->wMilliseconds);
    }

    ZEROMEM(f_psystemtimeExit, SIZEOF(DRMSYSTEMTIME));

    f_psystemtimeExit->wMinute       = wMinute;
    f_psystemtimeExit->wSecond       = wSecond;
    f_psystemtimeExit->wMilliseconds = wMilliseconds;
}
#endif

/*Entering: \t\t\t\tHH:MM:SS:mmm ScopeName [comment]*/
/*Exiting:  \t\t\t\tHH:MM:SS:mmm ScopeName MM:SS:mmm [comment]*/

static void _WriteProfileEntry(DRM_SCOPE_CONTEXT*f_pScopeCtx,
                         const DRM_WCHAR  *const f_pwszScope,
                         const DRM_WCHAR  *const f_pwszComment,
                               DRM_BOOL          f_fEntering)
{
    DRM_DWORD   ich         = 0;
    DRM_DWORD   iDepth      = 0;
    DRM_DWORD   dwSeeks     = 0;
    DRM_DWORD   i;
    DRM_DWORD   cch           = DRM_wcslen(f_pwszScope);
#if !DRM_PROFILE_USETICKCOUNT
    DRM_WORD    wMilliseconds = f_pScopeCtx->enterTime.wMilliseconds;
#endif
    const DRM_CHAR chIndent      = (f_fEntering ? '>' : '<');
    DRMSYSTEMTIME *psystemtime   = NULL;
    DRMSYSTEMTIME  systemtimeExit;
#if DRM_PROFILE_USETICKCOUNT
    DRM_DWORD dwTotalTime;
#endif

    if (f_fEntering)
    {
#if !DRM_PROFILE_USETICKCOUNT
        OEM_GetDeviceDateTime(&systemtimeExit);
        psystemtime = &systemtimeExit;
#else
        psystemtime = &(f_pScopeCtx->enterTime);
#endif
        f_pScopeCtx->dwNumSeeks = g_contextPRO.dwNumSeeks;
    }
    else
    {
        OEM_GetDeviceDateTime(&systemtimeExit);
        psystemtime = &systemtimeExit;
        dwSeeks = g_contextPRO.dwNumSeeks - f_pScopeCtx->dwNumSeeks;

#if DRM_PROFILE_USETICKCOUNT
        dwTotalTime = OEM_GetTickCount() - f_pScopeCtx->dwTickCount;
#endif
    }

    if ( ( g_contextPRO.dwDepth * 2 ) + 14 >= DRM_PROFILE_BUFFER_SIZE )
    {
        return;
    }
    /* indent two chevrons per depth of call stack */

    for (iDepth = 0; iDepth < g_contextPRO.dwDepth; iDepth++)
    {
        PUT_CHAR(g_contextPRO.rgchBuffer, ich++, chIndent);
        PUT_CHAR(g_contextPRO.rgchBuffer, ich++, chIndent);
    }

    PUT_CHAR(g_contextPRO.rgchBuffer, ich++, ',');

    /* format the time */

    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (psystemtime->wHour   / 10) + '0');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (psystemtime->wHour   % 10) + '0');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  ':');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (psystemtime->wMinute / 10) + '0');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (psystemtime->wMinute % 10) + '0');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  ':');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (psystemtime->wSecond / 10) + '0');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (psystemtime->wSecond % 10) + '0');

#if !DRM_PROFILE_USETICKCOUNT
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  ':');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (wMilliseconds / 100) + '0');
    wMilliseconds /= 100;
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (wMilliseconds / 10) + '0');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  (wMilliseconds % 10) + '0');
#endif
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++,  ',');

    /* if the scope name is too long for the buffer then write as much as we can
       we don't want to do multiple file writes because that will skew the timing*/

    if ( ( ich + cch + 2 ) >= DRM_PROFILE_BUFFER_SIZE )
    {
        cch = ich;
        
        for ( ; ich < DRM_PROFILE_BUFFER_SIZE; ich++)
        {
            PUT_CHAR(g_contextPRO.rgchBuffer, ich, (DRM_CHAR)f_pwszScope[ich - cch]);
        }

    }
    else
    {
        cch = ich;
        for ( ; f_pwszScope[ich - cch] != 0; ich++)
        {
            PUT_CHAR(g_contextPRO.rgchBuffer, ich, (DRM_CHAR)f_pwszScope[ich - cch]);
        }
    	
        /* if we are exiting the scope then write the duration spent in it */

        if (! f_fEntering)
        {
            /*  ...but only if we have room */

            if ((ich + (10 + 7)) < DRM_PROFILE_BUFFER_SIZE)
            {
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, ',');

                
#if DRM_PROFILE_USETICKCOUNT
                for(i = 10000000;i > 0;i /= 10)
                {
                    PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (char)((dwTotalTime / i) % 10) + '0');
                }
#else
                _CalculateInterval(&(f_pScopeCtx->enterTime), 
                                  &systemtimeExit);

                wMilliseconds = systemtimeExit.wMilliseconds;
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (systemtimeExit.wMinute / 10) + '0');
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (systemtimeExit.wMinute % 10) + '0');
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, ':');
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (systemtimeExit.wSecond / 10) + '0');
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (systemtimeExit.wSecond % 10) + '0');


                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, ':');
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (wMilliseconds / 100) + '0');
                wMilliseconds /= 100;
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (wMilliseconds / 10) + '0');
                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (wMilliseconds % 10) + '0');
#endif

                PUT_CHAR(g_contextPRO.rgchBuffer, ich++, ',');
                for(i = 100000;i > 0;i /= 10)
                {
                    PUT_CHAR(g_contextPRO.rgchBuffer, ich++, (char)((dwSeeks / i) % 10) + '0');
                }                
            }
        }
        else
        {
            PUT_CHAR(g_contextPRO.rgchBuffer, ich++, ',');
        }

        /* append the optional comment without overflowing the buffer */

        if (f_pwszComment != NULL)
        {
            PUT_CHAR(g_contextPRO.rgchBuffer, ich++, ',');
            
            cch = ich;
            for ( ; (f_pwszComment[ich - cch] != 0) 
                && (ich + 2) < DRM_PROFILE_BUFFER_SIZE; ich++)
            {
                PUT_CHAR(g_contextPRO.rgchBuffer, ich, (DRM_CHAR)f_pwszComment[ich - cch]);
            }
        }
    }

    /* end with a newline */

    PUT_CHAR(g_contextPRO.rgchBuffer, ich++, '\x0d');
    PUT_CHAR(g_contextPRO.rgchBuffer, ich++, '\x0a');

    OEM_WriteFile(g_contextPRO.hfile, g_contextPRO.rgchBuffer, ich, &cch);
}

DRM_RESULT DRM_API DRM_PRO_StartProfiling(void)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_WCHAR  rgwchPath [DRM_MAX_PATH];
    DRM_DWORD  cch = NO_OF(rgwchPath);
    DRM_SCOPE_CONTEXT xqrK224;
    
    g_contextPRO.dwDepth = 0;

    ChkDR(OEM_GetProfileLogPath(rgwchPath, &cch));

    g_contextPRO.hfile = OEM_OpenFile(rgwchPath, 
                                      OEM_GENERIC_WRITE, 
                                      OEM_FILE_SHARE_NONE, 
                                      OEM_OPEN_ALWAYS, 
                                      OEM_ATTRIBUTE_NORMAL);

    ChkBOOL(g_contextPRO.hfile != OEM_INVALID_HANDLE_VALUE, DRM_E_FILEOPEN);

    if (!OEM_SetFilePointer(g_contextPRO.hfile, 0, OEM_FILE_END, NULL))
    {
        ChkDR(DRM_E_FILESEEKERROR);
    }

    g_contextPRO.fRunning = TRUE;

    DRM_PRO_EnterScope(&xqrK224, (const DRM_WCHAR *const)L"--------- LOG SESSION BEGINS ---------", (const DRM_WCHAR *const)L"", DRM_PROFILING_DONT_START);
ErrorExit:
    return dr;
}

DRM_RESULT DRM_API DRM_PRO_StopProfiling(void)
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_SCOPE_CONTEXT xqrK224;
    
#if DRM_PROFILE_USETICKCOUNT
    xqrK224.dwTickCount = OEM_GetTickCount();
#else
    OEM_GetDeviceDateTime(&(xqrK224.enterTime));
#endif
    xqrK224.dwNumSeeks = g_contextPRO.dwNumSeeks;
    DRM_PRO_LeaveScope(&xqrK224, (const DRM_WCHAR *const)L"--------- LOG SESSION ENDS ---------", (const DRM_WCHAR *const)L"");

    if (g_contextPRO.hfile != OEM_INVALID_HANDLE_VALUE)
    {
        OEM_CloseFile(g_contextPRO.hfile);

        g_contextPRO.hfile    = OEM_INVALID_HANDLE_VALUE;
    }

    g_contextPRO.fRunning = FALSE;

    return dr;
}

DRM_RESULT DRM_API DRM_PRO_EnterScope(
    IN DRM_SCOPE_CONTEXT *f_pScopeCtx,
    IN const DRM_WCHAR  *const f_pwszScope,
    IN const DRM_WCHAR  *const f_pwszComment,
    IN DRM_BOOL                f_fStartIfNot)
{
    DRM_RESULT dr      = DRM_SUCCESS;

    ChkArg(f_pwszScope != NULL);

    if (f_fStartIfNot
    &&  ! g_contextPRO.fRunning)
    {
        ChkDR(DRM_PRO_StartProfiling());
    }

#if DRM_PROFILE_USETICKCOUNT
    f_pScopeCtx->dwTickCount = OEM_GetTickCount();
#else
    OEM_GetDeviceDateTime(&(f_pScopeCtx->enterTime));
#endif
    f_pScopeCtx->dwNumSeeks = g_contextPRO.dwNumSeeks;

    g_contextPRO.dwDepth++;

    if (g_contextPRO.fRunning)
    {
        _WriteProfileEntry(f_pScopeCtx,
                           f_pwszScope,
                           f_pwszComment,
                           TRUE);
    }

ErrorExit:
    return dr;
}

DRM_RESULT DRM_API DRM_PRO_LeaveScope(
    IN DRM_SCOPE_CONTEXT *f_pScopeCtx,
    IN const DRM_WCHAR  *const f_pwszScope,
    IN const DRM_WCHAR  *const f_pwszComment)
{
    DRM_RESULT     dr = DRM_SUCCESS;

    if (g_contextPRO.fRunning)
    {
        _WriteProfileEntry(f_pScopeCtx,
                           f_pwszScope,
                           f_pwszComment,
                           FALSE);
    }

    g_contextPRO.dwDepth--;

    return dr;
}

#endif /* #if DRM_SUPPORT_PROFILING */
