/* 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-1999 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>

#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "libpikaconfig/pikaconfig.h"

#include "core-types.h"

#include "pikacurve.h"
#include "pikadynamics.h"
#include "pikadynamics-load.h"
#include "pikadynamics-save.h"
#include "pikadynamicsoutput.h"

#include "pika-intl.h"


#define DEFAULT_NAME "Nameless dynamics"

enum
{
  PROP_0,

  PROP_NAME,

  PROP_OPACITY_OUTPUT,
  PROP_SIZE_OUTPUT,
  PROP_ANGLE_OUTPUT,
  PROP_COLOR_OUTPUT,
  PROP_FORCE_OUTPUT,
  PROP_HARDNESS_OUTPUT,
  PROP_ASPECT_RATIO_OUTPUT,
  PROP_SPACING_OUTPUT,
  PROP_RATE_OUTPUT,
  PROP_FLOW_OUTPUT,
  PROP_JITTER_OUTPUT
};


typedef struct _PikaDynamicsPrivate PikaDynamicsPrivate;

struct _PikaDynamicsPrivate
{
  PikaDynamicsOutput *opacity_output;
  PikaDynamicsOutput *hardness_output;
  PikaDynamicsOutput *force_output;
  PikaDynamicsOutput *rate_output;
  PikaDynamicsOutput *flow_output;
  PikaDynamicsOutput *size_output;
  PikaDynamicsOutput *aspect_ratio_output;
  PikaDynamicsOutput *color_output;
  PikaDynamicsOutput *angle_output;
  PikaDynamicsOutput *jitter_output;
  PikaDynamicsOutput *spacing_output;
};

#define GET_PRIVATE(output) \
        ((PikaDynamicsPrivate *) pika_dynamics_get_instance_private ((PikaDynamics *) (output)))


static void          pika_dynamics_finalize      (GObject      *object);
static void          pika_dynamics_set_property  (GObject      *object,
                                                  guint         property_id,
                                                  const GValue *value,
                                                  GParamSpec   *pspec);
static void          pika_dynamics_get_property  (GObject      *object,
                                                  guint         property_id,
                                                  GValue       *value,
                                                  GParamSpec   *pspec);
static void
       pika_dynamics_dispatch_properties_changed (GObject      *object,
                                                  guint         n_pspecs,
                                                  GParamSpec  **pspecs);

static const gchar * pika_dynamics_get_extension (PikaData     *data);
static void          pika_dynamics_copy          (PikaData     *data,
                                                  PikaData     *src_data);

static PikaDynamicsOutput *
                     pika_dynamics_create_output (PikaDynamics           *dynamics,
                                                  const gchar            *name,
                                                  PikaDynamicsOutputType  type);
static void          pika_dynamics_output_notify (GObject          *output,
                                                  const GParamSpec *pspec,
                                                  PikaDynamics     *dynamics);


G_DEFINE_TYPE_WITH_PRIVATE (PikaDynamics, pika_dynamics, PIKA_TYPE_DATA)

#define parent_class pika_dynamics_parent_class


static void
pika_dynamics_class_init (PikaDynamicsClass *klass)
{
  GObjectClass      *object_class   = G_OBJECT_CLASS (klass);
  PikaDataClass     *data_class     = PIKA_DATA_CLASS (klass);
  PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass);

  object_class->finalize                    = pika_dynamics_finalize;
  object_class->set_property                = pika_dynamics_set_property;
  object_class->get_property                = pika_dynamics_get_property;
  object_class->dispatch_properties_changed = pika_dynamics_dispatch_properties_changed;

  viewable_class->default_icon_name         = "pika-dynamics";

  data_class->save                          = pika_dynamics_save;
  data_class->get_extension                 = pika_dynamics_get_extension;
  data_class->copy                          = pika_dynamics_copy;

  PIKA_CONFIG_PROP_STRING (object_class, PROP_NAME,
                           "name",
                           NULL, NULL,
                           DEFAULT_NAME,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_OPACITY_OUTPUT,
                           "opacity-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_FORCE_OUTPUT,
                           "force-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_HARDNESS_OUTPUT,
                           "hardness-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_RATE_OUTPUT,
                           "rate-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_FLOW_OUTPUT,
                           "flow-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_SIZE_OUTPUT,
                           "size-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_ASPECT_RATIO_OUTPUT,
                           "aspect-ratio-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_COLOR_OUTPUT,
                           "color-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_ANGLE_OUTPUT,
                           "angle-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_JITTER_OUTPUT,
                           "jitter-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);

  PIKA_CONFIG_PROP_OBJECT (object_class, PROP_SPACING_OUTPUT,
                           "spacing-output",
                           NULL, NULL,
                           PIKA_TYPE_DYNAMICS_OUTPUT,
                           PIKA_CONFIG_PARAM_AGGREGATE);
}

static void
pika_dynamics_init (PikaDynamics *dynamics)
{
  PikaDynamicsPrivate *private = GET_PRIVATE (dynamics);

  private->opacity_output =
    pika_dynamics_create_output (dynamics,
                                 "opacity-output",
                                 PIKA_DYNAMICS_OUTPUT_OPACITY);

  private->force_output =
    pika_dynamics_create_output (dynamics,
                                 "force-output",
                                 PIKA_DYNAMICS_OUTPUT_FORCE);

  private->hardness_output =
    pika_dynamics_create_output (dynamics,
                                 "hardness-output",
                                 PIKA_DYNAMICS_OUTPUT_HARDNESS);

  private->rate_output =
    pika_dynamics_create_output (dynamics,
                                 "rate-output",
                                 PIKA_DYNAMICS_OUTPUT_RATE);

  private->flow_output =
    pika_dynamics_create_output (dynamics,
                                 "flow-output",
                                 PIKA_DYNAMICS_OUTPUT_FLOW);

  private->size_output =
    pika_dynamics_create_output (dynamics,
                                 "size-output",
                                 PIKA_DYNAMICS_OUTPUT_SIZE);

  private->aspect_ratio_output =
    pika_dynamics_create_output (dynamics,
                                 "aspect-ratio-output",
                                 PIKA_DYNAMICS_OUTPUT_ASPECT_RATIO);

  private->color_output =
    pika_dynamics_create_output (dynamics,
                                 "color-output",
                                 PIKA_DYNAMICS_OUTPUT_COLOR);

  private->angle_output =
    pika_dynamics_create_output (dynamics,
                                 "angle-output",
                                 PIKA_DYNAMICS_OUTPUT_ANGLE);

  private->jitter_output =
    pika_dynamics_create_output (dynamics,
                                 "jitter-output",
                                 PIKA_DYNAMICS_OUTPUT_JITTER);

  private->spacing_output =
    pika_dynamics_create_output (dynamics,
                                 "spacing-output",
                                 PIKA_DYNAMICS_OUTPUT_SPACING);
}

static void
pika_dynamics_finalize (GObject *object)
{
  PikaDynamicsPrivate *private = GET_PRIVATE (object);

  g_clear_object (&private->opacity_output);
  g_clear_object (&private->force_output);
  g_clear_object (&private->hardness_output);
  g_clear_object (&private->rate_output);
  g_clear_object (&private->flow_output);
  g_clear_object (&private->size_output);
  g_clear_object (&private->aspect_ratio_output);
  g_clear_object (&private->color_output);
  g_clear_object (&private->angle_output);
  g_clear_object (&private->jitter_output);
  g_clear_object (&private->spacing_output);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
pika_dynamics_set_property (GObject      *object,
                            guint         property_id,
                            const GValue *value,
                            GParamSpec   *pspec)
{
  PikaDynamicsPrivate *private     = GET_PRIVATE (object);
  PikaDynamicsOutput  *src_output  = NULL;
  PikaDynamicsOutput  *dest_output = NULL;

  switch (property_id)
    {
    case PROP_NAME:
      pika_object_set_name (PIKA_OBJECT (object), g_value_get_string (value));
      break;

    case PROP_OPACITY_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->opacity_output;
      break;

    case PROP_FORCE_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->force_output;
      break;

    case PROP_HARDNESS_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->hardness_output;
      break;

    case PROP_RATE_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->rate_output;
      break;

    case PROP_FLOW_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->flow_output;
      break;

    case PROP_SIZE_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->size_output;
      break;

    case PROP_ASPECT_RATIO_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->aspect_ratio_output;
      break;

    case PROP_COLOR_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->color_output;
      break;

    case PROP_ANGLE_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->angle_output;
      break;

    case PROP_JITTER_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->jitter_output;
      break;

    case PROP_SPACING_OUTPUT:
      src_output  = g_value_get_object (value);
      dest_output = private->spacing_output;
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }

  if (src_output && dest_output)
    {
      pika_config_copy (PIKA_CONFIG (src_output),
                        PIKA_CONFIG (dest_output),
                        PIKA_CONFIG_PARAM_SERIALIZE);
    }
}

static void
pika_dynamics_get_property (GObject    *object,
                            guint       property_id,
                            GValue     *value,
                            GParamSpec *pspec)
{
  PikaDynamicsPrivate *private = GET_PRIVATE (object);

  switch (property_id)
    {
    case PROP_NAME:
      g_value_set_string (value, pika_object_get_name (object));
      break;

    case PROP_OPACITY_OUTPUT:
      g_value_set_object (value, private->opacity_output);
      break;

    case PROP_FORCE_OUTPUT:
      g_value_set_object (value, private->force_output);
      break;

    case PROP_HARDNESS_OUTPUT:
      g_value_set_object (value, private->hardness_output);
      break;

    case PROP_RATE_OUTPUT:
      g_value_set_object (value, private->rate_output);
      break;

    case PROP_FLOW_OUTPUT:
      g_value_set_object (value, private->flow_output);
      break;

    case PROP_SIZE_OUTPUT:
      g_value_set_object (value, private->size_output);
      break;

    case PROP_ASPECT_RATIO_OUTPUT:
      g_value_set_object (value, private->aspect_ratio_output);
      break;

    case PROP_COLOR_OUTPUT:
      g_value_set_object (value, private->color_output);
      break;

    case PROP_ANGLE_OUTPUT:
      g_value_set_object (value, private->angle_output);
      break;

    case PROP_JITTER_OUTPUT:
      g_value_set_object (value, private->jitter_output);
      break;

    case PROP_SPACING_OUTPUT:
      g_value_set_object (value, private->spacing_output);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_dynamics_dispatch_properties_changed (GObject     *object,
                                           guint        n_pspecs,
                                           GParamSpec **pspecs)
{
  gint i;

  G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object,
                                                              n_pspecs, pspecs);

  for (i = 0; i < n_pspecs; i++)
    {
      if (pspecs[i]->flags & PIKA_CONFIG_PARAM_SERIALIZE)
        {
          pika_data_dirty (PIKA_DATA (object));
          break;
        }
    }
}

static const gchar *
pika_dynamics_get_extension (PikaData *data)
{
  return PIKA_DYNAMICS_FILE_EXTENSION;
}

static void
pika_dynamics_copy (PikaData *data,
                    PikaData *src_data)
{
  pika_data_freeze (data);

  pika_config_copy (PIKA_CONFIG (src_data),
                    PIKA_CONFIG (data), 0);

  pika_data_thaw (data);
}


/*  public functions  */

