/* 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
 *
 * pikauiconfigurer.c
 * Copyright (C) 2009 Martin Nordholts <martinn@src.gnome.org>
 *
 * 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 <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"
#include "libpikawidgets/pikawidgets.h"

#include "gui-types.h"

#include "core/pika.h"
#include "core/pikacontext.h"

#include "widgets/pikadialogfactory.h"
#include "widgets/pikadock.h"
#include "widgets/pikadockcolumns.h"
#include "widgets/pikadockcontainer.h"
#include "widgets/pikadockwindow.h"
#include "widgets/pikatoolbox.h"
#include "widgets/pikawidgets-utils.h"

#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikadisplayshell-appearance.h"
#include "display/pikaimagewindow.h"

#include "pikauiconfigurer.h"


enum
{
  PROP_0,
  PROP_PIKA
};


struct _PikaUIConfigurerPrivate
{
  Pika *pika;
};


static void              pika_ui_configurer_set_property                (GObject           *object,
                                                                         guint              property_id,
                                                                         const GValue      *value,
                                                                         GParamSpec        *pspec);
static void              pika_ui_configurer_get_property                (GObject           *object,
                                                                         guint              property_id,
                                                                         GValue            *value,
                                                                         GParamSpec        *pspec);
static void              pika_ui_configurer_move_docks_to_columns       (PikaUIConfigurer  *ui_configurer,
                                                                         PikaImageWindow   *uber_image_window);
static void              pika_ui_configurer_move_shells                 (PikaUIConfigurer  *ui_configurer,
                                                                         PikaImageWindow   *source_image_window,
                                                                         PikaImageWindow   *target_image_window);
static void              pika_ui_configurer_separate_docks              (PikaUIConfigurer  *ui_configurer,
                                                                         PikaImageWindow   *source_image_window);
static void              pika_ui_configurer_move_docks_to_window        (PikaUIConfigurer  *ui_configurer,
                                                                         PikaDockColumns   *dock_columns,
                                                                         PikaAlignmentType  screen_side);
static void              pika_ui_configurer_separate_shells             (PikaUIConfigurer  *ui_configurer,
                                                                         PikaImageWindow   *source_image_window);
static void              pika_ui_configurer_configure_for_single_window (PikaUIConfigurer  *ui_configurer);
static void              pika_ui_configurer_configure_for_multi_window  (PikaUIConfigurer  *ui_configurer);
static PikaImageWindow * pika_ui_configurer_get_uber_window             (PikaUIConfigurer  *ui_configurer);


G_DEFINE_TYPE_WITH_PRIVATE (PikaUIConfigurer, pika_ui_configurer,
                            PIKA_TYPE_OBJECT)

#define parent_class pika_ui_configurer_parent_class


static void
pika_ui_configurer_class_init (PikaUIConfigurerClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->set_property = pika_ui_configurer_set_property;
  object_class->get_property = pika_ui_configurer_get_property;

  g_object_class_install_property (object_class, PROP_PIKA,
                                   g_param_spec_object ("pika", NULL, NULL,
                                                        PIKA_TYPE_PIKA,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));
}

static void
pika_ui_configurer_init (PikaUIConfigurer *ui_configurer)
{
  ui_configurer->p = pika_ui_configurer_get_instance_private (ui_configurer);
}

static void
pika_ui_configurer_set_property (GObject      *object,
                                 guint         property_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
  PikaUIConfigurer *ui_configurer = PIKA_UI_CONFIGURER (object);

  switch (property_id)
    {
    case PROP_PIKA:
      ui_configurer->p->pika = g_value_get_object (value); /* don't ref */
      break;

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

