/************************************************************
    Module Name: MeteredSection.c
    Author: Dan Chou
    Description: Implements the metered section synchronization object
************************************************************/

#include <windows.h>
#include <tchar.h>
#include "MeteredSection.h"
#include "../../src/fmod_memory.h"

// Internal function declarations
BOOL InitMeteredSection(LPMETERED_SECTION lpMetSect, LONG lInitialCount, 
      LONG lMaximumCount, LPCTSTR lpName, BOOL bOpenOnly);
BOOL CreateMetSectEvent(LPMETERED_SECTION lpMetSect, LPCTSTR lpName, BOOL 
      bOpenOnly);
BOOL CreateMetSectFileView(LPMETERED_SECTION lpMetSect, LONG lInitialCount, 
      LONG lMaximumCount, LPCTSTR lpName, BOOL bOpenOnly);
void GetMeteredSectionLock(LPMETERED_SECTION lpMetSect);
void ReleaseMeteredSectionLock(LPMETERED_SECTION lpMetSect);

/*
 * CreateMeteredSection
 */
LPMETERED_SECTION CreateMeteredSection(LONG lInitialCount, 
      LONG lMaximumCount, LPCTSTR lpName)
{
    LPMETERED_SECTION lpMetSect;

    // Verify the parameters
    if ((lMaximumCount < 1)             ||
        (lInitialCount > lMaximumCount) ||
        (lInitialCount < 0)             ||
        ((lpName) && (_tcslen(lpName) > MAX_METSECT_NAMELEN)))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    // Allocate memory for the metered section
    lpMetSect = (LPMETERED_SECTION)FMOD_Memory_Alloc(sizeof(METERED_SECTION));

    // If the memory for the metered section was allocated okay, 
    // initialize it
    if (lpMetSect)
    {
        if (!InitMeteredSection(lpMetSect, lInitialCount, 
      lMaximumCount, lpName, FALSE))
        {
            CloseMeteredSection(lpMetSect);
            lpMetSect = NULL;
        }
    }
    return lpMetSect;
}

/*
 * OpenMeteredSection
 */
#ifndef _WIN32_WCE
LPMETERED_SECTION OpenMeteredSection(LPCTSTR lpName)
{
    LPMETERED_SECTION lpMetSect = NULL;

    if (lpName)
    {
        lpMetSect = (LPMETERED_SECTION)FMOD_Memory_Alloc(sizeof(METERED_SECTION));

        // If the memory for the metered section was allocated okay
        if (lpMetSect)
        {
            if (!InitMeteredSection(lpMetSect, 0, 0, lpName, TRUE))
            {
                // Metered section failed to initialize
                CloseMeteredSection(lpMetSect);
                lpMetSect = NULL;
            }
        }
    }
    return lpMetSect;
}
#endif

/*
 * EnterMeteredSection
 */
DWORD EnterMeteredSection(LPMETERED_SECTION lpMetSect, 
      DWORD dwMilliseconds)
{
    while (TRUE)
    {
        GetMeteredSectionLock(lpMetSect);

        // We have access to the metered section, everything we 
        // do now will be atomic
        if (lpMetSect->lpSharedInfo->lAvailableCount >= 1)
        {
            lpMetSect->lpSharedInfo->lAvailableCount--;
            ReleaseMeteredSectionLock(lpMetSect);
            return WAIT_OBJECT_0;
        }

        // Couldn't get in. Wait on the event object
        lpMetSect->lpSharedInfo->lThreadsWaiting++;
        ResetEvent(lpMetSect->hEvent);
        ReleaseMeteredSectionLock(lpMetSect);
        if (WaitForSingleObject(lpMetSect->hEvent, 
         dwMilliseconds) == WAIT_TIMEOUT)
        {
            return WAIT_TIMEOUT;
        }
    }
}

/*
 * LeaveMeteredSection
 */
