//*@@@+++@@@@******************************************************************
//
// Copyright © Microsoft Corp.
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// • Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// • Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//*@@@---@@@@******************************************************************
#include <stdlib.h>
#include <string.h>

#include <JXRTest.h>

#pragma pack(push, 1)

#define BI_RGB 0
#define BI_BITFIELDS 3

#define BI_RGB555_MASK_B 0x001F
#define BI_RGB555_MASK_G 0x03E0
#define BI_RGB555_MASK_R 0x7C00

#define BI_RGB565_MASK_B 0x001F
#define BI_RGB565_MASK_G 0x07E0
#define BI_RGB565_MASK_R 0xF800

#define BI_RGB101010_MASK_B 0x000003FF
#define BI_RGB101010_MASK_G 0x000FFC00
#define BI_RGB101010_MASK_R 0x3FF00000

typedef struct tagBITMAPFILEHEADER { 
    U8 szBM[2];
    U32 uSize;
    U16 reserved1;
    U16 reserved2;
    U32 uOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER; 

typedef struct tagBITMAPINFOHEADER{
    U32 uSize;
    I32 iWidth;
    I32 iHeight;
    I16 iPlanes;
    I16 iBitCount;
    U32 uCompression;
    U32 uImageSize;
    I32 iPelsPerMeterX;
    I32 iPelsPerMeterY;
    U32 uColorUsed;
    U32 uColorImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

typedef struct tagBITMAPINFOHEADEREXT{
    U32 uA;
    U32 uB;
    U32 uC;
    U32 uD;
} BITMAPINFOHEADEREXT, *PBITMAPINFOHEADEREXT;


#pragma pack(pop)

//================================================================
// PKImageEncode_BMP
//================================================================
ERR WriteBMPHeader(
    PKImageEncode* pIE)
{
    ERR err = WMP_errSuccess;

    static U32 rguColorTable[256] = {0};
    size_t cbColorTable = 0;
    size_t cbLineS = 0;
    U32 i = 0;

    struct WMPStream* pS = pIE->pStream;
    BITMAPFILEHEADER bmpFH = { 0, };
    BITMAPINFOHEADER bmpIH = {sizeof(bmpIH), 0, };

    bmpFH.szBM[0] = 'B';
    bmpFH.szBM[1] = 'M';

    if (IsEqualGUID(&GUID_PKPixelFormat24bppRGB, &pIE->guidPixFormat) || IsEqualGUID(&GUID_PKPixelFormat24bppBGR, &pIE->guidPixFormat))
    {
        pIE->cbPixel = 3;
        cbColorTable = 0;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat32bppBGRA, &pIE->guidPixFormat) 
        || IsEqualGUID(&GUID_PKPixelFormat32bppBGR, &pIE->guidPixFormat)
        || IsEqualGUID(&GUID_PKPixelFormat32bppPBGRA, &pIE->guidPixFormat))
    {
        pIE->cbPixel = 4;
        cbColorTable = 0;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat8bppGray, &pIE->guidPixFormat))
    {
        pIE->cbPixel = 1;

        cbColorTable = sizeof(rguColorTable);
        for (i = 0; i < sizeof2(rguColorTable); ++i)
        {
            rguColorTable[i] = i | (i << 8) | (i << 16);
        }
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat16bppRGB555, &pIE->guidPixFormat))
    {
        pIE->cbPixel = 2;
        bmpIH.uCompression = BI_BITFIELDS;

        cbColorTable = sizeof(rguColorTable[0]) * 3;
        rguColorTable[0] = BI_RGB555_MASK_R;
        rguColorTable[1] = BI_RGB555_MASK_G;
        rguColorTable[2] = BI_RGB555_MASK_B;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat16bppRGB565, &pIE->guidPixFormat))
    {
        pIE->cbPixel = 2;
        bmpIH.uCompression = BI_BITFIELDS;

        cbColorTable = sizeof(rguColorTable[0]) * 3;
        rguColorTable[0] = BI_RGB565_MASK_R;
        rguColorTable[1] = BI_RGB565_MASK_G;
        rguColorTable[2] = BI_RGB565_MASK_B;
    }
    else if (IsEqualGUID(&GUID_PKPixelFormat32bppRGB101010, &pIE->guidPixFormat))
    {
        pIE->cbPixel = 4;
        bmpIH.uCompression = BI_BITFIELDS;

        cbColorTable = sizeof(rguColorTable[0]) * 3;
        rguColorTable[0] = BI_RGB101010_MASK_R;
        rguColorTable[1] = BI_RGB101010_MASK_G;
        rguColorTable[2] = BI_RGB101010_MASK_B;
    }            
    else
        Call(WMP_errUnsupportedFormat);

    cbLineS = (pIE->cbPixel * pIE->uWidth + 3) / 4 * 4;

    bmpFH.uOffBits = (U32)(sizeof(bmpFH) + sizeof(bmpIH) + cbColorTable);
    bmpFH.uSize = (U32)(bmpFH.uOffBits + cbLineS * pIE->uHeight);

    bmpIH.iWidth = pIE->uWidth;
    bmpIH.iHeight = pIE->uHeight;
    bmpIH.iPlanes = 1;
    bmpIH.iBitCount = (I16)(8 * pIE->cbPixel);
    bmpIH.uImageSize = (U32)(cbLineS * pIE->uHeight);
    bmpIH.iPelsPerMeterX = (I32)(pIE->fResX * 39.37);
    bmpIH.iPelsPerMeterY = (I32)(pIE->fResY * 39.37);

    Call(pS->Write(pS, &bmpFH, sizeof(bmpFH)));
    Call(pS->Write(pS, &bmpIH, sizeof(bmpIH)));
    Call(pS->Write(pS, rguColorTable, cbColorTable));

    pIE->offPixel = pIE->offStart + bmpFH.uOffBits;
    pIE->fHeaderDone = !FALSE;

