/*******************************************************************************
* Copyright 2005-2020 Intel Corporation.
*
* This software and the related documents are Intel copyrighted  materials,  and
* your use of  them is  governed by the  express license  under which  they were
* provided to you (License).  Unless the License provides otherwise, you may not
* use, modify, copy, publish, distribute,  disclose or transmit this software or
* the related documents without Intel's prior written permission.
*
* This software and the related documents  are provided as  is,  with no express
* or implied  warranties,  other  than those  that are  expressly stated  in the
* License.
*******************************************************************************/

#include "base_image.h"

#pragma pack(push, 2)
struct BMPFileHeader
{
    unsigned short    bfType;
    unsigned int      bfSize;
    unsigned short    bfReserved1;
    unsigned short    bfReserved2;
    unsigned int      bfOffBits;
};

struct BMPCoreHeader
{
    unsigned int    bcSize;
    unsigned short  bcWidth;
    unsigned short  bcHeight;
    unsigned short  bcPlanes;
    unsigned short  bcBitCount;
};

struct BMPInfoHeader
{
    unsigned int    biSize;
    int             biWidth;
    int             biHeight;
    unsigned short  biPlanes;
    unsigned short  biBitCount;
    unsigned int    biCompression;
    unsigned int    biSizeImage;
    int             biXPelsPerMeter;
    int             biYPelsPerMeter;
    unsigned int    biClrUsed;
    unsigned int    biClrImportant;
};

struct __CIEXYZ
{
    unsigned int    ciexyzX;
    unsigned int    ciexyzY;
    unsigned int    ciexyzZ;
};
struct __CIEXYZTRIPLE
{
    __CIEXYZ        ciexyzRed;
    __CIEXYZ        ciexyzGreen;
    __CIEXYZ        ciexyzBlue;
};
struct BMPV4Header
{
    unsigned int    bV4RedMask;
    unsigned int    bV4GreenMaskc;
    unsigned int    bV4BlueMask;
    unsigned int    bV4AlphaMask;
    unsigned int    bV4CSType;
    __CIEXYZTRIPLE  bV4Endpoints;
    unsigned int    bV4GammaRed;
    unsigned int    bV4GammaGreen;
    unsigned int    bV4GammaBlue;
};

struct BMPV5Header
{
    unsigned int    bV5Intent;
    unsigned int    bV5ProfileData;
    unsigned int    bV5ProfileSize;
    unsigned int    bV5Reserved;
};
#pragma pack(pop)

struct RGBquad
{
    unsigned char rgbBlue;
    unsigned char rgbGreen;
    unsigned char rgbRed;
    unsigned char rgbReserved;
};

template <class T>
static Status cppiSwapChannesARGB(void *pSrc, size_t srcStep, void *pDst, size_t dstStep, Size size)
{
    T iTemp;

    for(long long i = 0; i < size.height; i++)
    {
        T *pSrcRow = (T*)((char*)pSrc + i*srcStep);
        T *pDstRow = (T*)((char*)pDst + i*dstStep);

        for(long long j = 0, k = 0; j < size.width; j++, k += 4)
        {
            iTemp = pSrcRow[k];
            pDstRow[k]     = pSrcRow[k + 1];
            pDstRow[k + 1] = pSrcRow[k + 2];
            pDstRow[k + 2] = pSrcRow[k + 3];
            pDstRow[k + 3] = iTemp;
        }
    }

    return STS_OK;
}

