////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                     /
// This library is free software; you can redistribute it and/or modify it under the terms of the  GNU  Lesser  General/
// Public License as published by the Free Software Foundation; either version 3.0 of the License, or (at your  option)/
// any later version.                                                                                                  /
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even  the  implied/
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more/
// details.                                                                                                            /
// You should have received a copy of the GNU Lesser General Public License along with this library.                   /
//                                                                                                                     /
// 2012-2021 (c) Baical                                                                                                /
//                                                                                                                     /
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once

#define SHARED_SEM_NULL           NULL


////////////////////////////////////////////////////////////////////////////////
//CShared
class CShared
{
    struct sShared
    {
        HANDLE  hMemory;
        HANDLE  hMutex;
        tXCHAR *pName;
    };

    enum eType
    {
        ETYPE_MUTEX   = 0,
        ETYPE_FILE       ,
        ETYPE_MAX        
    };

public:
    typedef sShared *hShared;
    typedef HANDLE   hSem;

    enum eLock
    {
        E_OK,
        E_TIMEOUT,
        E_ERROR,
        E_NOT_EXISTS
    };

    ////////////////////////////////////////////////////////////////////////////
    //CShared::Create
    static tBOOL Create(CShared::hShared *o_pHandle,
                        const tXCHAR     *i_pName, 
                        const tUINT8     *i_pData, 
                        tUINT16           i_wSize
                       )
    {
        sShared *l_pShared  = NULL;
        tBOOL    l_bReturn  = TRUE;
        DWORD    l_dwLen    = 0;
        wchar_t *l_pName    = NULL;
        BOOL     l_bRelease = FALSE;
        tUINT8  *l_pBuffer  = NULL;


        if (    (NULL == i_pName)
             || (NULL == i_pData)
             || (0    >= i_wSize)
             || (NULL == o_pHandle)
           )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        l_pShared = new sShared;
        if (NULL == l_pShared)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        memset(l_pShared, 0, sizeof(sShared));

        l_dwLen = (DWORD)wcslen(i_pName) + 128;
        l_pName = (wchar_t*)malloc(sizeof(wchar_t) * l_dwLen);
        l_pShared->pName = PStrDub(i_pName);

        if (    (NULL == l_pName)
             || (NULL == l_pShared->pName)
           )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }


        ////////////////////////////////////////////////////////////////////////////
        //create mutex and own it
        Create_Name(l_pName, l_dwLen, ETYPE_MUTEX, i_pName);

        l_pShared->hMutex = CreateMutexW(NULL, TRUE, l_pName);
        if (    (NULL == l_pShared->hMutex)
             || (ERROR_ALREADY_EXISTS == GetLastError())
           )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        l_bRelease = TRUE;

        ////////////////////////////////////////////////////////////////////////////
        //create shared memory object
        Create_Name(l_pName, l_dwLen, ETYPE_FILE, i_pName);

        l_pShared->hMemory = CreateFileMappingW(INVALID_HANDLE_VALUE, // use paging file
                                                NULL,                 // default security
                                                PAGE_READWRITE,       // read/write access
                                                0,                    // maximum object size (high-order tUINT32)
                                                i_wSize,              // maximum object size (low-order tUINT32)
                                                l_pName               // name of mapping object
                                               );     
    
