/* tiff loading for PIKA
 *  -Peter Mattis
 *
 * The TIFF loading code has been completely revamped by Nick Lamb
 * njl195@zepler.org.uk -- 18 May 1998
 * And it now gains support for tiles (and doubtless a zillion bugs)
 * njl195@zepler.org.uk -- 12 June 1999
 * LZW patent fuss continues :(
 * njl195@zepler.org.uk -- 20 April 2000
 * The code for this filter is based on "tifftopnm" and "pnmtotiff",
 *  2 programs that are a part of the netpbm package.
 * khk@khk.net -- 13 May 2000
 * Added support for ICCPROFILE tiff tag. If this tag is present in a
 * TIFF file, then a parasite is created and vice versa.
 * peter@kirchgessner.net -- 29 Oct 2002
 * Progress bar only when run interactive
 * Added support for layer offsets - pablo.dangelo@web.de -- 7 Jan 2004
 * Honor EXTRASAMPLES tag while loading images with alphachannel
 * pablo.dangelo@web.de -- 16 Jan 2004
 */

/*
 * tifftopnm.c - converts a Tagged Image File to a portable anymap
 *
 * Derived by Jef Poskanzer from tif2ras.c, which is:
 *
 * Copyright (c) 1990 by Sun Microsystems, Inc.
 *
 * Author: Patrick J. Naughton
 * naughton@wind.sun.com
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 */

#include "config.h"

#include <tiffio.h>

#include <libpika/pika.h>
#include <libpika/pikaui.h>

#include "file-tiff.h"
#include "file-tiff-io.h"
#include "file-tiff-load.h"
#include "file-tiff-save.h"

#include "libpika/stdplugins-intl.h"


#define SAVE_PROC      "file-tiff-save"
#define PLUG_IN_BINARY "file-tiff"


typedef struct _Tiff      Tiff;
typedef struct _TiffClass TiffClass;

struct _Tiff
{
  PikaPlugIn      parent_instance;
};

struct _TiffClass
{
  PikaPlugInClass parent_class;
};


#define TIFF_TYPE  (tiff_get_type ())
#define TIFF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIFF_TYPE, Tiff))

GType                    tiff_get_type         (void) G_GNUC_CONST;

static GList           * tiff_query_procedures (PikaPlugIn            *plug_in);
static PikaProcedure   * tiff_create_procedure (PikaPlugIn            *plug_in,
                                                const gchar           *name);

static PikaValueArray  * tiff_load             (PikaProcedure         *procedure,
                                                PikaRunMode            run_mode,
                                                GFile                 *file,
                                                PikaMetadata          *metadata,
                                                PikaMetadataLoadFlags *flags,
                                                PikaProcedureConfig   *config,
                                                gpointer               run_data);
static PikaValueArray  * tiff_save             (PikaProcedure         *procedure,
                                                PikaRunMode            run_mode,
                                                PikaImage             *image,
                                                gint                   n_drawables,
                                                PikaDrawable         **drawables,
                                                GFile                 *file,
                                                PikaMetadata          *metadata,
                                                PikaProcedureConfig   *config,
                                                gpointer               run_data);
static PikaPDBStatusType tiff_save_rec         (PikaProcedure         *procedure,
                                                PikaRunMode            run_mode,
                                                PikaImage             *orig_image,
                                                gint                   n_orig_drawables,
                                                PikaDrawable         **orig_drawables,
                                                GFile                 *file,
                                                PikaProcedureConfig   *config,
                                                PikaMetadata          *metadata,
                                                gboolean               retried,
                                                GError               **error);

static gboolean          image_is_monochrome  (PikaImage            *image);
static gboolean          image_is_multi_layer (PikaImage            *image);


G_DEFINE_TYPE (Tiff, tiff, PIKA_TYPE_PLUG_IN)

PIKA_MAIN (TIFF_TYPE)
DEFINE_STD_SET_I18N


