/*
 * TIFF Library
 *
 * The purpose of this test suite is to test the correctness of
 * writing and reading transfer functions with TIFFTAG_TRANSFERFUNCTION.
 *
 * Depending on the SamplesPerPixel (and ExtraSamples) values,
 * exactly one (1) or exactly three (3) transfer functions shall be written
 * to file and are expected for reading from file.
 * An exception is if all three transfer functions are equal.
 * Then only one transfer function is written to file and
 * at reading that transfer function is returned in three
 * pointers to TIFFGetField().
 * If only one array is returned, as for images with SamplesPerPixel=1,
 * the other two are set to NULL.
 *
 * Each transfer function length is (1 << BitsPerSample) times uint16_t values.
 *
 */
/* clang-format off */

#include "tif_config.h"

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "tiffio.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for unlink() on linux */
#endif

/* Define basic TIFF file settings */
uint16_t spp = 3;
const uint16_t width = 4;
const uint16_t length = 6;
const uint16_t bps = 8;
const uint16_t photometric = PHOTOMETRIC_RGB;
const uint16_t rows_per_strip = 100; /* up to 2**32-1 */
const uint16_t planarconfig = PLANARCONFIG_CONTIG;

/* Data and pointer to three transfer functions. */
uint16_t *pTransferFunctionData;
uint32_t nSamplesPerTransferFunction;
/* Pointers into pTransferFunctionData for writing */
const uint16_t *ptf0;
const uint16_t *ptf1;
const uint16_t *ptf2;
/* Pointers for reading */
const uint16_t *ptfR0;
const uint16_t *ptfR1;
const uint16_t *ptfR2;

#define GOTOFAILURE(x)                                                                                                 \
    {                                                                                                                  \
        retval = x;                                                                                                    \
        goto failure;                                                                                                  \
    }

/*-- setup_transferfunctions() --
 * Initializes the transfer function array pTransferFunctionData
 * with three transfer functions.
 * Pointer ptf0, ptf1, ptf2 are initialized to the first array element of
 * each transfer function.
 */
static int setup_transfer_functions(void)
{
    if (pTransferFunctionData)
        _TIFFfree(pTransferFunctionData);

    /* Setup array with some more values to shift start of the three arrays. */
    nSamplesPerTransferFunction = ((uint32_t)1 << bps);
    pTransferFunctionData = (uint16_t *)_TIFFmalloc(
        (tmsize_t)(3 * (size_t)nSamplesPerTransferFunction * sizeof(uint16_t)));
    if (!pTransferFunctionData)
        return 1;

    for (uint32_t i = 0; i < 3 * nSamplesPerTransferFunction; i++)
        pTransferFunctionData[i] = (uint16_t)(i + 1);

    ptf0 = &pTransferFunctionData[0];
    ptf1 = &pTransferFunctionData[nSamplesPerTransferFunction];
    ptf2 = &pTransferFunctionData[2 * nSamplesPerTransferFunction];

    return 0;
} /*-- setup_transferfunctions() --*/

/*--read_check_transferfunctions --
 * The file is opened and the transfer functions are read.
 * Then the read transfer functions are compared against the arrays provided
 * with ptfx0, ptfx1, ptfx2.
 * Depending on blnClose, the opened file is closed.
 * TIFF **ptif returns the TIFF pointer.
 * The function returns a bit-field with one bit set for each successfully read transfer function.
 */
static int read_check_transferfunctions(TIFF **ptif, const char *filename, int blnClose, const uint16_t *ptfx0,
                                 const uint16_t *ptfx1, const uint16_t *ptfx2)
{
    /* Test reading of transfer functions */
    int retval = 0;
    TIFF *tif = TIFFOpen(filename, "r");
    *ptif = tif;
    if (!tif)
    {
        fprintf(stderr, "read_check_transferfunctions(): Can't open %s for reading.\n", filename);
        GOTOFAILURE(-1);
    }

    if (!TIFFGetField(tif, TIFFTAG_TRANSFERFUNCTION, &ptfR0, &ptfR1, &ptfR2))
    {
        fprintf(stderr, "read_check_transferfunctions(): TIFFGetField for TIFFTAG_TRANSFERFUNCTION failed.\n");
        GOTOFAILURE(-1);
    }
    else
    {
        if (ptfR0 != NULL && !_TIFFmemcmp(ptfx0, ptfR0, (tmsize_t)((size_t)nSamplesPerTransferFunction * sizeof(uint16_t))))
            retval += 1;
        if (ptfR1 != NULL && !_TIFFmemcmp(ptfx1, ptfR1, (tmsize_t)((size_t)nSamplesPerTransferFunction * sizeof(uint16_t))))
            retval += 2;
        if (ptfR2 != NULL && !_TIFFmemcmp(ptfx2, ptfR2, (tmsize_t)((size_t)nSamplesPerTransferFunction * sizeof(uint16_t))))
            retval += 4;
    }

failure:
    if (blnClose)
    {
        TIFFClose(tif);
        tif = NULL;
    }
    *ptif = tif;

    return retval;
} /*-- read_check_transferfunctions() --*/

