/******************************************************************************
 * Project:  libtiff tools
 * Purpose:  Mainline for setting metadata in existing TIFF files.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Frank Warmerdam
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 ******************************************************************************
 */

#include "tif_config.h"

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

#include "tiffio.h"

#ifdef NEED_LIBPORT
#include "libport.h"
#endif

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif

static const char usageMsg[] =
    "Set the value of a TIFF header to a specified value\n\n"
    "usage: tiffset [options] filename\n"
    "where options are:\n"
    " -s  <tagname> [count] <value>...   set the tag value\n"
    " -u  <tagname> to unset the tag\n"
    " -d  <dirno> set the directory\n"
    " -sd <diroff> set the subdirectory\n"
    " -sf <tagname> <filename>  read the tag value from file (for ASCII tags "
    "only)\n"
    " -h  this help screen\n";

static void usage(int code)
{
    FILE *out = (code == EXIT_SUCCESS) ? stdout : stderr;

    fprintf(out, "%s\n\n", TIFFGetVersion());
    fprintf(out, "%s", usageMsg);
    exit(code);
}

static const TIFFField *GetField(TIFF *tiff, const char *tagname)
{
    const TIFFField *fip;

    if (atoi(tagname) > 0)
        fip = TIFFFieldWithTag(tiff, (ttag_t)atoi(tagname));
    else
        fip = TIFFFieldWithName(tiff, tagname);

    if (!fip)
    {
        fprintf(stderr, "Field name \"%s\" is not recognised.\n", tagname);
        return (TIFFField *)NULL;
    }

    return fip;
}

