/* 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 <gexiv2/gexiv2.h>

#include "core-types.h"

#include "config/pikadialogconfig.h"

#include "vectors/pikavectors.h"

#include "pika.h"
#include "pikacontainer.h"
#include "pikacontext.h"
#include "pikaguide.h"
#include "pikaimage.h"
#include "pikaimage-flip.h"
#include "pikaimage-metadata.h"
#include "pikaimage-rotate.h"
#include "pikaimage-guides.h"
#include "pikaimage-sample-points.h"
#include "pikaimage-undo.h"
#include "pikaimage-undo-push.h"
#include "pikaitem.h"
#include "pikalayer.h"
#include "pikaobjectqueue.h"
#include "pikaprogress.h"
#include "pikasamplepoint.h"


static void  pika_image_rotate_item_offset   (PikaImage         *image,
                                              PikaRotationType   rotate_type,
                                              PikaItem          *item,
                                              gint               off_x,
                                              gint               off_y);
static void  pika_image_rotate_guides        (PikaImage         *image,
                                              PikaRotationType   rotate_type);
static void  pika_image_rotate_sample_points (PikaImage         *image,
                                              PikaRotationType   rotate_type);

static void  pika_image_metadata_rotate      (PikaImage         *image,
                                              PikaContext       *context,
                                              GExiv2Orientation  orientation,
                                              PikaProgress      *progress);


/* Public Functions */

void
pika_image_rotate (PikaImage        *image,
                   PikaContext      *context,
                   PikaRotationType  rotate_type,
                   PikaProgress     *progress)
{
  PikaObjectQueue *queue;
  PikaItem        *item;
  GList           *list;
  gdouble          center_x;
  gdouble          center_y;
  gint             new_image_width;
  gint             new_image_height;
  gint             previous_image_width;
  gint             previous_image_height;
  gint             offset_x;
  gint             offset_y;
  gboolean         size_changed;

  g_return_if_fail (PIKA_IS_IMAGE (image));
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress));

  previous_image_width  = pika_image_get_width  (image);
  previous_image_height = pika_image_get_height (image);

  center_x              = previous_image_width  / 2.0;
  center_y              = previous_image_height / 2.0;

  /*  Resize the image (if needed)  */
  switch (rotate_type)
    {
    case PIKA_ROTATE_90:
    case PIKA_ROTATE_270:
      new_image_width  = pika_image_get_height (image);
      new_image_height = pika_image_get_width  (image);
      size_changed     = TRUE;
      offset_x         = (pika_image_get_width  (image) - new_image_width)  / 2;
      offset_y         = (pika_image_get_height (image) - new_image_height) / 2;
      break;

    case PIKA_ROTATE_180:
      new_image_width  = pika_image_get_width  (image);
      new_image_height = pika_image_get_height (image);
      size_changed     = FALSE;
      offset_x         = 0;
      offset_y         = 0;
      break;

    default:
      g_return_if_reached ();
      return;
    }

  pika_set_busy (image->pika);

  queue    = pika_object_queue_new (progress);
  progress = PIKA_PROGRESS (queue);

  pika_object_queue_push_container (queue, pika_image_get_layers (image));
  pika_object_queue_push (queue, pika_image_get_mask (image));
  pika_object_queue_push_container (queue, pika_image_get_channels (image));
  pika_object_queue_push_container (queue, pika_image_get_vectors (image));

  g_object_freeze_notify (G_OBJECT (image));

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ROTATE, NULL);

  /*  Rotate all layers, channels (including selection mask), and vectors  */
  while ((item = pika_object_queue_pop (queue)))
    {
      gint off_x;
      gint off_y;

      pika_item_get_offset (item, &off_x, &off_y);

      pika_item_rotate (item, context, rotate_type, center_x, center_y, FALSE);

      if (PIKA_IS_LAYER (item))
        {
          pika_image_rotate_item_offset (image, rotate_type, item, off_x, off_y);
        }
      else
        {
          pika_item_set_offset (item, 0, 0);

          if (PIKA_IS_VECTORS (item))
            {
              pika_item_set_size (item, new_image_width, new_image_height);

              pika_item_translate (item,
                                   (new_image_width  - pika_image_get_width  (image)) / 2,
                                   (new_image_height - pika_image_get_height (image)) / 2,
                                   FALSE);
            }
        }

      pika_progress_set_value (progress, 1.0);
    }

  /*  Rotate all Guides  */
  pika_image_rotate_guides (image, rotate_type);

  /*  Rotate all sample points  */
  pika_image_rotate_sample_points (image, rotate_type);

  /*  Resize the image (if needed)  */
  if (size_changed)
    {
      gdouble xres;
      gdouble yres;

      pika_image_undo_push_image_size (image,
                                       NULL,
                                       offset_x,
                                       offset_y,
                                       new_image_width,
                                       new_image_height);

      g_object_set (image,
                    "width",  new_image_width,
                    "height", new_image_height,
                    NULL);

      pika_image_get_resolution (image, &xres, &yres);

      if (xres != yres)
        pika_image_set_resolution (image, yres, xres);
    }

  /*  Notify guide movements  */
  for (list = pika_image_get_guides (image);
       list;
       list = g_list_next (list))
    {
      pika_image_guide_moved (image, list->data);
    }

  /*  Notify sample point movements  */
  for (list = pika_image_get_sample_points (image);
       list;
       list = g_list_next (list))
    {
      pika_image_sample_point_moved (image, list->data);
    }

  pika_image_undo_group_end (image);

  g_object_unref (queue);

  if (size_changed)
    pika_image_size_changed_detailed (image,
                                      -offset_x,
                                      -offset_y,
                                      previous_image_width,
                                      previous_image_height);

  g_object_thaw_notify (G_OBJECT (image));

  pika_unset_busy (image->pika);
}