Status BmpReadHeader(File &file, BMPInfoHeader *pHeader, Image &image)
{
    BMPFileHeader fHeader;
    BMPInfoHeader header;

    if(!pHeader)
        pHeader = &header;
    memset(pHeader, 0, sizeof(BMPInfoHeader));

    // read header
    file.Read(&fHeader.bfType, 1, sizeof(fHeader.bfType));
    file.Read(&fHeader.bfSize, 1, sizeof(fHeader.bfSize));

    if(fHeader.bfType != 0x4D42)
        return STS_ERR_FORMAT;

    file.Read(&fHeader.bfReserved1, 1, sizeof(fHeader.bfReserved1));
    file.Read(&fHeader.bfReserved2, 1, sizeof(fHeader.bfReserved2));
    file.Read(&fHeader.bfOffBits,   1, sizeof(fHeader.bfOffBits));

    file.Read(&pHeader->biSize,     1, sizeof(pHeader->biSize));

    if(pHeader->biSize == sizeof(BMPCoreHeader))
    {
        BMPCoreHeader coreHeader;
        coreHeader.bcSize = pHeader->biSize;
        file.Read(&coreHeader.bcWidth,       1, sizeof(coreHeader.bcWidth));
        file.Read(&coreHeader.bcHeight,      1, sizeof(coreHeader.bcHeight));
        file.Read(&coreHeader.bcPlanes,      1, sizeof(coreHeader.bcPlanes));
        file.Read(&coreHeader.bcBitCount,    1, sizeof(coreHeader.bcBitCount));

        pHeader->biWidth    = (int)coreHeader.bcWidth;
        pHeader->biHeight   = (int)coreHeader.bcHeight;
        pHeader->biPlanes   = coreHeader.bcPlanes;
        pHeader->biBitCount = coreHeader.bcBitCount;
    }
    else
    {
        if(pHeader->biSize != sizeof(BMPInfoHeader) &&
            pHeader->biSize != (sizeof(BMPV4Header)+sizeof(BMPInfoHeader)) &&
            pHeader->biSize != (sizeof(BMPV5Header)+sizeof(BMPV4Header)+sizeof(BMPInfoHeader)))
            return STS_ERR_UNSUPPORTED;


        file.Read(&pHeader->biWidth,       1, sizeof(pHeader->biWidth));
        file.Read(&pHeader->biHeight,      1, sizeof(pHeader->biHeight));
        file.Read(&pHeader->biPlanes,      1, sizeof(pHeader->biPlanes));
        file.Read(&pHeader->biBitCount,    1, sizeof(pHeader->biBitCount));
        file.Read(&pHeader->biCompression, 1, sizeof(pHeader->biCompression));

        if(pHeader->biWidth < 0)
            return STS_ERR_FAILED;
        if(pHeader->biBitCount != 8 && pHeader->biBitCount != 24 && pHeader->biBitCount != 32)
            return STS_ERR_FAILED;

        switch(pHeader->biCompression)
        {
        case 0L: //0L == BI_RGB
            break;

        case 3L: //3L == BI_BITFIELDS (we support only 8uC4 images)
            {
                if(pHeader->biBitCount != 32)
                    return STS_ERR_UNSUPPORTED;
            }
            break;

        default:
            return STS_ERR_UNSUPPORTED;
        }

        file.Read(&pHeader->biSizeImage,      1, sizeof(pHeader->biSizeImage));
        file.Read(&pHeader->biXPelsPerMeter,  1, sizeof(pHeader->biXPelsPerMeter));
        file.Read(&pHeader->biYPelsPerMeter,  1, sizeof(pHeader->biYPelsPerMeter));
        file.Read(&pHeader->biClrUsed,        1, sizeof(pHeader->biClrUsed));
        file.Read(&pHeader->biClrImportant,   1, sizeof(pHeader->biClrImportant));
    }

    image.m_size.width   = pHeader->biWidth;
    image.m_size.height  = ABS(pHeader->biHeight);

    image.m_samples = pHeader->biBitCount >> 3;

    if(image.m_samples == 1)
        image.m_color = CF_GRAY;
    else if(image.m_samples == 3)
        image.m_color = CF_BGR;
    else if(image.m_samples == 4)
    {
        if(3L == pHeader->biCompression && pHeader->biBitCount == 32)
            image.m_color = CF_RGBA;
        else
            image.m_color = CF_BGRA;
    }
    else
        return STS_ERR_UNSUPPORTED;

    // Reinit image to properly set data
    image = Image(image.m_size, image.m_color, ST_8U);

    // Set position for reading data
    if(!fHeader.bfOffBits)
        return STS_ERR_FAILED;

    if(file.Seek(fHeader.bfOffBits, File::ORIGIN_BEGIN))
        return STS_ERR_FAILED;

    return STS_OK;
}

