//========================================================================
//
// TiffWriter.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright (C) 2010, 2012 William Bader <williambader@hotmail.com>
// Copyright (C) 2012, 2021, 2022, 2025 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2012, 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2012 Pino Toscano <pino@kde.org>
// Copyright (C) 2014 Steven Lee <roc.sky@gmail.com>
//
//========================================================================

#include "TiffWriter.h"

#if ENABLE_LIBTIFF

#    include <cstring>

#    ifdef _WIN32
#        include <io.h>
#    endif

extern "C" {
#    include <tiffio.h>
}

#    include <cstdint>

struct TiffWriterPrivate
{
    TIFF *f; // LibTiff file context
    int numRows; // number of rows in the image
    int curRow; // number of rows written
    const char *compressionString; // compression type
    TiffWriter::Format format; // format of image data
};

TiffWriter::~TiffWriter()
{
    delete priv;
}

TiffWriter::TiffWriter(Format formatA)
{
    priv = new TiffWriterPrivate;
    priv->f = nullptr;
    priv->numRows = 0;
    priv->curRow = 0;
    priv->compressionString = nullptr;
    priv->format = formatA;
}

// Set the compression type

void TiffWriter::setCompressionString(const char *compressionStringArg)
{
    priv->compressionString = compressionStringArg;
}

// Write a TIFF file.