void
pika_image_import_rotation_metadata (PikaImage    *image,
                                     PikaContext  *context,
                                     PikaProgress *progress,
                                     gboolean      interactive)
{
  PikaMetadata *metadata;

  g_return_if_fail (PIKA_IS_IMAGE (image));
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress));

  metadata = pika_image_get_metadata (image);

  if (metadata)
    {
      PikaMetadataRotationPolicy policy;

      policy = PIKA_DIALOG_CONFIG (image->pika->config)->metadata_rotation_policy;
      if (policy == PIKA_METADATA_ROTATION_POLICY_ASK)
        {
          if (interactive)
            {
              gboolean dont_ask = FALSE;

              policy = pika_query_rotation_policy (image->pika, image,
                                                   context, &dont_ask);

              if (dont_ask)
                {
                  g_object_set (G_OBJECT (image->pika->config),
                                "metadata-rotation-policy", policy,
                                NULL);
                }
            }
          else
            {
              policy = PIKA_METADATA_ROTATION_POLICY_ROTATE;
            }
        }

      if (policy == PIKA_METADATA_ROTATION_POLICY_ROTATE)
        pika_image_metadata_rotate (image, context,
                                    gexiv2_metadata_try_get_orientation (GEXIV2_METADATA (metadata), NULL),
                                    progress);

      gexiv2_metadata_try_set_orientation (GEXIV2_METADATA (metadata),
                                           GEXIV2_ORIENTATION_NORMAL,
                                           NULL);
    }
}

void
pika_image_apply_metadata_orientation (PikaImage         *image,
                                       PikaContext       *context,
                                       PikaMetadata      *metadata,
                                       PikaProgress      *progress)
{
  pika_image_metadata_rotate (image, context,
                              gexiv2_metadata_try_get_orientation (GEXIV2_METADATA (metadata), NULL),
                              progress);
}


/* Private Functions */

static void
pika_image_rotate_item_offset (PikaImage        *image,
                               PikaRotationType  rotate_type,
                               PikaItem         *item,
                               gint              off_x,
                               gint              off_y)
{
  gint x = 0;
  gint y = 0;

  switch (rotate_type)
    {
    case PIKA_ROTATE_90:
      x = pika_image_get_height (image) - off_y - pika_item_get_width (item);
      y = off_x;
      break;

    case PIKA_ROTATE_270:
      x = off_y;
      y = pika_image_get_width (image) - off_x - pika_item_get_height (item);
      break;

    case PIKA_ROTATE_180:
      return;

    default:
      g_return_if_reached ();
    }

  pika_item_get_offset (item, &off_x, &off_y);

  x -= off_x;
  y -= off_y;

  if (x || y)
    pika_item_translate (item, x, y, FALSE);
}

