/******************************************************************************
 * Project:  GDAL
 * Author:   Raul Alonso Reyes <raul dot alonsoreyes at satcen dot europa dot
 *eu> Author:   Even Rouault, <even dot rouault at spatialys dot com> Purpose:
 *JPEG-2000 driver based on Lurawave library, driver developed by SatCen
 *
 ******************************************************************************
 * Copyright (c) 2016, SatCen - European Union Satellite Centre
 * Copyright (c) 2016, Even Rouault
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_port.h"

#include "lwf_jp2.h"

#include "jp2luracallbacks.h"

#ifdef ENABLE_MEMORY_REGISTRAR
JP2LuraMemoryRegistrar::JP2LuraMemoryRegistrar()
{
}

JP2LuraMemoryRegistrar::~JP2LuraMemoryRegistrar()
{
    CPLDebug("JP2Lura", "JP2LuraMemoryRegistrar: %d block allocated leaked",
             static_cast<int>(oMap.size()));
    std::map<void *, size_t>::const_iterator oIter = oMap.begin();
    for (; oIter != oMap.end(); ++oIter)
    {
        CPLDebug("JP2Lura", "force freeing %d bytes",
                 static_cast<int>(oIter->second));
        VSIFree(oIter->first);
    }
}

void JP2LuraMemoryRegistrar::Register(size_t nSize, void *ptr)
{
    CPLAssert(oMap.find(ptr) == oMap.end());
    oMap[ptr] = nSize;
}

void JP2LuraMemoryRegistrar::Unregister(void *ptr)
{
    CPLAssert(oMap.find(ptr) != oMap.end());
    oMap.erase(ptr);
}
#endif  // ENABLE_MEMORY_REGISTRAR

/************************************************************************/
/*                    GDALJP2Lura_Callback_Malloc()                     */
/************************************************************************/

void *JP2_Callback_Conv GDALJP2Lura_Callback_Malloc(size_t size,
                                                    JP2_Callback_Param
#ifdef ENABLE_MEMORY_REGISTRAR
                                                        lParam
#endif
)
{
    void *ptr = VSIMalloc(size);
#ifdef ENABLE_MEMORY_REGISTRAR
    if (lParam && ptr)
    {
        ((JP2LuraMemoryRegistrar *)lParam)->Register(size, ptr);
    }
#endif
    return ptr;
}

/************************************************************************/
/*                    GDALJP2Lura_Callback_Free()                       */
/************************************************************************/

JP2_Error JP2_Callback_Conv GDALJP2Lura_Callback_Free(void *ptr,
                                                      JP2_Callback_Param
#ifdef ENABLE_MEMORY_REGISTRAR
                                                          lParam
#endif
)
{
#ifdef ENABLE_MEMORY_REGISTRAR
    if (lParam && ptr)
    {
        ((JP2LuraMemoryRegistrar *)lParam)->Unregister(ptr);
    }
#endif
    VSIFree(ptr);
    return cJP2_Error_OK;
}

/************************************************************************/
/*                  GDALJP2Lura_Callback_Decompress_Read()              */
/************************************************************************/

unsigned long JP2_Callback_Conv GDALJP2Lura_Callback_Decompress_Read(
    unsigned char *pucData, unsigned long ulPos, unsigned long ulSize,
    JP2_Callback_Param lParam)
{
    VSILFILE *fp = reinterpret_cast<VSILFILE *>(lParam);

    if (VSIFSeekL(fp, ulPos, SEEK_SET) != 0)
    {
        return 0;
    }

    return static_cast<unsigned long>(
        VSIFReadL(pucData, 1, static_cast<size_t>(ulSize), fp));
}

/************************************************************************/
/*                          splitIEEE754Float()                         */
/************************************************************************/

typedef union
{
    float f;
    unsigned int ui;
} float_uint_union;

static void splitIEEE754Float(float f, unsigned int *mantissa, int *exponent,
                              int *sign)
{
    float_uint_union x;
    x.f = f;

    if (x.ui & 0x80000000)
        *sign = 1;
    else
        *sign = 0;

    *mantissa = x.ui & 0x07FFFFF;

    *exponent = (x.ui >> 23) & 0xFF;
}

/************************************************************************/
/*                           setIIIE754Sign()                           */
/************************************************************************/

static void setIIIE754Sign(float_uint_union *f, unsigned char sign)
{
    if (!sign)
        f->ui = (f->ui & 0x7FFFFFFFU);
    else
        f->ui = (f->ui & 0x7FFFFFFFU) | 0x80000000U;
}

/************************************************************************/
/*                         setIIIE754Exponent()                         */
/************************************************************************/