        if (    (NULL == l_pShared->hMemory)
             || (ERROR_ALREADY_EXISTS == GetLastError())
           )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }
    
        l_pBuffer = (tUINT8*)MapViewOfFile(l_pShared->hMemory,    
                                           FILE_MAP_ALL_ACCESS,
                                           0,
                                           0,
                                           i_wSize
                                          );
    
        if (NULL == l_pBuffer)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        *o_pHandle = (CShared::hShared)l_pShared;

        __try
        {
            memcpy(l_pBuffer, i_pData, i_wSize);
        }
    
        __except (   GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR 
                   ? EXCEPTION_EXECUTE_HANDLER 
                   : EXCEPTION_CONTINUE_SEARCH
                 )
        {
            l_bReturn  = FALSE;
            goto l_lblExit;
        }

    l_lblExit:
        if (l_pName)
        {
            free(l_pName);
            l_pName = NULL;
        }

        if (l_pBuffer)
        {
            UnmapViewOfFile(l_pBuffer);
            l_pBuffer = NULL;
        }

        if (l_bRelease)
        {
            ReleaseMutex(l_pShared->hMutex);
        }

        if (FALSE == l_bReturn)
        {
            Close((hShared)l_pShared);
            l_pShared = NULL;

            if (o_pHandle)
            {
                *o_pHandle = NULL;
            }
        }

        return l_bReturn;
    }//Shared_Create


    ////////////////////////////////////////////////////////////////////////////
    //Read
    static tBOOL Read(const tXCHAR  *i_pName, 
                      tUINT8        *o_pData,
                      tUINT16        i_wSize
                     )
    {
        HANDLE   l_hMemory  = NULL;
        tBOOL    l_bReturn  = TRUE;
        DWORD    l_dwLen    = 0;
        wchar_t *l_pName    = NULL;
        tUINT8  *l_pBuffer  = NULL;

        if (    (NULL == i_pName)
             || (NULL == o_pData)
             || (0    >= i_wSize)
           )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        l_dwLen = (DWORD)wcslen(i_pName) + 128;
        l_pName = (wchar_t*)malloc(sizeof(wchar_t) * l_dwLen);

        if (NULL == l_pName)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        ////////////////////////////////////////////////////////////////////////
        //open shared memory object
        Create_Name(l_pName, l_dwLen, ETYPE_FILE, i_pName);

        l_hMemory = OpenFileMappingW(FILE_MAP_ALL_ACCESS,  // read/write access
                                     FALSE,                // do not inherit the name
                                     l_pName               // name of mapping object
                                    );           

        if (NULL == l_hMemory)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }
    
        l_pBuffer = (tUINT8*)MapViewOfFile(l_hMemory,    
                                           FILE_MAP_READ,
                                           0,
                                           0,
                                           i_wSize
                                          );
    
        if (NULL == l_pBuffer)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }
    
        __try
        {
            memcpy(o_pData, l_pBuffer, i_wSize);
        }
    
        __except (   (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR)
                   ? EXCEPTION_EXECUTE_HANDLER 
                   : EXCEPTION_CONTINUE_SEARCH
                 )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

    l_lblExit:
        if (l_pName)
        {
            free(l_pName);
            l_pName = NULL;
        }

        if (l_pBuffer)
        {
            UnmapViewOfFile(l_pBuffer);
            l_pBuffer = NULL;
        }

        if (l_hMemory)
        {
            CloseHandle(l_hMemory);
            l_hMemory = NULL;
        }

        return l_bReturn;
    }//Read



    ////////////////////////////////////////////////////////////////////////////
    //Write
    static tBOOL Write(const tXCHAR  *i_pName, 
                       const tUINT8  *i_pData,
                       tUINT16        i_wSize
                      )
    {
        HANDLE   l_hMemory  = NULL;
        tBOOL    l_bReturn  = TRUE;
        DWORD    l_dwLen    = 0;
        wchar_t *l_pName    = NULL;
        tUINT8  *l_pBuffer  = NULL;

        if (    (NULL == i_pName)
             || (NULL == i_pData)
             || (0    >= i_wSize)
           )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        l_dwLen = (DWORD)wcslen(i_pName) + 128;
        l_pName = (wchar_t*)malloc(sizeof(wchar_t) * l_dwLen);

        if (NULL == l_pName)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

        ////////////////////////////////////////////////////////////////////////
        //open shared memory object
        Create_Name(l_pName, l_dwLen, ETYPE_FILE, i_pName);

        l_hMemory = OpenFileMappingW(FILE_MAP_ALL_ACCESS,  // read/write access
                                     FALSE,                // do not inherit the name
                                     l_pName               // name of mapping object
                                    );           

        if (NULL == l_hMemory)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }
    
        l_pBuffer = (tUINT8*)MapViewOfFile(l_hMemory,    
                                           FILE_MAP_WRITE,
                                           0,
                                           0,
                                           i_wSize
                                          );
    
        if (NULL == l_pBuffer)
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }
    
        __try
        {
            memcpy(l_pBuffer, i_pData, i_wSize);
        }
    
        __except (   (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR)
                   ? EXCEPTION_EXECUTE_HANDLER 
                   : EXCEPTION_CONTINUE_SEARCH
                 )
        {
            l_bReturn = FALSE;
            goto l_lblExit;
        }

    l_lblExit:
        if (l_pName)
        {
            free(l_pName);
            l_pName = NULL;
        }

        if (l_pBuffer)
        {
            UnmapViewOfFile(l_pBuffer);
            l_pBuffer = NULL;
        }

        if (l_hMemory)
        {
            CloseHandle(l_hMemory);
            l_hMemory = NULL;
        }

        return l_bReturn;
    }//Write


    ////////////////////////////////////////////////////////////////////////////
    //Lock
    static eLock Lock(const tXCHAR  *i_pName, CShared::hSem &o_rSem, tUINT32 i_dwTimeout_ms)
    {
        HANDLE   l_hMutex   = NULL;
        eLock    l_eReturn  = CShared::E_OK;
        DWORD    l_dwLen    = 0;
        wchar_t *l_pName    = NULL;

        o_rSem = SHARED_SEM_NULL;

        if (NULL == i_pName)
        {
            l_eReturn = CShared::E_ERROR;
            goto l_lblExit;
        }

        l_dwLen = (DWORD)wcslen(i_pName) + 128;
        l_pName = (wchar_t*)malloc(sizeof(wchar_t) * l_dwLen);

        if (NULL == l_pName)
        {
            l_eReturn = CShared::E_ERROR;
            goto l_lblExit;
        }

        ////////////////////////////////////////////////////////////////////////
        //open mutex and own it
        Create_Name(l_pName, l_dwLen, ETYPE_MUTEX, i_pName);

        l_hMutex = OpenMutexW(MUTEX_ALL_ACCESS, FALSE, l_pName);
        if (NULL == l_hMutex)
        {
            l_eReturn = CShared::E_NOT_EXISTS;
            goto l_lblExit;
        }

        if (WAIT_OBJECT_0 != WaitForSingleObject(l_hMutex, i_dwTimeout_ms))
        {
            l_eReturn = CShared::E_TIMEOUT;
            goto l_lblExit;
        }

    l_lblExit:
        if (l_pName)
        {
            free(l_pName);
            l_pName = NULL;
        }

        if (SHARED_SEM_NULL != l_hMutex)
        {
            if (CShared::E_OK == l_eReturn)
            {
                o_rSem = l_hMutex;
            }
            else 
            {
                CloseHandle(l_hMutex);
                l_hMutex = SHARED_SEM_NULL;
            }
        }

        return l_eReturn;
    }//Lock


    ////////////////////////////////////////////////////////////////////////////
    //UnLock
    static eLock UnLock(CShared::hSem &io_rSem)
    {
        HANDLE  l_hMutex  = NULL;
        eLock   l_eReturn = CShared::E_ERROR;

        if (SHARED_SEM_NULL == io_rSem)
        {
            l_eReturn = CShared::E_NOT_EXISTS;
            goto l_lblExit;
        }

        ReleaseMutex(io_rSem);

    l_lblExit:
        if (io_rSem)
        {
            CloseHandle(io_rSem);
            io_rSem = SHARED_SEM_NULL;
        }

        return l_eReturn;
    }//UnLock


    ////////////////////////////////////////////////////////////////////////////
    //GetSemName
    static const tXCHAR* GetName(hShared i_pShared)
    {
        if (NULL == i_pShared)
        {
            return FALSE;
        }

        return i_pShared->pName;
    }

    ////////////////////////////////////////////////////////////////////////////
    //Close
    static tBOOL Close(hShared i_hShared)
    {
        if (NULL == i_hShared)
        {
            return FALSE;
        }

        //if (i_hShared->hMutex)
        //{
        //    WaitForSingleObject(i_hShared->hMutex, 3000);
        //}

        if (i_hShared->hMemory)
        {
            CloseHandle(i_hShared->hMemory);
            i_hShared->hMemory = NULL;
        }

        if (i_hShared->hMutex)
        {
            CloseHandle(i_hShared->hMutex);
            i_hShared->hMutex = NULL;
        }

        if (i_hShared->pName)
        {
            PStrFreeDub(i_hShared->pName);
            i_hShared->pName = NULL;
        }

        delete i_hShared;

        return TRUE;
    }//Shared_Close

    ////////////////////////////////////////////////////////////////////////////
    //UnLink - linux required it due to fact that life time of shared object is
    //almost infinit, and cleanup should be done manually
    static tBOOL UnLink(const tXCHAR  *i_pName)
    {
        UNUSED_ARG(i_pName); 
        return TRUE; 
    }//UnLink