PikaData *
pika_dynamics_new (PikaContext *context,
                   const gchar *name)
{
  g_return_val_if_fail (name != NULL, NULL);
  g_return_val_if_fail (name[0] != '\0', NULL);

  return g_object_new (PIKA_TYPE_DYNAMICS,
                       "name", name,
                       NULL);
}

PikaData *
pika_dynamics_get_standard (PikaContext *context)
{
  static PikaData *standard_dynamics = NULL;

  if (! standard_dynamics)
    {
      g_set_weak_pointer (&standard_dynamics,
                          pika_dynamics_new (context, "Standard dynamics"));

      pika_data_clean (standard_dynamics);
      pika_data_make_internal (standard_dynamics, "pika-dynamics-standard");
    }

  return standard_dynamics;
}

PikaDynamicsOutput *
pika_dynamics_get_output (PikaDynamics           *dynamics,
                          PikaDynamicsOutputType  type_id)
{
  PikaDynamicsPrivate *private;

  g_return_val_if_fail (PIKA_IS_DYNAMICS (dynamics), NULL);

  private = GET_PRIVATE (dynamics);

  switch (type_id)
    {
    case PIKA_DYNAMICS_OUTPUT_OPACITY:
      return private->opacity_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_FORCE:
      return private->force_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_HARDNESS:
      return private->hardness_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_RATE:
      return private->rate_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_FLOW:
      return private->flow_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_SIZE:
      return private->size_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_ASPECT_RATIO:
      return private->aspect_ratio_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_COLOR:
      return private->color_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_ANGLE:
      return private->angle_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_JITTER:
      return private->jitter_output;
      break;

    case PIKA_DYNAMICS_OUTPUT_SPACING:
      return private->spacing_output;
      break;

    default:
      g_return_val_if_reached (NULL);
      break;
    }
}

