/******************************************************************************
 *
 * Project:  GeoTIFF Driver
 * Purpose:  Generator of quant_table_md5sum.h
 * Author:   Even Rouault
 *
 ******************************************************************************
 * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys dot com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include <cassert>

#include "cpl_error.h"
#include "cpl_string.h"
#include <tiffio.h>
#include "gdal_priv.h"

#define CPLMD5Init internal_CPLMD5Init
#define CPLMD5Update internal_CPLMD5Update
#define CPLMD5Final internal_CPLMD5Final
#define CPLMD5Transform internal_CPLMD5Transform
#define CPLMD5String internal_CPLMD5String
#include "cpl_md5.h"
#include "cpl_md5.cpp"

static const GByte *GTIFFFindNextTable(const GByte *paby, GByte byMarker,
                                       int nLen, int *pnLenTable)
{
    for (int i = 0; i + 1 < nLen;)
    {
        if (paby[i] != 0xFF)
            return nullptr;
        ++i;
        if (paby[i] == 0xD8)
        {
            ++i;
            continue;
        }
        if (i + 2 >= nLen)
            return nullptr;
        int nMarkerLen = paby[i + 1] * 256 + paby[i + 2];
        if (i + 1 + nMarkerLen >= nLen)
            return nullptr;
        if (paby[i] == byMarker)
        {
            if (pnLenTable)
                *pnLenTable = nMarkerLen;
            return paby + i + 1;
        }
        i += 1 + nMarkerLen;
    }
    return nullptr;
}

void generate(int nBands, uint16_t nPhotometric, uint16_t nBitsPerSample)
{
    char **papszOpts = nullptr;
    papszOpts = CSLSetNameValue(papszOpts, "COMPRESS", "JPEG");
    if (nPhotometric == PHOTOMETRIC_YCBCR)
        papszOpts = CSLSetNameValue(papszOpts, "PHOTOMETRIC", "YCBCR");
    else if (nPhotometric == PHOTOMETRIC_SEPARATED)
        papszOpts = CSLSetNameValue(papszOpts, "PHOTOMETRIC", "CMYK");
    papszOpts = CSLSetNameValue(papszOpts, "BLOCKYSIZE", "16");
    if (nBitsPerSample == 12)
        papszOpts = CSLSetNameValue(papszOpts, "NBITS", "12");

    CPLString osTmpFilename;
    osTmpFilename.Printf("gtiffdataset_guess_jpeg_quality_tmp");

    for (int nQuality = 1; nQuality <= 100; ++nQuality)
    {
        papszOpts = CSLSetNameValue(papszOpts, "JPEG_QUALITY",
                                    CPLSPrintf("%d", nQuality));
        CPLPushErrorHandler(CPLQuietErrorHandler);
        CPLString osTmp;
        std::unique_ptr<GDALDataset> poDS(
            GDALDriver::FromHandle(GDALGetDriverByName("GTiff"))
                ->Create(
                    osTmpFilename.c_str(), 16, 16, (nBands <= 4) ? nBands : 1,
                    nBitsPerSample == 8 ? GDT_Byte : GDT_UInt16, papszOpts));
        assert(poDS);
        poDS.reset();
        CPLPopErrorHandler();

        TIFF *hTIFFTmp = TIFFOpen(osTmpFilename.c_str(), "rb");
        uint32_t nJPEGTableSizeTry = 0;
        void *pJPEGTableTry = nullptr;
        if (TIFFGetField(hTIFFTmp, TIFFTAG_JPEGTABLES, &nJPEGTableSizeTry,
                         &pJPEGTableTry))
        {
            const GByte *const pabyTable =
                static_cast<const GByte *>(pJPEGTableTry);
            int nLen = static_cast<int>(nJPEGTableSizeTry);
            const GByte *paby = pabyTable;

            struct CPLMD5Context context;
            CPLMD5Init(&context);

            while (true)
            {
                int nLenTable = 0;
                const GByte *pabyNew =
                    GTIFFFindNextTable(paby, 0xDB, nLen, &nLenTable);
                if (pabyNew == nullptr)
                    break;
                CPLMD5Update(&context, pabyNew, nLenTable);
                pabyNew += nLenTable;
                nLen -= static_cast<int>(pabyNew - paby);
                paby = pabyNew;
            }

            GByte digest[16];
            CPLMD5Final(digest, &context);
            printf("{"); /*ok*/
            for (int i = 0; i < 16; i++)
                printf("0x%02X,", digest[i]);       /*ok*/
            printf("}, // quality %d\n", nQuality); /*ok*/
        }

        TIFFClose(hTIFFTmp);
    }

    VSIUnlink(osTmpFilename);

    CSLDestroy(papszOpts);
}

int main()
{
    GDALAllRegister();
    printf("// This file is automatically generated by " /*ok*/
           "generate_quant_table_md5sum. DO NOT EDIT !!!\n\n");

    {
        printf("// Valid for bands = 1, PHOTOMETRIC_MINISBLACK\n"); /*ok*/
        printf("// Valid for bands = 3, PHOTOMETRIC_RGB\n");        /*ok*/
        printf("// Valid for bands = 4, PHOTOMETRIC_SEPARATED\n");  /*ok*/
        printf(                                                     /*ok*/
               "const uint8_t md5JPEGQuantTable_generic_8bit[][16] = {\n");
        int nBands = 1;
        uint16_t nPhotometric = PHOTOMETRIC_MINISBLACK;
        uint16_t nBitsPerSample = 8;
        generate(nBands, nPhotometric, nBitsPerSample);
        printf("};\n"); /*ok*/
    }
    printf("\n"); /*ok*/
    {
        printf(/*ok*/
               "const uint8_t md5JPEGQuantTable_3_YCBCR_8bit[][16] = {\n");
        int nBands = 3;
        uint16_t nPhotometric = PHOTOMETRIC_YCBCR;
        uint16_t nBitsPerSample = 8;
        generate(nBands, nPhotometric, nBitsPerSample);
        printf("};\n"); /*ok*/
    }

    return 0;
}