static void
pika_image_rotate_guides (PikaImage        *image,
                          PikaRotationType  rotate_type)
{
  GList *list;

  /*  Rotate all Guides  */
  for (list = pika_image_get_guides (image);
       list;
       list = g_list_next (list))
    {
      PikaGuide           *guide       = list->data;
      PikaOrientationType  orientation = pika_guide_get_orientation (guide);
      gint                 position    = pika_guide_get_position (guide);

      switch (rotate_type)
        {
        case PIKA_ROTATE_90:
          switch (orientation)
            {
            case PIKA_ORIENTATION_HORIZONTAL:
              pika_image_undo_push_guide (image, NULL, guide);
              pika_guide_set_orientation (guide, PIKA_ORIENTATION_VERTICAL);
              pika_guide_set_position (guide,
                                       pika_image_get_height (image) - position);
              break;

            case PIKA_ORIENTATION_VERTICAL:
              pika_image_undo_push_guide (image, NULL, guide);
              pika_guide_set_orientation (guide, PIKA_ORIENTATION_HORIZONTAL);
              break;

            default:
              break;
            }
          break;

        case PIKA_ROTATE_180:
          switch (orientation)
            {
            case PIKA_ORIENTATION_HORIZONTAL:
              pika_image_move_guide (image, guide,
                                     pika_image_get_height (image) - position,
                                     TRUE);
              break;

            case PIKA_ORIENTATION_VERTICAL:
              pika_image_move_guide (image, guide,
                                     pika_image_get_width (image) - position,
                                     TRUE);
              break;

            default:
              break;
            }
          break;

        case PIKA_ROTATE_270:
          switch (orientation)
            {
            case PIKA_ORIENTATION_HORIZONTAL:
              pika_image_undo_push_guide (image, NULL, guide);
              pika_guide_set_orientation (guide, PIKA_ORIENTATION_VERTICAL);
              break;

            case PIKA_ORIENTATION_VERTICAL:
              pika_image_undo_push_guide (image, NULL, guide);
              pika_guide_set_orientation (guide, PIKA_ORIENTATION_HORIZONTAL);
              pika_guide_set_position (guide,
                                       pika_image_get_width (image) - position);
              break;

            default:
              break;
            }
          break;
        }
    }
}


static void
pika_image_rotate_sample_points (PikaImage        *image,
                                 PikaRotationType  rotate_type)
{
  GList *list;

  /*  Rotate all sample points  */
  for (list = pika_image_get_sample_points (image);
       list;
       list = g_list_next (list))
    {
      PikaSamplePoint *sample_point = list->data;
      gint             old_x;
      gint             old_y;

      pika_image_undo_push_sample_point (image, NULL, sample_point);

      pika_sample_point_get_position (sample_point, &old_x, &old_y);

      switch (rotate_type)
        {
        case PIKA_ROTATE_90:
          pika_sample_point_set_position (sample_point,
                                          pika_image_get_height (image) - old_y,
                                          old_x);
          break;

        case PIKA_ROTATE_180:
          pika_sample_point_set_position (sample_point,
                                          pika_image_get_width  (image) - old_x,
                                          pika_image_get_height (image) - old_y);
          break;

        case PIKA_ROTATE_270:
          pika_sample_point_set_position (sample_point,
                                          old_y,
                                          pika_image_get_width (image) - old_x);
          break;
        }
    }
}

static void
pika_image_metadata_rotate (PikaImage         *image,
                            PikaContext       *context,
                            GExiv2Orientation  orientation,
                            PikaProgress      *progress)
{
  switch (orientation)
    {
    case GEXIV2_ORIENTATION_UNSPECIFIED:
    case GEXIV2_ORIENTATION_NORMAL:  /* standard orientation, do nothing */
      break;

    case GEXIV2_ORIENTATION_HFLIP:
      pika_image_flip (image, context, PIKA_ORIENTATION_HORIZONTAL, progress);
      break;

    case GEXIV2_ORIENTATION_ROT_180:
      pika_image_rotate (image, context, PIKA_ROTATE_180, progress);
      break;

    case GEXIV2_ORIENTATION_VFLIP:
      pika_image_flip (image, context, PIKA_ORIENTATION_VERTICAL, progress);
      break;

    case GEXIV2_ORIENTATION_ROT_90_HFLIP:  /* flipped diagonally around '\' */
      pika_image_rotate (image, context, PIKA_ROTATE_90, progress);
      pika_image_flip (image, context, PIKA_ORIENTATION_HORIZONTAL, progress);
      break;

    case GEXIV2_ORIENTATION_ROT_90:  /* 90 CW */
      pika_image_rotate (image, context, PIKA_ROTATE_90, progress);
      break;

    case GEXIV2_ORIENTATION_ROT_90_VFLIP:  /* flipped diagonally around '/' */
      pika_image_rotate (image, context, PIKA_ROTATE_90, progress);
      pika_image_flip (image, context, PIKA_ORIENTATION_VERTICAL, progress);
      break;

    case GEXIV2_ORIENTATION_ROT_270:  /* 90 CCW */
      pika_image_rotate (image, context, PIKA_ROTATE_270, progress);
      break;

    default: /* shouldn't happen */
      break;
    }
}
