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

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

#include "paint-types.h"

#include "gegl/pika-gegl-loops.h"

#include "core/pika.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaimage.h"
#include "core/pikasymmetry.h"

#include "pikadodgeburn.h"
#include "pikadodgeburnoptions.h"

#include "pika-intl.h"


static void   pika_dodge_burn_paint  (PikaPaintCore    *paint_core,
                                      GList            *drawables,
                                      PikaPaintOptions *paint_options,
                                      PikaSymmetry     *sym,
                                      PikaPaintState    paint_state,
                                      guint32           time);
static void   pika_dodge_burn_motion (PikaPaintCore    *paint_core,
                                      PikaDrawable     *drawable,
                                      PikaPaintOptions *paint_options,
                                      PikaSymmetry     *sym);


G_DEFINE_TYPE (PikaDodgeBurn, pika_dodge_burn, PIKA_TYPE_BRUSH_CORE)

#define parent_class pika_dodge_burn_parent_class


void
pika_dodge_burn_register (Pika                      *pika,
                          PikaPaintRegisterCallback  callback)
{
  (* callback) (pika,
                PIKA_TYPE_DODGE_BURN,
                PIKA_TYPE_DODGE_BURN_OPTIONS,
                "pika-dodge-burn",
                _("Dodge/Burn"),
                "pika-tool-dodge");
}

static void
pika_dodge_burn_class_init (PikaDodgeBurnClass *klass)
{
  PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
  PikaBrushCoreClass *brush_core_class = PIKA_BRUSH_CORE_CLASS (klass);

  paint_core_class->paint = pika_dodge_burn_paint;

  brush_core_class->handles_changing_brush = TRUE;
}

static void
pika_dodge_burn_init (PikaDodgeBurn *dodgeburn)
{
}

static void
pika_dodge_burn_paint (PikaPaintCore    *paint_core,
                       GList            *drawables,
                       PikaPaintOptions *paint_options,
                       PikaSymmetry     *sym,
                       PikaPaintState    paint_state,
                       guint32           time)
{
  g_return_if_fail (g_list_length (drawables) == 1);

  switch (paint_state)
    {
    case PIKA_PAINT_STATE_INIT:
      break;

    case PIKA_PAINT_STATE_MOTION:
      for (GList *iter = drawables; iter; iter = iter->next)
        pika_dodge_burn_motion (paint_core, iter->data, paint_options, sym);
      break;

    case PIKA_PAINT_STATE_FINISH:
      break;
    }
}

static void
pika_dodge_burn_motion (PikaPaintCore    *paint_core,
                        PikaDrawable     *drawable,
                        PikaPaintOptions *paint_options,
                        PikaSymmetry     *sym)
{
  PikaBrushCore        *brush_core = PIKA_BRUSH_CORE (paint_core);
  PikaDodgeBurnOptions *options    = PIKA_DODGE_BURN_OPTIONS (paint_options);
  PikaContext          *context    = PIKA_CONTEXT (paint_options);
  PikaDynamics         *dynamics   = PIKA_BRUSH_CORE (paint_core)->dynamics;
  PikaImage            *image      = pika_item_get_image (PIKA_ITEM (drawable));
  GeglBuffer           *src_buffer;
  GeglBuffer           *paint_buffer;
  gint                  paint_buffer_x;
  gint                  paint_buffer_y;
  gdouble               fade_point;
  gdouble               opacity;
  gdouble               force;
  PikaCoords            coords;
  gint                  off_x, off_y;
  gint                  paint_width, paint_height;
  gint                  n_strokes;
  gint                  i;

  fade_point = pika_paint_options_get_fade (paint_options, image,
                                            paint_core->pixel_dist);

  pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);
  coords = *(pika_symmetry_get_origin (sym));
  coords.x -= off_x;
  coords.y -= off_y;
  pika_symmetry_set_origin (sym, drawable, &coords);

  opacity = pika_dynamics_get_linear_value (dynamics,
                                            PIKA_DYNAMICS_OUTPUT_OPACITY,
                                            &coords,
                                            paint_options,
                                            fade_point);
  if (opacity == 0.0)
    return;

  if (paint_options->application_mode == PIKA_PAINT_CONSTANT)
    src_buffer = pika_paint_core_get_orig_image (paint_core, drawable);
  else
    src_buffer = pika_drawable_get_buffer (drawable);

  pika_brush_core_eval_transform_dynamics (brush_core,
                                           image,
                                           paint_options,
                                           &coords);
  n_strokes = pika_symmetry_get_size (sym);
  for (i = 0; i < n_strokes; i++)
    {
      coords = *(pika_symmetry_get_coords (sym, i));

      pika_brush_core_eval_transform_symmetry (brush_core, sym, i);

      paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
                                                       paint_options,
                                                       PIKA_LAYER_MODE_NORMAL,
                                                       &coords,
                                                       &paint_buffer_x,
                                                       &paint_buffer_y,
                                                       &paint_width,
                                                       &paint_height);
      if (! paint_buffer)
        continue;

      /*  DodgeBurn the region  */
      pika_gegl_dodgeburn (src_buffer,
                           GEGL_RECTANGLE (paint_buffer_x,
                                           paint_buffer_y,
                                           gegl_buffer_get_width  (paint_buffer),
                                           gegl_buffer_get_height (paint_buffer)),
                           paint_buffer,
                           GEGL_RECTANGLE (0, 0, 0, 0),
                           options->exposure / 100.0,
                           options->type,
                           options->mode);

      if (pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_FORCE))
        force = pika_dynamics_get_linear_value (dynamics,
                                                PIKA_DYNAMICS_OUTPUT_FORCE,
                                                &coords,
                                                paint_options,
                                                fade_point);
      else
        force = paint_options->brush_force;

      /* Replace the newly dodgedburned area (paint_area) to the image */
      pika_brush_core_replace_canvas (PIKA_BRUSH_CORE (paint_core), drawable,
                                      &coords,
                                      MIN (opacity, PIKA_OPACITY_OPAQUE),
                                      pika_context_get_opacity (context),
                                      pika_paint_options_get_brush_mode (paint_options),
                                      force,
                                      paint_options->application_mode);
    }
}