private:
    ////////////////////////////////////////////////////////////////////////////
    //Create_Name
    static tBOOL Create_Name(tXCHAR        *o_pName, 
                             size_t         i_szName,
                             eType          i_eType,
                             const tXCHAR  *i_pPostfix
                            )
    {
        if (    (NULL == o_pName)
             || (16 >= i_szName)
             || (ETYPE_MAX <= i_eType)
             || (NULL == i_pPostfix)
           )
        {
            return FALSE;
        }

        FILETIME l_tProcess_Time = {0};
        FILETIME l_tStub_01      = {0};
        FILETIME l_tStub_02      = {0};
        FILETIME l_tStub_03      = {0};

        GetProcessTimes(GetCurrentProcess(),
                        &l_tProcess_Time,
                        &l_tStub_01,
                        &l_tStub_02,
                        &l_tStub_03
                        );

        if (ETYPE_MUTEX == i_eType)
        {
            swprintf_s(o_pName, 
                       i_szName, 
                       L"Local\\m%d%d%d%s", 
                       GetCurrentProcessId(), 
                       l_tProcess_Time.dwHighDateTime,
                       l_tProcess_Time.dwLowDateTime,
                       i_pPostfix
                      );
        }
        else if (ETYPE_FILE == i_eType)
        {
            swprintf_s(o_pName, 
                       i_szName, 
                       L"Local\\f%d%d%d%s", 
                       GetCurrentProcessId(), 
                       l_tProcess_Time.dwHighDateTime,
                       l_tProcess_Time.dwLowDateTime,
                       i_pPostfix
                      );
        }

        return TRUE;
    }//Create_Name
};