static void
pika_ui_configurer_get_property (GObject    *object,
                                 guint       property_id,
                                 GValue     *value,
                                 GParamSpec *pspec)
{
  PikaUIConfigurer *ui_configurer = PIKA_UI_CONFIGURER (object);

  switch (property_id)
    {
    case PROP_PIKA:
      g_value_set_object (value, ui_configurer->p->pika);
      break;

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


static void
pika_ui_configurer_get_window_center_pos (GtkWindow *window,
                                          gint      *out_x,
                                          gint      *out_y)
{
  gint x, y, w, h;
  gtk_window_get_position (window, &x, &y);
  gtk_window_get_size (window, &w, &h);

  if (out_x)
    *out_x = x + w / 2;
  if (out_y)
    *out_y = y + h / 2;
}

/**
 * pika_ui_configurer_get_relative_window_pos:
 * @window_a:
 * @window_b:
 *
 * Returns: At what side @window_b is relative to @window_a. Either
 * PIKA_ALIGN_LEFT or PIKA_ALIGN_RIGHT.
 **/
static PikaAlignmentType
pika_ui_configurer_get_relative_window_pos (GtkWindow *window_a,
                                            GtkWindow *window_b)
{
  gint a_x, b_x;

  pika_ui_configurer_get_window_center_pos (window_a, &a_x, NULL);
  pika_ui_configurer_get_window_center_pos (window_b, &b_x, NULL);

  return b_x < a_x ? PIKA_ALIGN_LEFT : PIKA_ALIGN_RIGHT;
}

static void
pika_ui_configurer_move_docks_to_columns (PikaUIConfigurer *ui_configurer,
                                          PikaImageWindow  *uber_image_window)
{
  GList *dialogs     = NULL;
  GList *dialog_iter = NULL;

  dialogs =
    g_list_copy (pika_dialog_factory_get_open_dialogs (pika_dialog_factory_get_singleton ()));

  for (dialog_iter = dialogs; dialog_iter; dialog_iter = dialog_iter->next)
    {
      PikaDockWindow    *dock_window;
      PikaDockContainer *dock_container;
      PikaDockColumns   *dock_columns;
      GList             *docks;
      GList             *dock_iter;

      if (!PIKA_IS_DOCK_WINDOW (dialog_iter->data))
        continue;

      dock_window = PIKA_DOCK_WINDOW (dialog_iter->data);

      /* If the dock window is on the left side of the image window,
       * move the docks to the left side. If the dock window is on the
       * right side, move the docks to the right side of the image
       * window.
       */
      if (pika_ui_configurer_get_relative_window_pos (GTK_WINDOW (uber_image_window),
                                                      GTK_WINDOW (dock_window)) == PIKA_ALIGN_LEFT)
        dock_columns = pika_image_window_get_left_docks (uber_image_window);
      else
        dock_columns = pika_image_window_get_right_docks (uber_image_window);

      dock_container = PIKA_DOCK_CONTAINER (dock_window);

      g_object_add_weak_pointer (G_OBJECT (dock_window),
                                 (gpointer) &dock_window);

      docks = pika_dock_container_get_docks (dock_container);
      for (dock_iter = docks; dock_iter; dock_iter = dock_iter->next)
        {
          PikaDock *dock = PIKA_DOCK (dock_iter->data);

          /* Move the dock from the image window to the dock columns
           * widget. Note that we need a ref while the dock is parentless
           */
          g_object_ref (dock);
          pika_dock_window_remove_dock (dock_window, dock);
          pika_dock_columns_add_dock (dock_columns, dock, -1);
          g_object_unref (dock);
        }
      g_list_free (docks);

      if (dock_window)
        g_object_remove_weak_pointer (G_OBJECT (dock_window),
                                      (gpointer) &dock_window);

      /* Kill the window if removing the dock didn't destroy it
       * already. This will be the case for the toolbox dock window
       */
      if (GTK_IS_WIDGET (dock_window))
        {
          guint docks_len;

          docks     = pika_dock_container_get_docks (dock_container);
          docks_len = g_list_length (docks);

          if (docks_len == 0)
            {
              pika_dialog_factory_remove_dialog (pika_dialog_factory_get_singleton (),
                                                 GTK_WIDGET (dock_window));
              gtk_widget_destroy (GTK_WIDGET (dock_window));
            }

          g_list_free (docks);
        }
    }

  g_list_free (dialogs);
}

/**
 * pika_ui_configurer_move_shells:
 * @ui_configurer:
 * @source_image_window:
 * @target_image_window:
 *
 * Move all display shells from one image window to the another.
 **/
static void
pika_ui_configurer_move_shells (PikaUIConfigurer  *ui_configurer,
                                PikaImageWindow   *source_image_window,
                                PikaImageWindow   *target_image_window)
{
  while (pika_image_window_get_n_shells (source_image_window) > 0)
    {
      PikaDisplayShell *shell;

      shell = pika_image_window_get_shell (source_image_window, 0);

      g_object_ref (shell);
      pika_image_window_remove_shell (source_image_window, shell);
      pika_image_window_add_shell (target_image_window, shell);
      g_object_unref (shell);
    }
}

/**
 * pika_ui_configurer_separate_docks:
 * @ui_configurer:
 * @image_window:
 *
 * Move out the docks from the image window.
 **/
static void
pika_ui_configurer_separate_docks (PikaUIConfigurer  *ui_configurer,
                                   PikaImageWindow   *image_window)
{
  PikaDockColumns *left_docks  = NULL;
  PikaDockColumns *right_docks = NULL;

  left_docks  = pika_image_window_get_left_docks (image_window);
  right_docks = pika_image_window_get_right_docks (image_window);

  pika_ui_configurer_move_docks_to_window (ui_configurer, left_docks, PIKA_ALIGN_LEFT);
  pika_ui_configurer_move_docks_to_window (ui_configurer, right_docks, PIKA_ALIGN_RIGHT);
}

/**
 * pika_ui_configurer_move_docks_to_window:
 * @dock_columns:
 * @screen_side: At what side of the screen the dock window should be put.
 *
 * Moves docks in @dock_columns into a new #PikaDockWindow and
 * position it on the screen in a non-overlapping manner.
 */
static void
pika_ui_configurer_move_docks_to_window (PikaUIConfigurer  *ui_configurer,
                                         PikaDockColumns   *dock_columns,
                                         PikaAlignmentType  screen_side)
{
  GdkMonitor    *monitor;
  GdkRectangle   monitor_rect;
  GdkGravity     gravity;
  GList         *docks;
  GList         *iter;
  gboolean       contains_toolbox = FALSE;
  GtkWidget     *dock_window;
  GtkAllocation  original_size;
  gint           x, y;

  docks = g_list_copy (pika_dock_columns_get_docks (dock_columns));
  if (! docks)
    return;

  monitor = pika_widget_get_monitor (GTK_WIDGET (dock_columns));

  gdk_monitor_get_workarea (monitor, &monitor_rect);

  /* Remember the size so we can set the new dock window to the same
   * size
   */
  gtk_widget_get_allocation (GTK_WIDGET (dock_columns), &original_size);

  /* Do we need a toolbox window? */
  for (iter = docks; iter; iter = g_list_next (iter))
    {
      PikaDock *dock = PIKA_DOCK (iter->data);

      if (PIKA_IS_TOOLBOX (dock))
        {
          contains_toolbox = TRUE;
          break;
        }
    }

  /* Create a dock window to put the dock in. Checking for
   * PIKA_IS_TOOLBOX() is kind of ugly but not a disaster. We need
   * the dock window correctly configured if we create it for the
   * toolbox
   */
  dock_window =
    pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (),
                                    monitor,
                                    NULL /*ui_manager*/,
                                    GTK_WIDGET (dock_columns),
                                    (contains_toolbox ?
                                     "pika-toolbox-window" :
                                     "pika-dock-window"),
                                    -1 /*view_size*/,
                                    FALSE /*present*/);

  for (iter = docks; iter; iter = g_list_next (iter))
    {
      PikaDock *dock = PIKA_DOCK (iter->data);

      /* Move the dock to the window */
      g_object_ref (dock);
      pika_dock_columns_remove_dock (dock_columns, dock);
      pika_dock_window_add_dock (PIKA_DOCK_WINDOW (dock_window), dock, -1);
      g_object_unref (dock);
    }

  /* Position the window */
  if (screen_side == PIKA_ALIGN_LEFT)
    {
      gravity = GDK_GRAVITY_NORTH_WEST;

      x = monitor_rect.x;
      y = monitor_rect.y;
    }
  else if (screen_side == PIKA_ALIGN_RIGHT)
    {
      gravity = GDK_GRAVITY_NORTH_EAST;

      x = monitor_rect.x + monitor_rect.width - original_size.width;
      y = monitor_rect.y;
    }
  else
    {
      pika_assert_not_reached ();
    }

  gtk_window_set_gravity (GTK_WINDOW (dock_window), gravity);
  gtk_window_move (GTK_WINDOW (dock_window), x, y);

  /* Try to keep the same size */
  gtk_window_set_default_size (GTK_WINDOW (dock_window),
                               original_size.width,
                               original_size.height);

  /* Don't forget to show the window */
  gtk_widget_show (dock_window);

  g_list_free (docks);
}

/**
 * pika_ui_configurer_separate_shells:
 * @ui_configurer:
 * @source_image_window:
 *
 * Create one image window per display shell and move it there.
 **/
static void
pika_ui_configurer_separate_shells (PikaUIConfigurer *ui_configurer,
                                    PikaImageWindow  *source_image_window)
{
  PikaDisplayShell *active_shell  = pika_image_window_get_active_shell (source_image_window);
  PikaImageWindow  *active_window = NULL;

  /* The last display shell remains in its window */
  while (pika_image_window_get_n_shells (source_image_window) > 1)
    {
      PikaImageWindow  *new_image_window;
      PikaDisplayShell *shell;

      /* Create a new image window */
      new_image_window = pika_image_window_new (ui_configurer->p->pika,
                                                NULL,
                                                pika_dialog_factory_get_singleton (),
                                                pika_widget_get_monitor (GTK_WIDGET (source_image_window)));
      /* Move the shell there */
      shell = pika_image_window_get_shell (source_image_window, 1);

      if (shell == active_shell)
        active_window = new_image_window;

      g_object_ref (shell);
      pika_image_window_remove_shell (source_image_window, shell);
      pika_image_window_add_shell (new_image_window, shell);
      g_object_unref (shell);

      /* FIXME: If we don't set a size request here the window will be
       * too small. Get rid of this hack and fix it the proper way
       */
      gtk_widget_set_size_request (GTK_WIDGET (new_image_window), 640, 480);

      /* Show after we have added the shell */
      gtk_widget_show (GTK_WIDGET (new_image_window));
    }

  /* If none of the shells were active, I assume the first one is. */
  if (active_window == NULL)
    active_window = source_image_window;

  /* The active tab must stay at the top of the windows stack. */
  gtk_window_present (GTK_WINDOW (active_window));
}

/**
 * pika_ui_configurer_configure_for_single_window:
 * @ui_configurer:
 *
 * Move docks and display shells into a single window.
 **/
static void
pika_ui_configurer_configure_for_single_window (PikaUIConfigurer *ui_configurer)
{
  Pika             *pika              = ui_configurer->p->pika;
  GList            *windows           = pika_get_image_windows (pika);
  GList            *iter              = NULL;
  PikaImageWindow  *uber_image_window = NULL;
  PikaDisplay      *active_display    = pika_context_get_display (pika_get_user_context (pika));
  PikaDisplayShell *active_shell      = pika_display_get_shell (active_display);

  /* Get and setup the window to put everything in */
  uber_image_window = pika_ui_configurer_get_uber_window (ui_configurer);

  /* Mve docks to the left and right side of the image window */
  pika_ui_configurer_move_docks_to_columns (ui_configurer,
                                            uber_image_window);

  /* Move image shells from other windows to the uber image window */
  for (iter = windows; iter; iter = g_list_next (iter))
    {
      PikaImageWindow *image_window = PIKA_IMAGE_WINDOW (iter->data);

      /* Don't move stuff to itself */
      if (image_window == uber_image_window)
        continue;

      /* Put the displays in the rest of the image windows into
       * the uber image window
       */
      pika_ui_configurer_move_shells (ui_configurer,
                                      image_window,
                                      uber_image_window);
      /* Destroy the window */
      pika_image_window_destroy (image_window);
    }

  /* Ensure the context shell remains active after mode switch. */
  pika_image_window_set_active_shell (uber_image_window, active_shell);

  g_list_free (windows);
}

/**
 * pika_ui_configurer_configure_for_multi_window:
 * @ui_configurer:
 *
 * Moves all display shells into their own image window.
 **/
static void
pika_ui_configurer_configure_for_multi_window (PikaUIConfigurer *ui_configurer)
{
  Pika  *pika    = ui_configurer->p->pika;
  GList *windows = pika_get_image_windows (pika);
  GList *iter    = NULL;

  for (iter = windows; iter; iter = g_list_next (iter))
    {
      PikaImageWindow *image_window = PIKA_IMAGE_WINDOW (iter->data);

      pika_ui_configurer_separate_docks (ui_configurer, image_window);

      pika_ui_configurer_separate_shells (ui_configurer, image_window);
    }

  g_list_free (windows);
}

/**
 * pika_ui_configurer_get_uber_window:
 * @ui_configurer:
 *
 * Returns: The window to be used as the main window for single-window
 *          mode.
 **/
static PikaImageWindow *
pika_ui_configurer_get_uber_window (PikaUIConfigurer *ui_configurer)
{
  Pika             *pika         = ui_configurer->p->pika;
  PikaDisplay      *display      = pika_get_display_iter (pika)->data;
  PikaDisplayShell *shell        = pika_display_get_shell (display);
  PikaImageWindow  *image_window = pika_display_shell_get_window (shell);

  return image_window;
}

/**
 * pika_ui_configurer_update_appearance:
 * @ui_configurer:
 *
 * Updates the appearance of all shells in all image windows, so they
 * do whatever they deem necessary to fit the new UI mode mode.
 **/
static void
pika_ui_configurer_update_appearance (PikaUIConfigurer *ui_configurer)
{
  Pika  *pika    = ui_configurer->p->pika;
  GList *windows = pika_get_image_windows (pika);
  GList *list;

  for (list = windows; list; list = g_list_next (list))
    {
      PikaImageWindow *image_window = PIKA_IMAGE_WINDOW (list->data);
      gint             n_shells;
      gint             i;

      n_shells = pika_image_window_get_n_shells (image_window);

      for (i = 0; i < n_shells; i++)
        {
          PikaDisplayShell *shell;

          shell = pika_image_window_get_shell (image_window, i);

          pika_display_shell_appearance_update (shell);
        }
    }

  g_list_free (windows);
}

/**
 * pika_ui_configurer_configure:
 * @ui_configurer:
 * @single_window_mode:
 *
 * Configure the UI.
 **/
void
pika_ui_configurer_configure (PikaUIConfigurer *ui_configurer,
                              gboolean          single_window_mode)
{
  if (single_window_mode)
    pika_ui_configurer_configure_for_single_window (ui_configurer);
  else
    pika_ui_configurer_configure_for_multi_window (ui_configurer);

  pika_ui_configurer_update_appearance (ui_configurer);
}
