/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>

#include "libpikabase/pikabase.h"
#include "libpikabase/pikaparasiteio.h"
#include "libpikacolor/pikacolor.h"

#include "core/core-types.h"

#include "core/pika.h"
#include "core/pikabrushpipe.h"
#include "core/pikabrushpipe-load.h"
#include "core/pikabrush-private.h"
#include "core/pikadrawable.h"
#include "core/pikaimage.h"
#include "core/pikalayer-new.h"
#include "core/pikaparamspecs.h"
#include "core/pikatempbuf.h"

#include "pdb/pikaprocedure.h"

#include "file-data-gbr.h"
#include "file-data-gih.h"

#include "pika-intl.h"


/*  local function prototypes  */

static PikaImage     * file_gih_pipe_to_image (Pika          *pika,
                                               PikaBrushPipe *pipe);
static PikaBrushPipe * file_gih_image_to_pipe (PikaImage     *image,
                                               const gchar   *name,
                                               gdouble        spacing,
                                               const gchar   *paramstring);


/*  public functions  */

PikaValueArray *
file_gih_load_invoker (PikaProcedure         *procedure,
                       Pika                  *pika,
                       PikaContext           *context,
                       PikaProgress          *progress,
                       const PikaValueArray  *args,
                       GError               **error)
{
  PikaValueArray *return_vals;
  PikaImage      *image = NULL;
  GFile          *file;
  GInputStream   *input;
  GError         *my_error = NULL;

  pika_set_busy (pika);

  file = g_value_get_object (pika_value_array_index (args, 1));

  input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error));

  if (input)
    {
      GList *list = pika_brush_pipe_load (context, file, input, error);

      if (list)
        {
          PikaBrushPipe *pipe = list->data;

          g_list_free (list);

          image = file_gih_pipe_to_image (pika, pipe);
          g_object_unref (pipe);
        }

      g_object_unref (input);
    }
  else
    {
      g_propagate_prefixed_error (error, my_error,
                                  _("Could not open '%s' for reading: "),
                                  pika_file_get_utf8_name (file));
    }

  return_vals = pika_procedure_get_return_values (procedure, image != NULL,
                                                  error ? *error : NULL);

  if (image)
    g_value_set_object (pika_value_array_index (return_vals, 1), image);

  pika_unset_busy (pika);

  return return_vals;
}

PikaValueArray *
file_gih_save_invoker (PikaProcedure         *procedure,
                       Pika                  *pika,
                       PikaContext           *context,
                       PikaProgress          *progress,
                       const PikaValueArray  *args,
                       GError               **error)
{
  PikaValueArray *return_vals;
  PikaImage      *image;
  PikaBrushPipe  *pipe;
  const gchar    *name;
  const gchar    *params;
  GFile          *file;
  gint            spacing;
  gboolean        success;

  pika_set_busy (pika);

  image   = g_value_get_object (pika_value_array_index (args, 1));
  /* XXX: drawable list is currently unused. GIH saving just uses the
   * whole layer list.
   */
  file    = g_value_get_object (pika_value_array_index (args, 4));
  spacing = g_value_get_int    (pika_value_array_index (args, 5));
  name    = g_value_get_string (pika_value_array_index (args, 6));
  params  = g_value_get_string (pika_value_array_index (args, 7));

  pipe = file_gih_image_to_pipe (image, name, spacing, params);

  pika_data_set_file (PIKA_DATA (pipe), file, TRUE, TRUE);

  success = pika_data_save (PIKA_DATA (pipe), error);

  g_object_unref (pipe);

  return_vals = pika_procedure_get_return_values (procedure, success,
                                                  error ? *error : NULL);

  pika_unset_busy (pika);

  return return_vals;
}


/*  private functions  */