gboolean
pika_dynamics_is_output_enabled (PikaDynamics           *dynamics,
                                 PikaDynamicsOutputType  type)
{
  PikaDynamicsOutput *output;

  g_return_val_if_fail (PIKA_IS_DYNAMICS (dynamics), FALSE);

  output = pika_dynamics_get_output (dynamics, type);

  return pika_dynamics_output_is_enabled (output);
}

gdouble
pika_dynamics_get_linear_value (PikaDynamics           *dynamics,
                                PikaDynamicsOutputType  type,
                                const PikaCoords       *coords,
                                PikaPaintOptions       *options,
                                gdouble                 fade_point)
{
  PikaDynamicsOutput *output;

  g_return_val_if_fail (PIKA_IS_DYNAMICS (dynamics), 0.0);

  output = pika_dynamics_get_output (dynamics, type);

  return pika_dynamics_output_get_linear_value (output, coords,
                                                options, fade_point);
}

gdouble
pika_dynamics_get_angular_value (PikaDynamics           *dynamics,
                                 PikaDynamicsOutputType  type,
                                 const PikaCoords       *coords,
                                 PikaPaintOptions       *options,
                                 gdouble                 fade_point)
{
  PikaDynamicsOutput *output;

  g_return_val_if_fail (PIKA_IS_DYNAMICS (dynamics), 0.0);

  output = pika_dynamics_get_output (dynamics, type);

  return pika_dynamics_output_get_angular_value (output, coords,
                                                 options, fade_point);
}

gdouble
pika_dynamics_get_aspect_value (PikaDynamics           *dynamics,
                                PikaDynamicsOutputType  type,
                                const PikaCoords       *coords,
                                PikaPaintOptions       *options,
                                gdouble                 fade_point)
{
  PikaDynamicsOutput *output;

  g_return_val_if_fail (PIKA_IS_DYNAMICS (dynamics), 0.0);

  output = pika_dynamics_get_output (dynamics, type);

  return pika_dynamics_output_get_aspect_value (output, coords,
                                                options, fade_point);
}


/*  private functions  */

static PikaDynamicsOutput *
pika_dynamics_create_output (PikaDynamics           *dynamics,
                             const gchar            *name,
                             PikaDynamicsOutputType  type)
{
  PikaDynamicsOutput *output = pika_dynamics_output_new (name, type);

  g_signal_connect (output, "notify",
                    G_CALLBACK (pika_dynamics_output_notify),
                    dynamics);

  return output;
}

static void
pika_dynamics_output_notify (GObject          *output,
                             const GParamSpec *pspec,
                             PikaDynamics     *dynamics)
{
  g_object_notify (G_OBJECT (dynamics), pika_object_get_name (output));
}