bool TiffWriter::init(FILE *openedFile, int width, int height, double hDPI, double vDPI)
{
    unsigned int compression;
    uint16_t photometric = 0;
    auto rowsperstrip = (uint32_t)-1;
    int bitspersample;
    uint16_t samplesperpixel = 0;
    const struct compression_name_tag
    {
        const char *compressionName; // name of the compression option from the command line
        unsigned int compressionCode; // internal libtiff code
        const char *compressionDescription; // descriptive name
    } compressionList[] = { { .compressionName = "none", .compressionCode = COMPRESSION_NONE, .compressionDescription = "no compression" },
                            { .compressionName = "ccittrle", .compressionCode = COMPRESSION_CCITTRLE, .compressionDescription = "CCITT modified Huffman RLE" },
                            { .compressionName = "ccittfax3", .compressionCode = COMPRESSION_CCITTFAX3, .compressionDescription = "CCITT Group 3 fax encoding" },
                            { .compressionName = "ccittt4", .compressionCode = COMPRESSION_CCITT_T4, .compressionDescription = "CCITT T.4 (TIFF 6 name)" },
                            { .compressionName = "ccittfax4", .compressionCode = COMPRESSION_CCITTFAX4, .compressionDescription = "CCITT Group 4 fax encoding" },
                            { .compressionName = "ccittt6", .compressionCode = COMPRESSION_CCITT_T6, .compressionDescription = "CCITT T.6 (TIFF 6 name)" },
                            { .compressionName = "lzw", .compressionCode = COMPRESSION_LZW, .compressionDescription = "Lempel-Ziv  & Welch" },
                            { .compressionName = "ojpeg", .compressionCode = COMPRESSION_OJPEG, .compressionDescription = "!6.0 JPEG" },
                            { .compressionName = "jpeg", .compressionCode = COMPRESSION_JPEG, .compressionDescription = "%JPEG DCT compression" },
                            { .compressionName = "next", .compressionCode = COMPRESSION_NEXT, .compressionDescription = "NeXT 2-bit RLE" },
                            { .compressionName = "packbits", .compressionCode = COMPRESSION_PACKBITS, .compressionDescription = "Macintosh RLE" },
                            { .compressionName = "ccittrlew", .compressionCode = COMPRESSION_CCITTRLEW, .compressionDescription = "CCITT modified Huffman RLE w/ word alignment" },
                            { .compressionName = "deflate", .compressionCode = COMPRESSION_DEFLATE, .compressionDescription = "Deflate compression" },
                            { .compressionName = "adeflate", .compressionCode = COMPRESSION_ADOBE_DEFLATE, .compressionDescription = "Deflate compression, as recognized by Adobe" },
                            { .compressionName = "dcs", .compressionCode = COMPRESSION_DCS, .compressionDescription = "Kodak DCS encoding" },
                            { .compressionName = "jbig", .compressionCode = COMPRESSION_JBIG, .compressionDescription = "ISO JBIG" },
                            { .compressionName = "jp2000", .compressionCode = COMPRESSION_JP2000, .compressionDescription = "Leadtools JPEG2000" },
                            { .compressionName = nullptr, .compressionCode = 0, .compressionDescription = nullptr } };

    // Initialize

    priv->f = nullptr;
    priv->curRow = 0;

    // Store the number of rows

    priv->numRows = height;

    // Set the compression

    compression = COMPRESSION_NONE;

    if (priv->compressionString == nullptr || strcmp(priv->compressionString, "") == 0) {
        compression = COMPRESSION_NONE;
    } else {
        int i;
        for (i = 0; compressionList[i].compressionName != nullptr; i++) {
            if (strcmp(priv->compressionString, compressionList[i].compressionName) == 0) {
                compression = compressionList[i].compressionCode;
                break;
            }
        }
        if (compressionList[i].compressionName == nullptr) {
            fprintf(stderr, "TiffWriter: Unknown compression type '%.10s', using 'none'.\n", priv->compressionString);
            fprintf(stderr, "Known compression types (the tiff library might not support every type)\n");
            for (i = 0; compressionList[i].compressionName != nullptr; i++) {
                fprintf(stderr, "%10s %s\n", compressionList[i].compressionName, compressionList[i].compressionDescription);
            }
        }
    }

    // Set bits per sample, samples per pixel, and photometric type from format

    bitspersample = (priv->format == MONOCHROME ? 1 : 8);

    switch (priv->format) {
    case MONOCHROME:
    case GRAY:
        samplesperpixel = 1;
        photometric = PHOTOMETRIC_MINISBLACK;
        break;

    case RGB:
        samplesperpixel = 3;
        photometric = PHOTOMETRIC_RGB;
        break;

    case RGBA_PREMULTIPLIED:
        samplesperpixel = 4;
        photometric = PHOTOMETRIC_RGB;
        break;

    case CMYK:
        samplesperpixel = 4;
        photometric = PHOTOMETRIC_SEPARATED;
        break;

    case RGB48:
        samplesperpixel = 3;
        bitspersample = 16;
        photometric = PHOTOMETRIC_RGB;
        break;
    }

    // Open the file

    if (openedFile == nullptr) {
        fprintf(stderr, "TiffWriter: No output file given.\n");
        return false;
    }

#    ifdef _WIN32
    // Convert C Library handle to Win32 Handle
    priv->f = TIFFFdOpen(_get_osfhandle(fileno(openedFile)), "-", "w");
#    else
    priv->f = TIFFFdOpen(fileno(openedFile), "-", "w");
#    endif

    if (!priv->f) {
        return false;
    }

    // Set TIFF tags

    TIFFSetField(priv->f, TIFFTAG_IMAGEWIDTH, width);
    TIFFSetField(priv->f, TIFFTAG_IMAGELENGTH, height);
    TIFFSetField(priv->f, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
    TIFFSetField(priv->f, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
    TIFFSetField(priv->f, TIFFTAG_BITSPERSAMPLE, bitspersample);
    TIFFSetField(priv->f, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField(priv->f, TIFFTAG_PHOTOMETRIC, photometric);
    TIFFSetField(priv->f, TIFFTAG_COMPRESSION, (uint16_t)compression);
    TIFFSetField(priv->f, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(priv->f, rowsperstrip));
    TIFFSetField(priv->f, TIFFTAG_XRESOLUTION, hDPI);
    TIFFSetField(priv->f, TIFFTAG_YRESOLUTION, vDPI);
    TIFFSetField(priv->f, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);

    if (priv->format == RGBA_PREMULTIPLIED) {
        uint16_t extra = EXTRASAMPLE_ASSOCALPHA;
        TIFFSetField(priv->f, TIFFTAG_EXTRASAMPLES, 1, &extra);
    }

    if (priv->format == CMYK) {
        TIFFSetField(priv->f, TIFFTAG_INKSET, INKSET_CMYK);
        TIFFSetField(priv->f, TIFFTAG_NUMBEROFINKS, 4);
    }

    return true;
}

bool TiffWriter::writePointers(unsigned char **rowPointers, int rowCount)
{
    // Write all rows to the file

    for (int row = 0; row < rowCount; row++) {
        if (TIFFWriteScanline(priv->f, rowPointers[row], row, 0) < 0) {
            fprintf(stderr, "TiffWriter: Error writing tiff row %d\n", row);
            return false;
        }
    }

    return true;
}

bool TiffWriter::writeRow(unsigned char **rowData)
{
    // Add a single row

    if (TIFFWriteScanline(priv->f, *rowData, priv->curRow, 0) < 0) {
        fprintf(stderr, "TiffWriter: Error writing tiff row %d\n", priv->curRow);
        return false;
    }

    priv->curRow++;

    return true;
}

bool TiffWriter::close()
{
    // Close the file

    TIFFClose(priv->f);

    return true;
}

#endif