int main(int argc, char *argv[])
{
    TIFF *tiff;
    int arg_index;

    if (argc < 2)
        usage(EXIT_FAILURE);

    tiff = TIFFOpen(argv[argc - 1], "r+");
    if (tiff == NULL)
        return EXIT_FAILURE;

    for (arg_index = 1; arg_index < argc - 1; arg_index++)
    {
        if (strcmp(argv[arg_index], "-d") == 0 && arg_index < argc - 2)
        {
            arg_index++;
            if (TIFFSetDirectory(tiff, atoi(argv[arg_index])) != 1)
            {
                fprintf(stderr, "Failed to set directory=%s\n",
                        argv[arg_index]);
                return EXIT_FAILURE;
            }
            arg_index++;
        }
        if (strcmp(argv[arg_index], "-sd") == 0 && arg_index < argc - 2)
        {
            arg_index++;
            if (TIFFSetSubDirectory(tiff, atoi(argv[arg_index])) != 1)
            {
                fprintf(stderr, "Failed to set sub directory=%s\n",
                        argv[arg_index]);
                return EXIT_FAILURE;
            }
            arg_index++;
        }
        /* Add unset option to tiffset -- Zach Baker (niquil@niquil.net)
         * 11/14/2012 */
        if (strcmp(argv[arg_index], "-u") == 0 && arg_index < argc - 2)
        {
            const TIFFField *fip;
            const char *tagname;
            arg_index++;
            tagname = argv[arg_index];
            fip = GetField(tiff, tagname);
            if (!fip)
                return EXIT_FAILURE;

            if (TIFFUnsetField(tiff, TIFFFieldTag(fip)) != 1)
            {
                fprintf(stderr, "Failed to unset %s\n", TIFFFieldName(fip));
            }
            arg_index++;
        }
        else if (strcmp(argv[arg_index], "-s") == 0 && arg_index < argc - 3)
        {
            const TIFFField *fip;
            const char *tagname;

            arg_index++;
            tagname = argv[arg_index];
            fip = GetField(tiff, tagname);

            if (!fip)
                return 3;

            arg_index++;
            if (TIFFFieldDataType(fip) == TIFF_ASCII)
            {
                if (TIFFFieldPassCount(fip))
                {
                    size_t len;
                    len = strlen(argv[arg_index]) + 1;
                    if (len > UINT16_MAX ||
                        TIFFSetField(tiff, TIFFFieldTag(fip), (uint16_t)len,
                                     argv[arg_index]) != 1)
                        fprintf(stderr, "Failed to set %s=%s\n",
                                TIFFFieldName(fip), argv[arg_index]);
                }
                else
                {
                    if (TIFFSetField(tiff, TIFFFieldTag(fip),
                                     argv[arg_index]) != 1)
                        fprintf(stderr, "Failed to set %s=%s\n",
                                TIFFFieldName(fip), argv[arg_index]);
                }
            }
            else if (TIFFFieldWriteCount(fip) > 0 ||
                     TIFFFieldWriteCount(fip) == TIFF_VARIABLE)
            {
                int ret = 1;
                short wc;

                if (TIFFFieldWriteCount(fip) == TIFF_VARIABLE)
                    wc = atoi(argv[arg_index++]);
                else
                    wc = TIFFFieldWriteCount(fip);

                if (argc - arg_index < wc)
                {
                    fprintf(stderr,
                            "Number of tag values is not enough. "
                            "Expected %d values for %s tag, got %d\n",
                            wc, TIFFFieldName(fip), argc - arg_index);
                    return EXIT_FAILURE;
                }

                if (wc > 1 || TIFFFieldWriteCount(fip) == TIFF_VARIABLE)
                {
                    int i, size;
                    void *array;

                    switch (TIFFFieldDataType(fip))
                    {
                        /*
                         * XXX: We can't use TIFFDataWidth()
                         * to determine the space needed to store
                         * the value. For TIFF_RATIONAL values
                         * TIFFDataWidth() returns 8, but we use 4-byte
                         * float to represent rationals.
                         */
                        case TIFF_BYTE:
                        case TIFF_ASCII:
                        case TIFF_SBYTE:
                        case TIFF_UNDEFINED:
                        default:
                            size = 1;
                            break;

                        case TIFF_SHORT:
                        case TIFF_SSHORT:
                            size = 2;
                            break;

                        case TIFF_LONG:
                        case TIFF_SLONG:
                        case TIFF_FLOAT:
                        case TIFF_IFD:
                        case TIFF_RATIONAL:
                        case TIFF_SRATIONAL:
                            size = 4;
                            break;

                        case TIFF_LONG8:
                        case TIFF_SLONG8:
                        case TIFF_IFD8:
                        case TIFF_DOUBLE:
                            size = 8;
                            break;
                    }

                    array = _TIFFmalloc(wc * size);
                    if (!array)
                    {
                        fprintf(stderr, "No space for %s tag\n", tagname);
                        return EXIT_FAILURE;
                    }

                    switch (TIFFFieldDataType(fip))
                    {
                        case TIFF_BYTE:
                            for (i = 0; i < wc; i++)
                                ((uint8_t *)array)[i] =
                                    atoi(argv[arg_index + i]);
                            break;
                        case TIFF_SHORT:
                            for (i = 0; i < wc; i++)
                                ((uint16_t *)array)[i] =
                                    atoi(argv[arg_index + i]);
                            break;
                        case TIFF_SBYTE:
                            for (i = 0; i < wc; i++)
                                ((int8_t *)array)[i] =
                                    atoi(argv[arg_index + i]);
                            break;
                        case TIFF_SSHORT:
                            for (i = 0; i < wc; i++)
                                ((int16_t *)array)[i] =
                                    atoi(argv[arg_index + i]);
                            break;
                        case TIFF_LONG:
                            for (i = 0; i < wc; i++)
                                ((uint32_t *)array)[i] =
                                    atol(argv[arg_index + i]);
                            break;
                        case TIFF_SLONG:
                        case TIFF_IFD:
                            for (i = 0; i < wc; i++)
                                ((int32_t *)array)[i] =
                                    atol(argv[arg_index + i]);
                            break;
                        case TIFF_LONG8:
                            for (i = 0; i < wc; i++)
                                ((uint64_t *)array)[i] = strtoll(
                                    argv[arg_index + i], (char **)NULL, 10);
                            break;
                        case TIFF_SLONG8:
                        case TIFF_IFD8:
                            for (i = 0; i < wc; i++)
                                ((int64_t *)array)[i] = strtoll(
                                    argv[arg_index + i], (char **)NULL, 10);
                            break;
                        case TIFF_DOUBLE:
                            for (i = 0; i < wc; i++)
                                ((double *)array)[i] =
                                    atof(argv[arg_index + i]);
                            break;
                        case TIFF_RATIONAL:
                        case TIFF_SRATIONAL:
                        case TIFF_FLOAT:
                            for (i = 0; i < wc; i++)
                                ((float *)array)[i] =
                                    (float)atof(argv[arg_index + i]);
                            break;
                        default:
                            break;
                    }

                    if (TIFFFieldPassCount(fip))
                    {
                        ret = TIFFSetField(tiff, TIFFFieldTag(fip), wc, array);
                    }
                    else if (TIFFFieldTag(fip) == TIFFTAG_PAGENUMBER ||
                             TIFFFieldTag(fip) == TIFFTAG_HALFTONEHINTS ||
                             TIFFFieldTag(fip) == TIFFTAG_YCBCRSUBSAMPLING ||
                             TIFFFieldTag(fip) == TIFFTAG_DOTRANGE)
                    {
                        if (TIFFFieldDataType(fip) == TIFF_BYTE)
                        {
                            ret = TIFFSetField(tiff, TIFFFieldTag(fip),
                                               ((uint8_t *)array)[0],
                                               ((uint8_t *)array)[1]);
                        }
                        else if (TIFFFieldDataType(fip) == TIFF_SHORT)
                        {
                            ret = TIFFSetField(tiff, TIFFFieldTag(fip),
                                               ((uint16_t *)array)[0],
                                               ((uint16_t *)array)[1]);
                        }
                    }
                    else
                    {
                        ret = TIFFSetField(tiff, TIFFFieldTag(fip), array);
                    }

                    _TIFFfree(array);
                }
                else
                {
                    switch (TIFFFieldDataType(fip))
                    {
                        case TIFF_BYTE:
                        case TIFF_SHORT:
                        case TIFF_SBYTE:
                        case TIFF_SSHORT:
                            ret = TIFFSetField(tiff, TIFFFieldTag(fip),
                                               atoi(argv[arg_index++]));
                            break;
                        case TIFF_LONG:
                        case TIFF_SLONG:
                        case TIFF_IFD:
                            ret = TIFFSetField(tiff, TIFFFieldTag(fip),
                                               atol(argv[arg_index++]));
                            break;
                        case TIFF_LONG8:
                        case TIFF_SLONG8:
                        case TIFF_IFD8:
                            ret = TIFFSetField(
                                tiff, TIFFFieldTag(fip),
                                strtoll(argv[arg_index++], (char **)NULL, 10));
                            break;
                        case TIFF_DOUBLE:
                            ret = TIFFSetField(tiff, TIFFFieldTag(fip),
                                               atof(argv[arg_index++]));
                            break;
                        case TIFF_RATIONAL:
                        case TIFF_SRATIONAL:
                        case TIFF_FLOAT:
                            ret = TIFFSetField(tiff, TIFFFieldTag(fip),
                                               (float)atof(argv[arg_index++]));
                            break;
                        default:
                            break;
                    }
                }

                if (ret != 1)
                    fprintf(stderr, "Failed to set %s\n", TIFFFieldName(fip));
                arg_index += wc;
            }
        }
        else if (strcmp(argv[arg_index], "-sf") == 0 && arg_index < argc - 3)
        {
            FILE *fp;
            const TIFFField *fip;
            char *text;
            size_t len;
            int ret;

            arg_index++;
            fip = GetField(tiff, argv[arg_index]);

            if (!fip)
                return EXIT_FAILURE;

            if (TIFFFieldDataType(fip) != TIFF_ASCII)
            {
                fprintf(stderr,
                        "Only ASCII tags can be set from file. "
                        "%s is not ASCII tag.\n",
                        TIFFFieldName(fip));
                return EXIT_FAILURE;
            }

            arg_index++;
            fp = fopen(argv[arg_index], "rt");
            if (fp == NULL)
            {
                perror(argv[arg_index]);
                continue;
            }

            text = (char *)malloc(1000000);
            if (text == NULL)
            {
                fprintf(stderr, "Memory allocation error\n");
                fclose(fp);
                continue;
            }
            len = fread(text, 1, 999999, fp);
            text[len] = '\0';

            fclose(fp);

            if (TIFFFieldPassCount(fip))
            {
                ret =
                    TIFFSetField(tiff, TIFFFieldTag(fip), (uint16_t)len, text);
            }
            else
            {
                ret = TIFFSetField(tiff, TIFFFieldTag(fip), text);
            }
            if (!ret)
            {
                fprintf(stderr, "Failed to set %s from file %s\n",
                        TIFFFieldName(fip), argv[arg_index]);
            }

            _TIFFfree(text);
            arg_index++;
        }
        else if (strcmp(argv[arg_index], "-h") == 0 ||
                 strcmp(argv[arg_index], "--help") == 0)
        {
            usage(EXIT_SUCCESS);
        }
        else
        {
            fprintf(stderr, "Unrecognised option: %s\n", argv[arg_index]);
            usage(EXIT_FAILURE);
        }
    }

    TIFFRewriteDirectory(tiff);
    TIFFClose(tiff);
    return EXIT_SUCCESS;
}
