/*
 * PROJECT:     ReactOS Clipboard Viewer
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     Clipboard file format helper functions.
 * COPYRIGHT:   Copyright 2015-2018 Ricardo Hanke
 *              Copyright 2015-2018 Hermes Belusca-Maito
 */

#include "precomp.h"

static HGLOBAL ClipboardReadMemoryBlock(HANDLE hFile, DWORD dwOffset, DWORD dwLength)
{
    HGLOBAL hData;
    LPVOID lpData;
    DWORD dwBytesRead;

    hData = GlobalAlloc(GHND, dwLength);
    if (!hData)
        return NULL;

    lpData = GlobalLock(hData);
    if (!lpData)
    {
        GlobalFree(hData);
        return NULL;
    }

    if (SetFilePointer(hFile, dwOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
    {
        GlobalUnlock(hData);
        GlobalFree(hData);
        return NULL;
    }

    if (!ReadFile(hFile, lpData, dwLength, &dwBytesRead, NULL))
    {
        GlobalUnlock(hData);
        GlobalFree(hData);
        return NULL;
    }

    GlobalUnlock(hData);

    return hData;
}

static BOOL ClipboardReadMemory(HANDLE hFile, DWORD dwFormat, DWORD dwOffset, DWORD dwLength, WORD FileIdentifier, PVOID lpFormatName)
{
    HGLOBAL hData;
    DWORD dwTemp = 0;

    hData = ClipboardReadMemoryBlock(hFile, dwOffset, dwLength);
    if (!hData)
        return FALSE;

    if ((dwFormat >= 0xC000) && (dwFormat <= 0xFFFF))
    {
        if (FileIdentifier == CLIP_FMT_31)
            dwTemp = RegisterClipboardFormatA((LPCSTR)lpFormatName);
        else if ((FileIdentifier == CLIP_FMT_NT) || (FileIdentifier == CLIP_FMT_BK))
            dwTemp = RegisterClipboardFormatW((LPCWSTR)lpFormatName);

        if (!dwTemp)
        {
            GlobalFree(hData);
            return FALSE;
        }
    }
    else
    {
        dwTemp = dwFormat;
    }

    if (!SetClipboardData(dwTemp, hData))
    {
        GlobalFree(hData);
        return FALSE;
    }

    return TRUE;
}

static BOOL ClipboardWriteMemory(HANDLE hFile, DWORD dwFormat, DWORD dwOffset, PDWORD pdwLength)
{
    HGLOBAL hData;
    LPVOID lpData;
    DWORD dwBytesWritten;

    hData = GetClipboardData(dwFormat);
    if (!hData)
        return FALSE;

    lpData = GlobalLock(hData);
    if (!lpData)
        return FALSE;

    *pdwLength = GlobalSize(hData);

    if (SetFilePointer(hFile, dwOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
    {
        GlobalUnlock(hData);
        return FALSE;
    }

    if (!WriteFile(hFile, lpData, *pdwLength, &dwBytesWritten, NULL))
    {
        GlobalUnlock(hData);
        return FALSE;
    }

    GlobalUnlock(hData);

    return TRUE;
}

static BOOL ClipboardReadPalette(HANDLE hFile, DWORD dwOffset, DWORD dwLength)
{
    LPLOGPALETTE lpPalette;
    HPALETTE hPalette;
    HGLOBAL hData;

    hData = ClipboardReadMemoryBlock(hFile, dwOffset, dwLength);
    if (!hData)
    {
        return FALSE;
    }

    lpPalette = GlobalLock(hData);
    if (!lpPalette)
    {
        GlobalFree(hData);
        return FALSE;
    }

    hPalette = CreatePalette(lpPalette);
    if (!hPalette)
    {
        GlobalUnlock(hData);
        GlobalFree(hData);
        SetLastError(ERROR_OUTOFMEMORY);
        return FALSE;
    }

    GlobalUnlock(hData);
    GlobalFree(hData);

    if (!SetClipboardData(CF_PALETTE, hPalette))
    {
        DeleteObject(hPalette);
        return FALSE;
    }

    return TRUE;
}

static BOOL ClipboardReadMetafile(HANDLE hFile, DWORD dwOffset, DWORD dwLength)
{
    HMETAFILE hMf;
    HGLOBAL hData;
    LPVOID lpData;

    hData = ClipboardReadMemoryBlock(hFile, dwOffset, dwLength);
    if (!hData)
    {
        return FALSE;
    }

    lpData = GlobalLock(hData);
    if (!lpData)
    {
        GlobalFree(hData);
        return FALSE;
    }

    hMf = SetMetaFileBitsEx(dwLength, lpData);

    GlobalUnlock(hData);
    GlobalFree(hData);

    if (!hMf)
    {
        SetLastError(ERROR_OUTOFMEMORY);
        return FALSE;
    }

    if (!SetClipboardData(CF_METAFILEPICT, hMf))
    {
        DeleteMetaFile(hMf);
        return FALSE;
    }

    return TRUE;
}

static BOOL ClipboardReadEnhMetafile(HANDLE hFile, DWORD dwOffset, DWORD dwLength)
{
    HENHMETAFILE hEmf;
    HGLOBAL hData;
    LPVOID lpData;

    hData = ClipboardReadMemoryBlock(hFile, dwOffset, dwLength);
    if (!hData)
    {
        return FALSE;
    }

    lpData = GlobalLock(hData);
    if (!lpData)
    {
        GlobalFree(hData);
        return FALSE;
    }

    hEmf = SetEnhMetaFileBits(dwLength, lpData);

    GlobalUnlock(hData);
    GlobalFree(hData);

    if (!hEmf)
    {
        SetLastError(ERROR_OUTOFMEMORY);
        return FALSE;
    }

    if (!SetClipboardData(CF_ENHMETAFILE, hEmf))
    {
        DeleteEnhMetaFile(hEmf);
        return FALSE;
    }

    return TRUE;
}

static BOOL ClipboardReadBitmap(HANDLE hFile, DWORD dwOffset, DWORD dwLength)
{
    HGLOBAL hData;
    HBITMAP hBitmap;
    LPBITMAP lpBitmap;

    hData = ClipboardReadMemoryBlock(hFile, dwOffset, dwLength);
    if (!hData)
    {
        return FALSE;
    }

    lpBitmap = GlobalLock(hData);
    if (!lpBitmap)
    {
        GlobalFree(hData);
        return FALSE;
    }

    lpBitmap->bmBits = lpBitmap + sizeof(BITMAP) + 1;

    hBitmap = CreateBitmapIndirect(lpBitmap);

    GlobalUnlock(hData);
    GlobalFree(hData);

    if (!hBitmap)
    {
        SetLastError(ERROR_OUTOFMEMORY);
        return FALSE;
    }

    if (!SetClipboardData(CF_BITMAP, hBitmap))
    {
        DeleteObject(hBitmap);
        return FALSE;
    }

    return TRUE;
}

void ReadClipboardFile(LPCWSTR lpFileName)
{
    CLIPFILEHEADER ClipFileHeader;
    CLIPFORMATHEADER ClipFormatArray;
    NTCLIPFILEHEADER NtClipFileHeader;
    NTCLIPFORMATHEADER NtClipFormatArray;
    PVOID pClipFileHeader;
    PVOID pClipFormatArray;
    DWORD SizeOfFileHeader, SizeOfFormatHeader;

    WORD wFileIdentifier;
    WORD wFormatCount;
    DWORD dwFormatID;
    DWORD dwLenData;
    DWORD dwOffData;
    PVOID szName;

    HANDLE hFile;
    DWORD dwBytesRead;
    BOOL bResult;
    int i;

    /* Open the file for read access */
    hFile = CreateFileW(lpFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        ShowLastWin32Error(Globals.hMainWnd);
        goto done;
    }

    /* Just read enough bytes to get the clipboard file format ID */
    if (!ReadFile(hFile, &wFileIdentifier, sizeof(wFileIdentifier), &dwBytesRead, NULL))
    {
        ShowLastWin32Error(Globals.hMainWnd);
        goto done;
    }

    /* Set data according to the clipboard file format ID */
    switch (wFileIdentifier)
    {
        case CLIP_FMT_31:
            SizeOfFileHeader   = sizeof(CLIPFILEHEADER);
            SizeOfFormatHeader = sizeof(CLIPFORMATHEADER);
            pClipFileHeader    = &ClipFileHeader;
            pClipFormatArray   = &ClipFormatArray;
            break;

        case CLIP_FMT_NT:
        case CLIP_FMT_BK:
            SizeOfFileHeader   = sizeof(NTCLIPFILEHEADER);
            SizeOfFormatHeader = sizeof(NTCLIPFORMATHEADER);
            pClipFileHeader    = &NtClipFileHeader;
            pClipFormatArray   = &NtClipFormatArray;
            break;

        default:
            MessageBoxRes(Globals.hMainWnd, Globals.hInstance, ERROR_INVALID_FILE_FORMAT, 0, MB_ICONSTOP | MB_OK);
            goto done;
    }

    /* Completely read the header */
    SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
    if (!ReadFile(hFile, pClipFileHeader, SizeOfFileHeader, &dwBytesRead, NULL) ||
        dwBytesRead != SizeOfFileHeader)
    {
        ShowLastWin32Error(Globals.hMainWnd);
        goto done;
    }

    /* Get header data */
    switch (wFileIdentifier)
    {
        case CLIP_FMT_31:
            assert(wFileIdentifier == ((CLIPFILEHEADER*)pClipFileHeader)->wFileIdentifier);
            wFormatCount = ((CLIPFILEHEADER*)pClipFileHeader)->wFormatCount;
            break;

        case CLIP_FMT_NT:
        case CLIP_FMT_BK:
            assert(wFileIdentifier == ((NTCLIPFILEHEADER*)pClipFileHeader)->wFileIdentifier);
            wFormatCount = ((NTCLIPFILEHEADER*)pClipFileHeader)->wFormatCount;
            break;
    }

    /* Loop through the format data array */
    for (i = 0; i < wFormatCount; i++)
    {
        if (SetFilePointer(hFile, SizeOfFileHeader + i * SizeOfFormatHeader, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
        {
            ShowLastWin32Error(Globals.hMainWnd);
            goto done;
        }

        if (!ReadFile(hFile, pClipFormatArray, SizeOfFormatHeader, &dwBytesRead, NULL))
        {
            ShowLastWin32Error(Globals.hMainWnd);
            goto done;
        }

        /* Get format data */
        switch (wFileIdentifier)
        {
            case CLIP_FMT_31:
                dwFormatID = ((CLIPFORMATHEADER*)pClipFormatArray)->dwFormatID;
                dwLenData  = ((CLIPFORMATHEADER*)pClipFormatArray)->dwLenData;
                dwOffData  = ((CLIPFORMATHEADER*)pClipFormatArray)->dwOffData;
                szName     = ((CLIPFORMATHEADER*)pClipFormatArray)->szName;
                break;

            case CLIP_FMT_NT:
            case CLIP_FMT_BK:
                dwFormatID = ((NTCLIPFORMATHEADER*)pClipFormatArray)->dwFormatID;
                dwLenData  = ((NTCLIPFORMATHEADER*)pClipFormatArray)->dwLenData;
                dwOffData  = ((NTCLIPFORMATHEADER*)pClipFormatArray)->dwOffData;
                szName     = ((NTCLIPFORMATHEADER*)pClipFormatArray)->szName;
                break;
        }

        switch (dwFormatID)
        {
            case CF_OWNERDISPLAY:
            {
                break;
            }

            case CF_DSPBITMAP:
            case CF_BITMAP:
            {
                bResult = ClipboardReadBitmap(hFile, dwOffData, dwLenData);
                break;
            }

            case CF_DSPMETAFILEPICT:
            case CF_METAFILEPICT:
            {
                bResult = ClipboardReadMetafile(hFile, dwOffData, dwLenData);
                break;
            }

            case CF_DSPENHMETAFILE:
            case CF_ENHMETAFILE:
            {
                bResult = ClipboardReadEnhMetafile(hFile, dwOffData, dwLenData);
                break;
            }

            case CF_PALETTE:
            {
                bResult = ClipboardReadPalette(hFile, dwOffData, dwLenData);
                break;
            }

            default:
            {
                if ((dwFormatID < CF_PRIVATEFIRST) || (dwFormatID > CF_PRIVATELAST))
                {
                    bResult = ClipboardReadMemory(hFile, dwFormatID, dwOffData, dwLenData, wFileIdentifier, szName);
                }
                break;
            }
        }

        if (!bResult)
            ShowLastWin32Error(Globals.hMainWnd);
    }

done:
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    return;
}

void WriteClipboardFile(LPCWSTR lpFileName, WORD wFileIdentifier)
{
    CLIPFILEHEADER ClipFileHeader;
    CLIPFORMATHEADER ClipFormatArray;
    NTCLIPFILEHEADER NtClipFileHeader;
    NTCLIPFORMATHEADER NtClipFormatArray;
    PVOID pClipFileHeader;
    PVOID pClipFormatArray;
    DWORD SizeOfFileHeader, SizeOfFormatHeader;

    WORD wFormatCount;
    DWORD dwFormatID;
    DWORD dwLenData;
    DWORD dwOffData;
    // PVOID szName;

    HANDLE hFile;
    DWORD dwBytesWritten;
    int i;

    /* Create the file for write access */
    hFile = CreateFileW(lpFileName, GENERIC_WRITE, 0, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        ShowLastWin32Error(Globals.hMainWnd);
        goto done;
    }

    wFormatCount = CountClipboardFormats();

    /* Select the file format and setup the header according to the clipboard file format ID */
    switch (wFileIdentifier)
    {
        case CLIP_FMT_31:
            SizeOfFileHeader   = sizeof(CLIPFILEHEADER);
            SizeOfFormatHeader = sizeof(CLIPFORMATHEADER);
            pClipFileHeader    = &ClipFileHeader;
            pClipFormatArray   = &ClipFormatArray;

            ClipFileHeader.wFileIdentifier = CLIP_FMT_31; // wFileIdentifier
            ClipFileHeader.wFormatCount    = wFormatCount;
            break;

        case CLIP_FMT_NT:
        case CLIP_FMT_BK:
            SizeOfFileHeader   = sizeof(NTCLIPFILEHEADER);
            SizeOfFormatHeader = sizeof(NTCLIPFORMATHEADER);
            pClipFileHeader    = &NtClipFileHeader;
            pClipFormatArray   = &NtClipFormatArray;

            NtClipFileHeader.wFileIdentifier = CLIP_FMT_NT; // wFileIdentifier
            NtClipFileHeader.wFormatCount    = wFormatCount;
            break;

        default:
            MessageBoxRes(Globals.hMainWnd, Globals.hInstance, ERROR_INVALID_FILE_FORMAT, 0, MB_ICONSTOP | MB_OK);
            goto done;
    }

    /* Write the header */
    SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
    if (!WriteFile(hFile, pClipFileHeader, SizeOfFileHeader, &dwBytesWritten, NULL) ||
        dwBytesWritten != SizeOfFileHeader)
    {
        ShowLastWin32Error(Globals.hMainWnd);
        goto done;
    }

    /* Compute where the data should start (after the file header and the format array) */
    dwOffData = SizeOfFileHeader + wFormatCount * SizeOfFormatHeader;

    /* Loop through each format and save the data */
    i = 0;
    dwFormatID = EnumClipboardFormats(0);
    while (dwFormatID)
    {
        if (i >= wFormatCount)
        {
            /* Must never happen! */
            assert(FALSE);
            break;
        }

        /* Write the clipboard data at the specified offset, and retrieve its length */
        if (!ClipboardWriteMemory(hFile, dwFormatID, dwOffData, &dwLenData))
            goto Cont;

        /* Write the format data header */
        switch (wFileIdentifier)
        {
            case CLIP_FMT_31:
                ZeroMemory(pClipFormatArray, sizeof(CLIPFORMATHEADER));
                ((CLIPFORMATHEADER*)pClipFormatArray)->dwFormatID = dwFormatID;
                ((CLIPFORMATHEADER*)pClipFormatArray)->dwLenData  = dwLenData;
                ((CLIPFORMATHEADER*)pClipFormatArray)->dwOffData  = dwOffData;
                RetrieveClipboardFormatName(Globals.hInstance,
                                            dwFormatID,
                                            FALSE,
                                            ((CLIPFORMATHEADER*)pClipFormatArray)->szName,
                                            ARRAYSIZE(((CLIPFORMATHEADER*)pClipFormatArray)->szName));
                break;

            case CLIP_FMT_NT:
            case CLIP_FMT_BK:
                ZeroMemory(pClipFormatArray, sizeof(NTCLIPFORMATHEADER));
                ((NTCLIPFORMATHEADER*)pClipFormatArray)->dwFormatID = dwFormatID;
                ((NTCLIPFORMATHEADER*)pClipFormatArray)->dwLenData  = dwLenData;
                ((NTCLIPFORMATHEADER*)pClipFormatArray)->dwOffData  = dwOffData;
                RetrieveClipboardFormatName(Globals.hInstance,
                                            dwFormatID,
                                            TRUE,
                                            ((NTCLIPFORMATHEADER*)pClipFormatArray)->szName,
                                            ARRAYSIZE(((NTCLIPFORMATHEADER*)pClipFormatArray)->szName));
                break;
        }

        if (SetFilePointer(hFile, SizeOfFileHeader + i * SizeOfFormatHeader, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
        {
            ShowLastWin32Error(Globals.hMainWnd);
            goto done;
        }

        if (!WriteFile(hFile, pClipFormatArray, SizeOfFormatHeader, &dwBytesWritten, NULL))
        {
            ShowLastWin32Error(Globals.hMainWnd);
            goto done;
        }

        /* Adjust the offset for the next data stream */
        dwOffData += dwLenData;

Cont:
        i++;
        dwFormatID = EnumClipboardFormats(dwFormatID);
    }

done:
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    return;
}