Status BmpReadData(File &file, Image &image)
{
    Status status;
    BMPInfoHeader header;

    status = BmpReadHeader(file, &header, image);
    if(status < 0)
        return status;
    
    // check for file size doesn't exceed allowed BMP_IMAGE_MAX_SIZE 
    if((BMP_IMAGE_MAX_SIZE < (size_t)(image.m_size.height * image.m_size.width * image.m_sampleSize * image.m_samples)) || ((size_t)(image.m_size.height * image.m_size.width * image.m_sampleSize * image.m_samples) <= 0))
        return STS_ERR_UNSUPPORTED; 

    image.Alloc();

    size_t iFileStep = alignValue<size_t>((size_t)(image.m_size.width*image.m_sampleSize*image.m_samples), 4);

    if(0 < header.biHeight) // Read bottom-up BMP
    {
        for(long long i = image.m_size.height; i > 0; i--)
        {
            if(iFileStep != file.Read(image.ptr(i-1), 1, iFileStep))
                return STS_ERR_NOT_ENOUGH_DATA;
        }
    }
    else // Read up-bottom BMP
    {
        for(long long i = 0; i < image.m_size.height; i++)
        {
            if(iFileStep != file.Read(image.ptr(i), 1, iFileStep))
                return STS_ERR_NOT_ENOUGH_DATA;
        }
    }

     // Convert from ABGR to RGBA
    if(3L == header.biCompression && header.biBitCount == 32)
        cppiSwapChannesARGB<unsigned char>(image.ptr(), image.m_step, image.ptr(), image.m_step, image.m_size);

    return STS_OK;
}

Status BmpWriteData(Image &image, File &file)
{
    unsigned int iIHSize = 40;
    unsigned int iFHSize = 14;
    size_t iFileSize;
    size_t iImageSize;
    size_t iFileStep;

    RGBquad         palette[256] = {0};
    BMPFileHeader   fHeader;
    BMPInfoHeader   header;

    iFileStep   = alignValue<size_t>((size_t)(image.m_size.width*image.m_sampleSize*image.m_samples), 4);
    iImageSize  = iFileStep * (size_t)image.m_size.height;
    iFileSize   = iImageSize + iIHSize + iFHSize;

    fHeader.bfType      = 0x4D42;
    fHeader.bfSize      = (unsigned int)iFileSize;
    fHeader.bfReserved1 = 0;
    fHeader.bfReserved2 = 0;
    fHeader.bfOffBits   = iIHSize + iFHSize;

    if(image.m_samples == 1)
        fHeader.bfOffBits += sizeof(palette);

    // write header
    file.Write(&fHeader.bfType,         1, sizeof(fHeader.bfType));
    file.Write(&fHeader.bfSize,         1, sizeof(fHeader.bfSize));
    file.Write(&fHeader.bfReserved1,    1, sizeof(fHeader.bfReserved1));
    file.Write(&fHeader.bfReserved2,    1, sizeof(fHeader.bfReserved2));
    file.Write(&fHeader.bfOffBits,      1, sizeof(fHeader.bfOffBits));

    header.biSize          = iIHSize;
    header.biWidth         = (unsigned int)image.m_size.width;
    header.biHeight        = (unsigned int)image.m_size.height;
    header.biPlanes        = 1;
    header.biBitCount      = (unsigned short)(image.m_samples << 3);
    header.biCompression   = 0L; // BI_RGB
    header.biSizeImage     = (unsigned int)iImageSize;
    header.biXPelsPerMeter = 0;
    header.biYPelsPerMeter = 0;
    header.biClrUsed       = ((image.m_samples == 1) ? 256 : 0);
    header.biClrImportant  = ((image.m_samples == 1) ? 256 : 0);

    file.Write(&header.biSize,          1, sizeof(header.biSize));
    file.Write(&header.biWidth,         1, sizeof(header.biWidth));
    file.Write(&header.biHeight,        1, sizeof(header.biHeight));
    file.Write(&header.biPlanes,        1, sizeof(header.biPlanes));
    file.Write(&header.biBitCount,      1, sizeof(header.biBitCount));
    file.Write(&header.biCompression,   1, sizeof(header.biCompression));
    file.Write(&header.biSizeImage,     1, sizeof(header.biSizeImage));
    file.Write(&header.biXPelsPerMeter, 1, sizeof(header.biXPelsPerMeter));
    file.Write(&header.biYPelsPerMeter, 1, sizeof(header.biYPelsPerMeter));
    file.Write(&header.biClrUsed,       1, sizeof(header.biClrUsed));
    file.Write(&header.biClrImportant,  1, sizeof(header.biClrImportant));

    if(image.m_samples == 1)
    {
        for(int i = 0; i < 256; i++)
        {
            palette[i].rgbBlue     = (unsigned char)i;
            palette[i].rgbGreen    = (unsigned char)i;
            palette[i].rgbRed      = (unsigned char)i;
            palette[i].rgbReserved = (unsigned char)0;
        }

        file.Write(&palette[0], 1, sizeof(palette));
    }

    // write data
    for(long long i = image.m_size.height; i > 0; i--)
    {
        if(iFileStep != file.Write(image.ptr(i-1), 1, iFileStep))
            return STS_ERR_FAILED;
    }

    return STS_OK;
}