BOOL LeaveMeteredSection(LPMETERED_SECTION lpMetSect, LONG lReleaseCount, 
         LPLONG lpPreviousCount)
{
    int iCount;
    GetMeteredSectionLock(lpMetSect);

    // Save the old value if they want it
    if (lpPreviousCount)
    {
        *lpPreviousCount = lpMetSect->lpSharedInfo->lAvailableCount;
    }

    // We have access to the metered section, 
    // everything we do now will be atomic
    if ((lReleaseCount < 0) ||
        (lpMetSect->lpSharedInfo->lAvailableCount+lReleaseCount >
         lpMetSect->lpSharedInfo->lMaximumCount))
    {
        ReleaseMeteredSectionLock(lpMetSect);
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    lpMetSect->lpSharedInfo->lAvailableCount += lReleaseCount;
    
    // Set the event the appropriate number of times
    lReleaseCount = 
      min(lReleaseCount,lpMetSect->lpSharedInfo->lThreadsWaiting);
    if (lpMetSect->lpSharedInfo->lThreadsWaiting)
    {
        for (iCount=0; iCount < lReleaseCount ; iCount++)
        {
            lpMetSect->lpSharedInfo->lThreadsWaiting--;
            SetEvent(lpMetSect->hEvent);
        }
    }
    ReleaseMeteredSectionLock(lpMetSect);
    return TRUE;
}

/*
 * CloseMeteredSection
 */
void CloseMeteredSection(LPMETERED_SECTION lpMetSect)
{
    if (lpMetSect)
    {
        // Clean up
        #ifdef FILEMAPPING
        if (lpMetSect->lpSharedInfo) UnmapViewOfFile(lpMetSect->lpSharedInfo);
        if (lpMetSect->hFileMap) CloseHandle(lpMetSect->hFileMap);
        #else
        DeleteCriticalSection(&lpMetSect->hCrit);
        #endif

        if (lpMetSect->hEvent) CloseHandle(lpMetSect->hEvent);
        FMOD_Memory_Free(lpMetSect);
    }
}

/*
 * InitMeteredSection
 */
BOOL InitMeteredSection(LPMETERED_SECTION lpMetSect, 
         LONG lInitialCount, LONG lMaximumCount,
                        LPCTSTR lpName, BOOL bOpenOnly)
{
    // Try to create the event object
    if (CreateMetSectEvent(lpMetSect, lpName, bOpenOnly))
    {
#ifdef FILEMAPPING
        // Try to create the memory mapped file
        if (CreateMetSectFileView(lpMetSect, lInitialCount, lMaximumCount, lpName, bOpenOnly))
#else        
        lpMetSect->lpSharedInfo = &lpMetSect->SharedInfo;
        lpMetSect->lpSharedInfo->lThreadsWaiting = 0;
        lpMetSect->lpSharedInfo->lAvailableCount = lInitialCount;
        lpMetSect->lpSharedInfo->lMaximumCount = lMaximumCount;
        InitializeCriticalSection(&lpMetSect->hCrit);
        InterlockedExchange((long *)&(lpMetSect->lpSharedInfo->fInitialized), TRUE);
#endif
        {
            return TRUE;
        }
    }

    // Error occured, return FALSE so the caller knows to clean up
    return FALSE;
}

/*
 * CreateMetSectEvent
 */
BOOL CreateMetSectEvent(LPMETERED_SECTION lpMetSect, 
         LPCTSTR lpName, BOOL bOpenOnly)
{
    TCHAR sz[MAX_PATH];
    if (lpName)
    {
        //wsprintf(sz, _TEXT("DKC_MSECT_EVT_%s"), lpName);

#ifndef _WIN32_WCE
        if (bOpenOnly)
        {
            lpMetSect->hEvent = OpenEvent(0, FALSE, sz);
        }
        else
        {
#endif
            // Create an auto-reset named event object
            lpMetSect->hEvent = CreateEvent(NULL, FALSE, FALSE, sz);
#ifndef _WIN32_WCE
        }
#endif
    }
    else
    {
        // Create an auto-reset unnamed event object
        lpMetSect->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    }
    return (lpMetSect->hEvent ? TRUE : FALSE);
}

#ifdef FILEMAPPING
/*
 * CreateMetSectFileView
 */
BOOL CreateMetSectFileView(LPMETERED_SECTION lpMetSect, 
         LONG lInitialCount, LONG lMaximumCount,
                           LPCTSTR lpName, BOOL bOpenOnly)
{
    TCHAR sz[MAX_PATH];
    DWORD dwLastError; 

    if (lpName)
    {
        //wsprintf(sz, _TEXT("DKC_MSECT_MMF_%s"), lpName);

#ifndef _WIN32_WCE
        if (bOpenOnly)
        {
            lpMetSect->hFileMap = OpenFileMapping(0, FALSE, sz);
        }
        else
        {
#endif
            // Create a named file mapping
            lpMetSect->hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, 
         NULL, PAGE_READWRITE, 0, sizeof(METSECT_SHARED_INFO), sz);
#ifndef _WIN32_WCE
        }
#endif
    }
    else
    {
        // Create an unnamed file mapping
        lpMetSect->hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, 
         NULL, PAGE_READWRITE, 0, sizeof(METSECT_SHARED_INFO), NULL);
    }
 
    // Map a view of the file
    if (lpMetSect->hFileMap)
    {
        dwLastError = GetLastError();
        lpMetSect->lpSharedInfo = (LPMETSECT_SHARED_INFO) 
         MapViewOfFile(lpMetSect->hFileMap, FILE_MAP_WRITE, 0, 0, 0);
        if (lpMetSect->lpSharedInfo)
        {
            if (dwLastError != ERROR_ALREADY_EXISTS)
            {
                lpMetSect->lpSharedInfo->lSpinLock = 0;
                lpMetSect->lpSharedInfo->lThreadsWaiting = 0;
                lpMetSect->lpSharedInfo->lAvailableCount = lInitialCount;
                lpMetSect->lpSharedInfo->lMaximumCount = lMaximumCount;
                InterlockedExchange((long *)&(lpMetSect->lpSharedInfo->fInitialized), TRUE);
            }
            else
            {   // Already exists; wait for it to be initialized by the creator
              while (!lpMetSect->lpSharedInfo->fInitialized) Sleep(0);
            }
            return TRUE;
        }
    }

    return FALSE;
}
#endif

/*
 * GetMeteredSectionLock
 */
void GetMeteredSectionLock(LPMETERED_SECTION lpMetSect)
{
#if FILEMAPPING
    // Spin and get access to the metered section lock
    while (InterlockedExchange(&(lpMetSect->lpSharedInfo->lSpinLock), 1) != 0)
        Sleep(1);
#else
    EnterCriticalSection(&lpMetSect->hCrit);
#endif
}

/*
 * ReleaseMeteredSectionLock
 */
void ReleaseMeteredSectionLock(LPMETERED_SECTION lpMetSect)
{
#if FILEMAPPING
    InterlockedExchange(&(lpMetSect->lpSharedInfo->lSpinLock), 0);
#else
    LeaveCriticalSection(&lpMetSect->hCrit);
#endif
}