/*-- write_basic_IFD_data() --
 * Opens a file for writing and initializes a basic TIFF image with the
 * globally specified parameters.
 *
 * wrtTransferFunction: 0 = don't write TIFFTAG_TRANSFERFUNCTION tag
 *                      1 = write TIFFTAG_TRANSFERFUNCTION tag before and
 *                      2 = after ExtraSamples tag.
 * blnExtraSamples=true writes ExtraSamples tag.
 * Only if blnCloseFile is true, the file is closed.
 * TIFF **ptif returns the pointer to the opened TIFF file.
 */
static int write_basic_IFD_data(TIFF **ptif, const char *filename, int wrtTransferFunction, int nExtraSamples,
                         int blnCloseFile)
{
    unsigned char buf[3] = {0, 127, 255};
    int retval = 0;
    uint8_t *bufLine = NULL;
    uint16_t extraSamples[4] = {EXTRASAMPLE_UNSPECIFIED, EXTRASAMPLE_UNSPECIFIED, EXTRASAMPLE_UNSPECIFIED,
                                EXTRASAMPLE_UNSPECIFIED};
    size_t bufLen;
    TIFF *tif = TIFFOpen(filename, "w");
    *ptif = tif;
    if (!tif)
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't create %s\n", filename);
        GOTOFAILURE(1);
    }

    if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width))
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't set ImageWidth tag.\n");
        GOTOFAILURE(1);
    }
    if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, length))
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't set ImageLength tag.\n");
        GOTOFAILURE(1);
    }
    if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bps))
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't set BitsPerSample tag.\n");
        GOTOFAILURE(1);
    }
    if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, spp))
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't set SamplesPerPixel tag.\n");
        GOTOFAILURE(1);
    }
    if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rows_per_strip))
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't set SamplesPerPixel tag.\n");
        GOTOFAILURE(1);
    }
    if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, planarconfig))
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't set PlanarConfiguration tag.\n");
        GOTOFAILURE(1);
    }
    if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric))
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't set PhotometricInterpretation tag.\n");
        GOTOFAILURE(1);
    }

    /* Set transfer function before ExtraSamples*/
    if (wrtTransferFunction == 1)
    {

        if (!TIFFSetField(tif, TIFFTAG_TRANSFERFUNCTION, ptf0, ptf1, ptf2))
        {
            fprintf(stderr, "write_basic_IFD_data(): TIFFSetField for TIFFTAG_TRANSFERFUNCTION failed.\n");
            GOTOFAILURE(1);
        }
    }

    /* Set ExtraSamples thus SamplesPerPixel for transfer functions is reduced by one. */
    if (nExtraSamples > 0 && nExtraSamples < 4)
    {
        if (!TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, nExtraSamples, extraSamples))
        {
            fprintf(stderr, "write_basic_IFD_data(): Warning: Could not set TIFFTAG_EXTRASAMPLES tag.\n");
        }
    }

    /* Set transfer function after ExtraSamples*/
    if (wrtTransferFunction == 2)
    {

        if (!TIFFSetField(tif, TIFFTAG_TRANSFERFUNCTION, ptf0, ptf1, ptf2))
        {
            fprintf(stderr, "write_basic_IFD_data(): TIFFSetField for TIFFTAG_TRANSFERFUNCTION failed.\n");
            GOTOFAILURE(1);
        }
    }

    /* Setup buffer for image line */
    bufLen = (size_t)width * spp * (size_t)(bps + 7) / 8;
    bufLine = (uint8_t *)_TIFFmalloc((tmsize_t)bufLen);
    if (!bufLine)
    {
        fprintf(stderr, "write_basic_IFD_data(): Can't allocate bufLine buffer.\n");
        GOTOFAILURE(1);
    }
    if (bufLen > sizeof(buf))
        _TIFFmemcpy(bufLine, buf, sizeof(buf));

    /* Write dummy pixel data. */
    for (int i = 0; i < length; i++)
    {
        if (TIFFWriteScanline(tif, bufLine, (uint32_t)i, 0) == -1)
        {
            fprintf(stderr, "write_basic_IFD_data(): Can't write image data.\n");
            GOTOFAILURE(1);
        }
        size_t idx = (size_t)i * spp < bufLen ? (size_t)i * spp : bufLen - 2;
        bufLine[idx] = (uint8_t)(i * 80);
    }

failure:
    if (bufLine) 
        _TIFFfree(bufLine);
    if (blnCloseFile)
    {
        TIFFClose(tif);
        tif = NULL;
    }
    *ptif = tif;

    return retval;
} /*-- write_basic_IFD_data() --*/