static void setIIIE754Exponent(float_uint_union *f, unsigned char exponent)
{
    f->ui = (f->ui & 0x807fffffU) | (exponent << 23);
}

/************************************************************************/
/*                         setIIIE754Mantissa()                         */
/************************************************************************/

static void setIIIE754Mantissa(float_uint_union *f, unsigned int mantissa)
{
    f->ui = (f->ui & (0xFF800000U)) | mantissa;
}

/************************************************************************/
/*                 GDALJP2Lura_Callback_Decompress_Write()              */
/************************************************************************/

JP2_Error JP2_Callback_Conv GDALJP2Lura_Callback_Decompress_Write(
    unsigned char *pucData, short sComponent, unsigned long ulRow,
    unsigned long ulStart,  // starting pixel
    unsigned long ulNum,    // number of pixels
    JP2_Callback_Param lParam)
{
#ifdef DEBUG_VERBOSE
    CPLDebug("JP2Lura", "Decompress(%d, %lu, %lu, %lu)", sComponent, ulRow,
             ulStart, ulNum);
#endif

    GDALJP2Lura_Output_Data *pOutputData =
        reinterpret_cast<GDALJP2Lura_Output_Data *>(lParam);

    CPLAssert(ulRow < static_cast<unsigned long>(pOutputData->nBufYSize));
    CPLAssert(ulStart + ulNum <=
              static_cast<unsigned long>(pOutputData->nBufXSize));

    long lBps = 0;
    /****************************************************/
    /*  convert from component index to channel index   */
    /*  i.e. index after expanding any palette samples  */
    /****************************************************/
    if (pOutputData->lBps == 0)  // float
    {
        switch (sComponent)
        {
            case 0:
            case 1:
            case 2:
            {
                lBps = 32;
                break;
            }

            default:
                return cJP2_Error_Write_Callback_Undefined;
        }
    }
    else
    {
        lBps = pOutputData->lBps;
    }

    unsigned char *pucImageData;  // buffer for decompressed image stripe

    if (pOutputData->lBps == 0)
        pucImageData = pOutputData->pimage;
    else
    {
        if (sComponent >= pOutputData->nBands)
        {
            // Ignored component
            return cJP2_Error_OK;
        }
        if (sComponent != pOutputData->nBand - 1)
        {
            pucImageData = pOutputData->pDatacache[sComponent];
        }
        else
        {
            pucImageData = pOutputData->pimage;
        }
    }

    /***********************************/
    /* number of bytes for each sample */
    /***********************************/

    unsigned long ulBytesFromLura = ((lBps + 7) >> 3);
    unsigned long ulBytesrequest =
        GDALGetDataTypeSizeBytes(pOutputData->eBufType);

    /* distance between samples of the same channel */

    unsigned long ulSkip = ulBytesrequest;

    unsigned long ulOffset =
        (pOutputData->nBufXSize * ulSkip) * ulRow + ulStart * ulSkip;

    unsigned char *pucStart = pucImageData + ulOffset;
    if (pOutputData->lBps == 0)
    {
#ifdef DEBUG_VERBOSE
        CPLString osLineValues;
#endif
        const unsigned int nSpaceMantissa = 4;
        for (unsigned long i = 0; i < ulNum; i++)
        {
            float_uint_union *f = (float_uint_union *)(&pucStart[i * 4]);
            if (sComponent == 0)
            {
#ifdef DEBUG_VERBOSE
                osLineValues += CPLSPrintf("%02X ", *pucData);
#endif
                setIIIE754Sign(f, (*pucData == 0) ? 0 : 1);
                pucData++;
            }
            else if (sComponent == 1)
            {
#ifdef DEBUG_VERBOSE
                osLineValues += CPLSPrintf("%02X ", *pucData);
#endif
                setIIIE754Exponent(f, *pucData);
                pucData++;
            }
            else if (sComponent == 2)
            {
                unsigned int mantissa = *(unsigned int *)pucData;
#ifdef DEBUG_VERBOSE
                osLineValues += CPLSPrintf("%02X ", mantissa);
#endif
                setIIIE754Mantissa(f, mantissa);
                pucData += nSpaceMantissa;
            }
        }
#ifdef DEBUG_VERBOSE
        CPLDebug("JP2Lura", "Component %d: %s", sComponent,
                 osLineValues.c_str());
#endif
    }
    else
    {
        memcpy(pucStart, pucData, ulBytesFromLura * ulNum);
    }

    return cJP2_Error_OK;
}

/************************************************************************/
/*                 GDALJP2Lura_Callback_Compress_Write()                */
/************************************************************************/