static void
tiff_class_init (TiffClass *klass)
{
  PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = tiff_query_procedures;
  plug_in_class->create_procedure = tiff_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
tiff_init (Tiff *tiff)
{
}

static GList *
tiff_query_procedures (PikaPlugIn *plug_in)
{
  GList *list = NULL;

  list = g_list_append (list, g_strdup (LOAD_PROC));
  list = g_list_append (list, g_strdup (SAVE_PROC));

  return list;
}

static PikaProcedure *
tiff_create_procedure (PikaPlugIn  *plug_in,
                      const gchar *name)
{
  PikaProcedure *procedure = NULL;

  if (! strcmp (name, LOAD_PROC))
    {
      procedure = pika_load_procedure_new (plug_in, name,
                                           PIKA_PDB_PROC_TYPE_PLUGIN,
                                           tiff_load, NULL, NULL);

      pika_procedure_set_menu_label (procedure, _("TIFF or BigTIFF image"));

      pika_procedure_set_documentation (procedure,
                                        "Loads files of the TIFF and BigTIFF file formats",
                                        "Loads files of the Tag Image File Format (TIFF) and "
                                        "its 64-bit offsets variant (BigTIFF)",
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Spencer Kimball, Peter Mattis & Nick Lamb",
                                      "Nick Lamb <njl195@zepler.org.uk>",
                                      "1995-1996,1998-2003");

      pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
                                              TRUE);
      pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
                                          "image/tiff");
      pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
                                          "tif,tiff");
      pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
                                      "0,string,II*\\0,0,string,MM\\0*");

      /* TODO: the 2 below AUX arguments should likely be real arguments, but I
       * just wanted to get rid of pika_get_data/pika_set_data() usage at first
       * and didn't dig much into proper and full usage. Since it's always
       * possible to add arguments, but not to remove them, I prefer to make
       * them AUX for now and leave it as further exercise to decide whether it
       * should be part of the PDB API.
       */
      PIKA_PROC_AUX_ARG_ENUM (procedure, "target",
                              "Open _pages as", NULL,
                              PIKA_TYPE_PAGE_SELECTOR_TARGET,
                              PIKA_PAGE_SELECTOR_TARGET_LAYERS,
                              G_PARAM_READWRITE);

      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "keep-empty-space",
                                 _("_Keep empty space around imported layers"),
                                 NULL, TRUE, PIKA_PARAM_READWRITE);
    }
  else if (! strcmp (name, SAVE_PROC))
    {
      procedure = pika_save_procedure_new (plug_in, name,
                                           PIKA_PDB_PROC_TYPE_PLUGIN,
                                           TRUE, tiff_save, NULL, NULL);

      pika_procedure_set_image_types (procedure, "*");

      pika_procedure_set_menu_label (procedure, _("TIFF or BigTIFF image"));

      pika_procedure_set_documentation (procedure,
                                        "Exports files in the TIFF or BigTIFF file formats",
                                        "Exports files in the Tag Image File Format (TIFF) or "
                                        "its 64-bit offsets variant (BigTIFF) able to support "
                                        "much bigger file sizes",
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Spencer Kimball & Peter Mattis",
                                      "Spencer Kimball & Peter Mattis",
                                      "1995-1996,2000-2003");

      pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
                                              TRUE);
      pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
                                           _("TIFF"));
      pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
                                          "image/tiff");
      pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
                                          "tif,tiff");

      PIKA_PROC_ARG_BOOLEAN (procedure, "bigtiff",
                             _("Export in _BigTIFF variant file format"),
                             _("The BigTIFF variant file format uses 64-bit offsets, "
                               "hence supporting over 4GiB files and bigger"),
                             FALSE,
                             G_PARAM_READWRITE);

      PIKA_PROC_ARG_CHOICE (procedure, "compression",
                            _("Co_mpression"),
                            _("Compression type"),
                            pika_choice_new_with_values ("none",          PIKA_COMPRESSION_NONE,          _("None"),              NULL,
                                                         "lzw",           PIKA_COMPRESSION_LZW,           _("LZW"),               NULL,
                                                         "packbits",      PIKA_COMPRESSION_PACKBITS,      _("Pack Bits"),         NULL,
                                                         "adobe_deflate", PIKA_COMPRESSION_ADOBE_DEFLATE, _("Deflate"),           NULL,
                                                         "jpeg",          PIKA_COMPRESSION_JPEG,          _("JPEG"),              NULL,
                                                         "ccittfax3",     PIKA_COMPRESSION_CCITTFAX3,     _("CCITT Group 3 fax"), NULL,
                                                         "ccittfax4",     PIKA_COMPRESSION_CCITTFAX4,     _("CCITT Group 4 fax"), NULL,
                                                         NULL),
                            "none", G_PARAM_READWRITE);

      PIKA_PROC_ARG_BOOLEAN (procedure, "save-transparent-pixels",
                             _("Save color _values from transparent pixels"),
                             _("Keep the color data masked by an alpha channel "
                               "intact (do not store premultiplied components)"),
                             TRUE,
                             G_PARAM_READWRITE);

      PIKA_PROC_ARG_BOOLEAN (procedure, "cmyk",
                             _("Export as CMY_K"),
                             _("Create a CMYK TIFF image using the soft-proofing color profile"),
                             FALSE,
                             G_PARAM_READWRITE);

      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "save-layers",
                                 _("Save La_yers"),
                                 _("Save Layers"),
                                 TRUE,
                                 G_PARAM_READWRITE);

      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "crop-layers",
                                 _("Crop L_ayers"),
                                 _("Crop Layers"),
                                 TRUE,
                                 G_PARAM_READWRITE);

      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "save-geotiff",
                                 _("Save _GeoTIFF data"),
                                 _("Save GeoTIFF data"),
                                 TRUE,
                                 G_PARAM_READWRITE);

      pika_save_procedure_set_support_exif      (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_iptc      (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_xmp       (PIKA_SAVE_PROCEDURE (procedure), TRUE);
#ifdef TIFFTAG_ICCPROFILE
      pika_save_procedure_set_support_profile   (PIKA_SAVE_PROCEDURE (procedure), TRUE);
#endif
      pika_save_procedure_set_support_thumbnail (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_comment   (PIKA_SAVE_PROCEDURE (procedure), TRUE);
    }

  return procedure;
}

static PikaValueArray *
tiff_load (PikaProcedure         *procedure,
           PikaRunMode            run_mode,
           GFile                 *file,
           PikaMetadata          *metadata,
           PikaMetadataLoadFlags *flags,
           PikaProcedureConfig   *config,
           gpointer               run_data)
{
  PikaValueArray    *return_vals;
  PikaPDBStatusType  status;
  PikaImage         *image              = NULL;
  gboolean           resolution_loaded  = FALSE;
  gboolean           profile_loaded     = FALSE;
  gboolean           ps_metadata_loaded = FALSE;
  GError            *error = NULL;

  gegl_init (NULL, NULL);

  if (run_mode == PIKA_RUN_INTERACTIVE)
    pika_ui_init (PLUG_IN_BINARY);

  status = load_image (file, run_mode, &image,
                       &resolution_loaded,
                       &profile_loaded,
                       &ps_metadata_loaded,
                       config, &error);

  if (!image)
    return pika_procedure_new_return_values (procedure, status, error);

  if (resolution_loaded)
    *flags &= ~PIKA_METADATA_LOAD_RESOLUTION;

  if (profile_loaded)
    *flags &= ~PIKA_METADATA_LOAD_COLORSPACE;

  return_vals = pika_procedure_new_return_values (procedure,
                                                  PIKA_PDB_SUCCESS,
                                                  NULL);

  PIKA_VALUES_SET_IMAGE (return_vals, 1, image);

  return return_vals;
}

static PikaValueArray *
tiff_save (PikaProcedure        *procedure,
           PikaRunMode           run_mode,
           PikaImage            *image,
           gint                  n_drawables,
           PikaDrawable        **drawables,
           GFile                *file,
           PikaMetadata         *metadata,
           PikaProcedureConfig  *config,
           gpointer              run_data)
{
  GError            *error  = NULL;
  PikaPDBStatusType  status = PIKA_PDB_SUCCESS;

  gegl_init (NULL, NULL);

  switch (run_mode)
    {
    case PIKA_RUN_INTERACTIVE:
    case PIKA_RUN_WITH_LAST_VALS:
      pika_ui_init (PLUG_IN_BINARY);
      break;
    default:
      break;
    }

  status = tiff_save_rec (procedure, run_mode, image,
                          n_drawables, drawables,
                          file, config, metadata, FALSE, &error);

  return pika_procedure_new_return_values (procedure, status, error);
}

static PikaPDBStatusType
tiff_save_rec (PikaProcedure        *procedure,
               PikaRunMode           run_mode,
               PikaImage            *orig_image,
               gint                  n_orig_drawables,
               PikaDrawable        **orig_drawables,
               GFile                *file,
               PikaProcedureConfig  *config,
               PikaMetadata         *metadata,
               gboolean              retried,
               GError              **error)
{
  PikaImage         *image       = orig_image;
  PikaDrawable     **drawables   = orig_drawables;
  gint               n_drawables = n_orig_drawables;
  PikaPDBStatusType  status      = PIKA_PDB_SUCCESS;
  PikaExportReturn   export      = PIKA_EXPORT_CANCEL;
  gboolean           bigtiff     = FALSE;

  if (run_mode == PIKA_RUN_INTERACTIVE)
    {
      if (! save_dialog (orig_image, procedure, G_OBJECT (config),
                         n_drawables == 1 ? pika_drawable_has_alpha (drawables[0]) : TRUE,
                         image_is_monochrome (orig_image),
                         pika_image_get_base_type (orig_image) == PIKA_INDEXED,
                         image_is_multi_layer (orig_image),
                         retried))
        {
          return PIKA_PDB_CANCEL;
        }
    }

  switch (run_mode)
    {
    case PIKA_RUN_INTERACTIVE:
    case PIKA_RUN_WITH_LAST_VALS:
      {
        PikaExportCapabilities capabilities;
        PikaCompression        compression;
        gboolean               save_layers;
        gboolean               crop_layers;

        g_object_get (config,
                      "bigtiff",     &bigtiff,
                      "save-layers", &save_layers,
                      "crop-layers", &crop_layers,
                      NULL);
        compression = pika_procedure_config_get_choice_id (config, "compression");

        if (compression == PIKA_COMPRESSION_CCITTFAX3 ||
            compression == PIKA_COMPRESSION_CCITTFAX4)
          {
            /* G3/G4 are fax compressions. They only support
             * monochrome images without alpha support.
             */
            capabilities = PIKA_EXPORT_CAN_HANDLE_INDEXED;
          }
        else
          {
            capabilities = (PIKA_EXPORT_CAN_HANDLE_RGB     |
                            PIKA_EXPORT_CAN_HANDLE_GRAY    |
                            PIKA_EXPORT_CAN_HANDLE_INDEXED |
                            PIKA_EXPORT_CAN_HANDLE_ALPHA);
          }

        if (save_layers && image_is_multi_layer (orig_image))
          {
            capabilities |= PIKA_EXPORT_CAN_HANDLE_LAYERS;

            if (crop_layers)
              capabilities |= PIKA_EXPORT_NEEDS_CROP;
          }

        export = pika_export_image (&image, &n_drawables, &drawables, "TIFF",
                                    capabilities);

        if (export == PIKA_EXPORT_CANCEL)
          return PIKA_PDB_CANCEL;
      }
      break;

    default:
      break;
    }

#if 0
  /* FIXME */
  if (n_drawables != 1 && tsvals.save_layers)
    {
      g_set_error (&error, G_FILE_ERROR, 0,
                   _("\"Save layers\" option not set while trying to export multiple layers."));

      status = PIKA_PDB_CALLING_ERROR;
    }
#endif

  if (status == PIKA_PDB_SUCCESS)
    {
      if (! save_image (file, image, orig_image, G_OBJECT (config),
                        metadata, error))
        status = PIKA_PDB_EXECUTION_ERROR;
    }

  if (export == PIKA_EXPORT_EXPORT)
    {
      pika_image_delete (image);
      g_free (drawables);
    }

  if (status == PIKA_PDB_EXECUTION_ERROR &&
      run_mode == PIKA_RUN_INTERACTIVE   &&
      ! retried && ! bigtiff && tiff_got_file_size_error ())
    {
      /* Retrying but just once, when the save failed because we exceeded
       * TIFF max size, to propose BigTIFF instead. */
      tiff_reset_file_size_error ();
      g_clear_error (error);

      return tiff_save_rec (procedure, run_mode,
                            orig_image, n_orig_drawables, orig_drawables,
                            file, config, metadata, TRUE, error);
    }

  return status;
}

static gboolean
image_is_monochrome (PikaImage *image)
{
  guchar   *colors;
  gint      num_colors;
  gboolean  monochrome = FALSE;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);

  colors = pika_image_get_colormap (image, NULL, &num_colors);

  if (colors)
    {
      if (num_colors == 2 || num_colors == 1)
        {
          const guchar  bw_map[] = { 0, 0, 0, 255, 255, 255 };
          const guchar  wb_map[] = { 255, 255, 255, 0, 0, 0 };

          if (memcmp (colors, bw_map, 3 * num_colors) == 0 ||
              memcmp (colors, wb_map, 3 * num_colors) == 0)
            {
              monochrome = TRUE;
            }
        }

      g_free (colors);
    }

  return monochrome;
}