Cleanup:
    return err;
}

ERR PKImageEncode_WritePixels_BMP(
    PKImageEncode* pIE,
    U32 cLine,
    U8* pbPixel,
    U32 cbStride)
{
    ERR err = WMP_errSuccess;

    struct WMPStream* pS = pIE->pStream;
    size_t cbLineM = 0, cbLineS = 0;
    I32 i = 0;
    static U8 pPadding[4] = {0};

    // header
    if (!pIE->fHeaderDone)
    {
        // WriteBMPHeader() also inits this object
        Call(WriteBMPHeader(pIE));
    }

    // body
    // calculate line size in memory and in stream
    cbLineM = pIE->cbPixel * pIE->uWidth;
    cbLineS = (cbLineM + 3) / 4 * 4;

    //FailIf(pRect->X < 0 || pID->uWidth <= pRect->X, WMP_errInvalidParameter);
    //FailIf(pRect->Y < 0 || pID->uHeight <= pRect->Y, WMP_errInvalidParameter);
    //FailIf(pRect->Width < 0 || pID->uWidth < pRect->X + pRect->Width, WMP_errInvalidParameter);
    //FailIf(pRect->Height < 0 || pID->uHeight < pRect->Y + pRect->Height, WMP_errInvalidParameter);
    FailIf(cbStride < cbLineM, WMP_errInvalidParameter);

    for (i = cLine - 1; 0 <= i; --i)
    {
        size_t offM = cbStride * i;
        size_t offS = cbLineS * (pIE->uHeight - (pIE->idxCurrentLine + i + 1));

        Call(pS->SetPos(pS, pIE->offPixel + offS));
        Call(pS->Write(pS, pbPixel + offM, cbLineM));
    }
    Call(pS->Write(pS, pPadding, (cbLineS - cbLineM)));
    pIE->idxCurrentLine += cLine;

Cleanup:
    return err;
}

ERR PKImageEncode_Create_BMP(
    PKImageEncode** ppIE)
{
    ERR err = WMP_errSuccess;
    PKImageEncode* pIE = NULL;

    Call(PKImageEncode_Create(ppIE));

    pIE = *ppIE;
    pIE->WritePixels = PKImageEncode_WritePixels_BMP;

Cleanup:
    return err;
}


//================================================================
// PKImageDecode_BMP
//================================================================
ERR ParseBMPHeader(
    PKTestDecode* pID,
    struct WMPStream* pWS)
{
    ERR err = WMP_errSuccess;

    BITMAPFILEHEADER bmpFH = {0};
    BITMAPINFOHEADER bmpIH = {0};
    static U32 bmpIHE[32] = {0};    // should be >= sizeof(BITMAPV5HEADER) - sizeof(BITMAPINFOHEADER)
    static U32 rguColorTable[256] = {0};
    U32 i = 0;

    Call(pWS->Read(pWS, &bmpFH, sizeof(bmpFH)));
    FailIf(bmpFH.szBM != (U8 *) strstr((char *) bmpFH.szBM, "BM"), WMP_errUnsupportedFormat);

    Call(pWS->Read(pWS, &bmpIH, sizeof(bmpIH)));

    FailIf(((sizeof(bmpIH) > bmpIH.uSize) || ((sizeof(bmpIH) + sizeof(bmpIHE)) < bmpIH.uSize)), WMP_errUnsupportedFormat);

    if (sizeof(bmpIH) < bmpIH.uSize)
        Call(pWS->Read(pWS, &bmpIHE, bmpIH.uSize - sizeof(bmpIH)));