static PikaImage *
file_gih_pipe_to_image (Pika          *pika,
                        PikaBrushPipe *pipe)
{
  PikaImage         *image;
  const gchar       *name;
  PikaImageBaseType  base_type;
  PikaParasite      *parasite;
  gchar              spacing[8];
  gint               i;

  if (pika_brush_get_pixmap (pipe->current))
    base_type = PIKA_RGB;
  else
    base_type = PIKA_GRAY;

  name = pika_object_get_name (pipe);

  image = pika_image_new (pika, 1, 1, base_type,
                          PIKA_PRECISION_U8_NON_LINEAR);

  parasite = pika_parasite_new ("pika-brush-pipe-name",
                                PIKA_PARASITE_PERSISTENT,
                                strlen (name) + 1, name);
  pika_image_parasite_attach (image, parasite, FALSE);
  pika_parasite_free (parasite);

  g_snprintf (spacing, sizeof (spacing), "%d",
              pika_brush_get_spacing (PIKA_BRUSH (pipe)));

  parasite = pika_parasite_new ("pika-brush-pipe-spacing",
                                PIKA_PARASITE_PERSISTENT,
                                strlen (spacing) + 1, spacing);
  pika_image_parasite_attach (image, parasite, FALSE);
  pika_parasite_free (parasite);

  for (i = 0; i < pipe->n_brushes; i++)
    {
      PikaLayer *layer;

      layer = file_gbr_brush_to_layer (image, pipe->brushes[i]);
      pika_image_add_layer (image, layer, NULL, i, FALSE);
    }

  if (pipe->params)
    {
      PikaPixPipeParams  params;
      gchar             *paramstring;

      /* Since we do not (yet) load the pipe as described in the
       * header, but use one layer per brush, we have to alter the
       * paramstring before attaching it as a parasite.
       *
       * (this comment copied over from file-gih, whatever "as
       * described in the header" means) -- mitch
       */

      pika_pixpipe_params_init (&params);
      pika_pixpipe_params_parse (pipe->params, &params);

      params.cellwidth  = pika_image_get_width  (image);
      params.cellheight = pika_image_get_height (image);
      params.cols       = 1;
      params.rows       = 1;

      paramstring = pika_pixpipe_params_build (&params);
      if (paramstring)
        {
          parasite = pika_parasite_new ("pika-brush-pipe-parameters",
                                        PIKA_PARASITE_PERSISTENT,
                                        strlen (paramstring) + 1,
                                        paramstring);
          pika_image_parasite_attach (image, parasite, FALSE);
          pika_parasite_free (parasite);
          g_free (paramstring);
        }

      pika_pixpipe_params_free (&params);
    }

  return image;
}

static PikaBrushPipe *
file_gih_image_to_pipe (PikaImage   *image,
                        const gchar *name,
                        gdouble      spacing,
                        const gchar *paramstring)
{
  PikaBrushPipe     *pipe;
  PikaPixPipeParams  params;
  GList             *layers;
  GList             *list;
  GList             *brushes = NULL;
  gint               image_width;
  gint               image_height;
  gint               i;

  pipe = g_object_new (PIKA_TYPE_BRUSH_PIPE,
                       "name",      name,
                       "mime-type", "image/x-pika-gih",
                       "spacing",   spacing,
                       NULL);

  pika_pixpipe_params_init (&params);
  pika_pixpipe_params_parse (paramstring, &params);

  image_width  = pika_image_get_width  (image);
  image_height = pika_image_get_height (image);

  layers = pika_image_get_layer_iter (image);

  for (list = layers; list; list = g_list_next (list))
    {
      PikaLayer *layer = list->data;
      gint       width;
      gint       height;
      gint       offset_x;
      gint       offset_y;
      gint       row;

      width  = pika_item_get_width  (PIKA_ITEM (layer));
      height = pika_item_get_height (PIKA_ITEM (layer));

      pika_item_get_offset (PIKA_ITEM (layer), &offset_x, &offset_y);

      /* Since we assume positive layer offsets we need to make sure this
       * is always the case or we will crash for grayscale layers.
       * See issue #6436. */
      if (offset_x < 0)
        {
          g_warning (_("Negative x offset: %d for layer %s corrected."),
                     offset_x, pika_object_get_name (layer));
          width += offset_x;
          offset_x = 0;
        }
      if (offset_y < 0)
        {
          g_warning (_("Negative y offset: %d for layer %s corrected."),
                     offset_y, pika_object_get_name (layer));
          height += offset_y;
          offset_y = 0;
        }

      for (row = 0; row < params.rows; row++)
        {
          gint y, ynext;
          gint thisy, thish;
          gint col;

          y     = (row * image_height) / params.rows;
          ynext = ((row + 1) * image_height / params.rows);

          /* Assume layer is offset to positive direction in x and y.
           * That's reasonable, as otherwise all of the layer
           * won't be visible.
           * thisy and thisx are in the drawable's coordinate space.
           */
          thisy = MAX (0, y - offset_y);
          thish = (ynext - offset_y) - thisy;
          thish = MIN (thish, height - thisy);

          for (col = 0; col < params.cols; col++)
            {
              PikaBrush *brush;
              gint       x, xnext;
              gint       thisx, thisw;

              x     = (col * image_width / params.cols);
              xnext = ((col + 1) * image_width / params.cols);
              thisx = MAX (0, x - offset_x);
              thisw = (xnext - offset_x) - thisx;
              thisw = MIN (thisw, width - thisx);

              brush = file_gbr_drawable_to_brush (PIKA_DRAWABLE (layer),
                                                  GEGL_RECTANGLE (thisx, thisy,
                                                                  thisw, thish),
                                                  pika_object_get_name (layer),
                                                  spacing);

              brushes = g_list_prepend (brushes, brush);
            }
        }
    }

  brushes = g_list_reverse (brushes);

  pipe->n_brushes = g_list_length (brushes);
  pipe->brushes   = g_new0 (PikaBrush *, pipe->n_brushes);

  for (list = brushes, i = 0; list; list = g_list_next (list), i++)
    pipe->brushes[i] = list->data;

  g_list_free (brushes);

  pika_pixpipe_params_free (&params);

  pika_brush_pipe_set_params (pipe, paramstring);

  return pipe;
}