/*==== main() ====*/
int main(void)
{

    const char *filename = "test_transferfunction_write_read.tif";

    if (setup_transfer_functions())
        return 1;

    int ret;
    TIFF *tif = NULL;

    fprintf(stderr,
            "--- Testing TIFF_TRANSFERFUNCTION tag writing and reading. ---\n    Expect 6 warning messages ...\n");

    /* Test with RGB data */
    spp = 3;
    if (write_basic_IFD_data(&tif, filename, 2, 0, true))
        return 1;
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != 7)
    {
        fprintf(stderr, "TRANSFERFUNCTION for RGB (SPP=3) could not be read correctly.\n");
        return 1;
    }

    /* Test with Grayscale data */
    spp = 1;
    if (write_basic_IFD_data(&tif, filename, 2, 0, true))
        return 1;
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != 1)
    {
        fprintf(stderr, "TRANSFERFUNCTION SPP=1 could not be read correctly.\n");
        return 1;
    }

    /* Test with RGB and ExtraSample, thus only 2 SamplesPerPixel left.
     * ExtraSample is written before transfer function.
     * --> For v4.5.1: This gives an Error when reading the transfer functions!
     */
    spp = 3;
    if (write_basic_IFD_data(&tif, filename, 2, 1, true))
        return 1;
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != 7)
    {
        fprintf(stderr, "One ExtraSample plus TRANSFERFUNCTION SPP=3 could not be read correctly.\n");
        return 1;
    }
    /* Now write two ExtraSamples, thus only one transfer function is written to file and read. */
    if (write_basic_IFD_data(&tif, filename, 2, 2, true))
        return 1;
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != 1)
    {
        fprintf(stderr, "Two ExtraSamples plus TRANSFERFUNCTION SPP=3 could not be read correctly.\n");
        return 1;
    }

    /* Test with tree times the same transfer functions.
     * Thus, only one should be written to file and when read copied three times.
     */
    spp = 3;
    if (write_basic_IFD_data(&tif, filename, 0, 0, false))
        return 1;
    ret = TIFFSetField(tif, TIFFTAG_TRANSFERFUNCTION, ptf0, ptf0, ptf0);
    TIFFClose(tif);
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf0, ptf0);
    if (ret != 7)
    {
        fprintf(stderr, "TRANSFERFUNCTIONs with same content could not be read correctly.\n");
        return 1;
    }

    /*---- Test the setting of the transfer functions with different wrong calls of TIFFSetField(). ----*/
    /* (ptf0, NULL, NULL) For v4.5.1: writes ptf0 to file and reads it back.
     *  Now, this should work for spp=1 but not for spp=3
     */
    spp = 1;
    if (write_basic_IFD_data(&tif, filename, 0, 0, false))
        return 1;
    ret = TIFFSetField(tif, TIFFTAG_TRANSFERFUNCTION, ptf0, NULL, NULL);
    TIFFClose(tif);
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != 1)
    {
        fprintf(stderr, "Unexpected at %d.\n", __LINE__);
        return 1;
    }
    spp = 3;
    /* (ptf0, NULL, NULL) For v4.5.1: writes ptf0 to file and reads it back.*/
    if (write_basic_IFD_data(&tif, filename, 0, 0, false))
        return 1;
    ret = TIFFSetField(tif, TIFFTAG_TRANSFERFUNCTION, ptf0, NULL, NULL);
    TIFFClose(tif);
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != -1)
    {
        fprintf(stderr, "Unexpected at %d.\n", __LINE__);
        return 1;
    }

    /* (ptf0, NULL, ptf2): For v4.5.1: Overflow error, when second ptf1 is copied in
     * TIFFWriteDirectoryTagTransferfunction().*/
    if (write_basic_IFD_data(&tif, filename, 0, 0, false))
        return 1;
    ret = TIFFSetField(tif, TIFFTAG_TRANSFERFUNCTION, ptf0, NULL, ptf2);
    TIFFClose(tif);
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != -1)
    {
        fprintf(stderr, "Unexpected at %d.\n", __LINE__);
        return 1;
    }

    /* (ptf0, ptf1, NULL) For v4.5.1: writes ptf0 and ptf1 to file but does not read it back, because only 2 but 3
     * required.*/
    if (write_basic_IFD_data(&tif, filename, 0, 0, false))
        return 1;
    ret = TIFFSetField(tif, TIFFTAG_TRANSFERFUNCTION, ptf0, ptf1, NULL);
    TIFFClose(tif);
    ret = read_check_transferfunctions(&tif, filename, true, ptf0, ptf1, ptf2);
    if (ret != -1)
    {
        fprintf(stderr, "Unexpected at %d.\n", __LINE__);
        return 1;
    }

    /* All tests passed; delete file and exit with success status. */
    unlink(filename);
    fprintf(stderr, "-------- Test finished OK ----------\n");
    return 0;
}