JP2_Error JP2_Callback_Conv GDALJP2Lura_Callback_Compress_Write(
    unsigned char *pucData, unsigned long ulPos, unsigned long ulSize,
    JP2_Callback_Param lParam)
{
    JP2_Gdal_Stream_Data *data =
        reinterpret_cast<JP2_Gdal_Stream_Data *>(lParam);

    if (VSIFSeekL(data->fp, (vsi_l_offset)(ulPos + data->Position), SEEK_SET) !=
        0)
    {
        return cJP2_Error_Failure_Write;
    }
    if (VSIFWriteL(pucData, 1, (vsi_l_offset)ulSize, data->fp) != ulSize)
    {
        return cJP2_Error_Failure_Write;
    }

    return cJP2_Error_OK;
}

/************************************************************************/
/*                 GDALJP2Lura_Callback_Compress_Read()                 */
/************************************************************************/

JP2_Error JP2_Callback_Conv GDALJP2Lura_Callback_Compress_Read(
    unsigned char *pucData, short sComponent, unsigned long ulRow,
    unsigned long ulStart, unsigned long ulNum, JP2_Callback_Param lParam)
{
    GDALJP2Lura_Input_Data *idata =
        reinterpret_cast<GDALJP2Lura_Input_Data *>(lParam);
    GDALDataset *poSrcDS = idata->poSrcDS;
    const int nBands = poSrcDS->GetRasterCount();
    const int nYSize = poSrcDS->GetRasterYSize();

    GDALProgressFunc pfnProgress = idata->pfnProgress;
    void *pProgressData = idata->pProgressData;

    if (ulStart == 0 && pfnProgress &&
        !pfnProgress(static_cast<double>(ulRow + 1) / nYSize, "",
                     pProgressData))
    {
        return cJP2_Error_Read_Callback_Undefined;
    }

    GDALRasterBand *poBand;
    if (nBands == 1 &&
        poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Float32)
    {
        poBand = poSrcDS->GetRasterBand(1);
    }
    else
    {
        poBand = poSrcDS->GetRasterBand(sComponent + 1);
    }
    GDALDataType eDataType = poBand->GetRasterDataType();

    unsigned long ulBpsRead = 0;
    switch (eDataType)
    {
        case GDT_Byte:
        {
            ulBpsRead = 8;
            // Signed = 0;
            break;
        }
        case GDT_UInt16:
        {
            ulBpsRead = 16;
            // Signed = 0;
            break;
        }
        case GDT_Int16:
        {
            ulBpsRead = 16;
            // Signed = 1;
            break;
        }
        case GDT_UInt32:
        {
            ulBpsRead = 32;
            // Signed = 0;
            break;
        }
        case GDT_Int32:
        {
            ulBpsRead = 32;
            // Signed = 1;
            break;
        }
        case GDT_Float32:
        {
            ulBpsRead = 32;
            // Signed = 1;
            break;
        }

        default:
            break;
    }

    unsigned long ulBytes = (ulBpsRead <= 8) ? 1 : ((ulBpsRead > 16) ? 4 : 2);
    unsigned long ulRowBytes = ulBytes * ulNum;

    /* malloc of the row*/
    unsigned char *pucPos =
        reinterpret_cast<unsigned char *>(VSIMalloc(ulRowBytes));
    if (pucPos == nullptr)
    {
        return cJP2_Error_Failure_Malloc;
    }

    /* check scanlines already read */
    CPLErr err = poBand->RasterIO(
        GF_Read, static_cast<int>(ulStart), static_cast<int>(ulRow),
        static_cast<int>(ulNum), 1, pucPos, static_cast<int>(ulNum), 1,
        eDataType, 0, 0, nullptr);
    if (err != CE_None)
    {
        VSIFree(pucPos);
        return cJP2_Error_Read_Callback_Undefined;
    }

    /* deliver the requested pixels to the library */
    if (nBands == 1 && eDataType == GDT_Float32)
    {
        unsigned int mantissa;
        int exponent;
        int sign;

        const unsigned long nSpaceMantissa = 4;
        for (int i = 0; i < (int)ulNum; i++)
        {
            float *ptr = (float *)(&pucPos[i * 4]);
            splitIEEE754Float(*ptr, &mantissa, &exponent, &sign);
            switch (sComponent)
            {
                case 0:
                {
                    pucData[i] = (sign) ? 255 : 0;
                    break;
                }
                case 1:
                {
                    pucData[i] = static_cast<unsigned char>(exponent);
                    break;
                }
                case 2:
                {
                    *reinterpret_cast<unsigned int *>(
                        pucData + i * nSpaceMantissa) = mantissa;
                    break;
                }
            }
        }
    }
    else
    {
        memcpy(pucData, pucPos, ulBytes * ulNum);
    }

    VSIFree(pucPos);

    return cJP2_Error_OK;
}