static gboolean
image_is_multi_layer (PikaImage *image)
{
  gint32 n_layers;

  g_free (pika_image_get_layers (image, &n_layers));

  return (n_layers > 1);
}

gint
pika_compression_to_tiff_compression (PikaCompression compression)
{
  switch (compression)
    {
    case PIKA_COMPRESSION_NONE:          return COMPRESSION_NONE;
    case PIKA_COMPRESSION_LZW:           return COMPRESSION_LZW;
    case PIKA_COMPRESSION_PACKBITS:      return COMPRESSION_PACKBITS;
    case PIKA_COMPRESSION_ADOBE_DEFLATE: return COMPRESSION_ADOBE_DEFLATE;
    case PIKA_COMPRESSION_JPEG:          return COMPRESSION_JPEG;
    case PIKA_COMPRESSION_CCITTFAX3:     return COMPRESSION_CCITTFAX3;
    case PIKA_COMPRESSION_CCITTFAX4:     return COMPRESSION_CCITTFAX4;
    }

  return COMPRESSION_NONE;
}

PikaCompression
tiff_compression_to_pika_compression (gint compression)
{
  switch (compression)
    {
    case COMPRESSION_NONE:          return PIKA_COMPRESSION_NONE;
    case COMPRESSION_LZW:           return PIKA_COMPRESSION_LZW;
    case COMPRESSION_PACKBITS:      return PIKA_COMPRESSION_PACKBITS;
    case COMPRESSION_DEFLATE:       return PIKA_COMPRESSION_ADOBE_DEFLATE;
    case COMPRESSION_ADOBE_DEFLATE: return PIKA_COMPRESSION_ADOBE_DEFLATE;
    case COMPRESSION_OJPEG:         return PIKA_COMPRESSION_JPEG;
    case COMPRESSION_JPEG:          return PIKA_COMPRESSION_JPEG;
    case COMPRESSION_CCITTFAX3:     return PIKA_COMPRESSION_CCITTFAX3;
    case COMPRESSION_CCITTFAX4:     return PIKA_COMPRESSION_CCITTFAX4;
    }

  return PIKA_COMPRESSION_NONE;
}