    switch (bmpIH.iBitCount)
    {
        case 8:
            // check the color table to verify the image is actually gray scale
            Call(pWS->Read(pWS, rguColorTable, sizeof(rguColorTable)));
            for (i = 0; i < sizeof2(rguColorTable); ++i)
            {
                U32 c = i | (i << 8) | (i << 16);
                FailIf(c != rguColorTable[i], WMP_errUnsupportedFormat);
            }
            
            pID->guidPixFormat = GUID_PKPixelFormat8bppGray;
            pID->EXT.BMP.cbPixel = 1;
            break;

        case 16:
            Call(pWS->Read(pWS, rguColorTable, sizeof(rguColorTable[0]) * 3));
            if (BI_RGB555_MASK_B == rguColorTable[0] && BI_RGB555_MASK_G == rguColorTable[1] && BI_RGB555_MASK_R == rguColorTable[2])
            {
                pID->guidPixFormat = GUID_PKPixelFormat16bppRGB555;
            }
            if (BI_RGB565_MASK_B == rguColorTable[0] && BI_RGB565_MASK_G == rguColorTable[1] && BI_RGB565_MASK_R == rguColorTable[2])
            {
                pID->guidPixFormat = GUID_PKPixelFormat16bppRGB565;
            }
            else
            {
                Call(WMP_errUnsupportedFormat);
            }
            pID->EXT.BMP.cbPixel = 2;
            break;
            
        case 24:
            pID->guidPixFormat = GUID_PKPixelFormat24bppBGR;
            pID->EXT.BMP.cbPixel = 3;
            break;

        case 32:
            Call(pWS->Read(pWS, rguColorTable, sizeof(rguColorTable[0]) * 3));
            if (BI_RGB101010_MASK_B == rguColorTable[0] && BI_RGB101010_MASK_G == rguColorTable[1] && BI_RGB101010_MASK_R == rguColorTable[2])
            {
                pID->guidPixFormat = GUID_PKPixelFormat32bppRGB101010;
            }
            else
            {
                pID->guidPixFormat = GUID_PKPixelFormat32bppBGRA;
            }
            pID->EXT.BMP.cbPixel = 4;
            break;
            
        default:
            Call(WMP_errUnsupportedFormat);
            break;
    }

    pID->uWidth = (U32)bmpIH.iWidth;
    pID->uHeight = (U32)bmpIH.iHeight;

    pID->fResX = (0 == bmpIH.iPelsPerMeterX ? 96 : (Float)(bmpIH.iPelsPerMeterX * .0254));
    pID->fResY = (0 == bmpIH.iPelsPerMeterY ? 96 : (Float)(bmpIH.iPelsPerMeterY * .0254));
    
    pID->EXT.BMP.offPixel = pID->offStart + bmpFH.uOffBits;

Cleanup:
    return err;
}

ERR PKImageDecode_Initialize_BMP(
    PKTestDecode* pID,
    struct WMPStream* pWS)
{
    ERR err = WMP_errSuccess;

    Call(PKTestDecode_Initialize(pID, pWS));
    Call(ParseBMPHeader(pID, pWS));

Cleanup:
    return err;
}

ERR PKImageDecode_Copy_BMP(
    PKTestDecode* pID,
    const PKRect* pRect,
    U8* pb,
    U32 cbStride)
{
    ERR err = WMP_errSuccess;

    struct WMPStream* pS = pID->pStream;

    size_t cbLineS = (pID->EXT.BMP.cbPixel * pID->uWidth + 3) / 4 * 4;
    size_t cbLineM = pID->EXT.BMP.cbPixel * pRect->Width;
    
    I32 i = 0;

    //FailIf(pRect->X < 0 || pID->uWidth <= pRect->X, WMP_errInvalidParameter);
    //FailIf(pRect->Y < 0 || pID->uHeight <= pRect->Y, WMP_errInvalidParameter);
    //FailIf(pRect->Width < 0 || pID->uWidth < pRect->X + pRect->Width, WMP_errInvalidParameter);
    //FailIf(pRect->Height < 0 || pID->uHeight < pRect->Y + pRect->Height, WMP_errInvalidParameter);
    FailIf(cbStride < cbLineM, WMP_errInvalidParameter);

    for (i = pRect->Y + pRect->Height - 1; pRect->Y <= i; --i)
    {
        size_t offLine = pID->EXT.BMP.cbPixel * pRect->X;
        size_t offS = cbLineS * (pID->uHeight - i - 1) + offLine;
        size_t offM = cbStride * (i - pRect->Y) + offLine;

        Call(pS->SetPos(pS, pID->EXT.BMP.offPixel + offS));
        Call(pS->Read(pS, pb + offM, cbLineM));
    }

Cleanup:
    return err;
}

ERR PKImageDecode_Create_BMP(
    PKTestDecode** ppID)
{
    ERR err = WMP_errSuccess;
    PKTestDecode* pID = NULL;

    Call(PKTestDecode_Create(ppID));

    pID = *ppID;
    pID->Initialize = PKImageDecode_Initialize_BMP;
    pID->Copy = PKImageDecode_Copy_BMP;

Cleanup:
    return err;
}

