/* 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) 2009 Martin Nordholts
 *
 * 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 <string.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef G_OS_WIN32
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <io.h>
#endif

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"

#include "widgets/widgets-types.h"

#include "widgets/pikauimanager.h"

#include "core/pika.h"
#include "core/pikachannel.h"
#include "core/pikachannel-select.h"
#include "core/pikadrawable.h"
#include "core/pikagrid.h"
#include "core/pikagrouplayer.h"
#include "core/pikaguide.h"
#include "core/pikaimage.h"
#include "core/pikaimage-grid.h"
#include "core/pikaimage-guides.h"
#include "core/pikaimage-sample-points.h"
#include "core/pikalayer.h"
#include "core/pikalayer-new.h"
#include "core/pikasamplepoint.h"
#include "core/pikaselection.h"

#include "vectors/pikaanchor.h"
#include "vectors/pikabezierstroke.h"
#include "vectors/pikavectors.h"

#include "plug-in/pikapluginmanager-file.h"

#include "file/file-open.h"
#include "file/file-save.h"

#include "tests.h"

#include "pika-app-test-utils.h"


/* we continue to use LEGACY layers for testing, so we can use the
 * same test image for all tests, including loading
 * files/pika-2-6-file.xcf which can't have any non-LEGACY modes
 */

#define PIKA_MAINIMAGE_WIDTH            100
#define PIKA_MAINIMAGE_HEIGHT           90
#define PIKA_MAINIMAGE_TYPE             PIKA_RGB
#define PIKA_MAINIMAGE_PRECISION        PIKA_PRECISION_U8_NON_LINEAR

#define PIKA_MAINIMAGE_LAYER1_NAME      "layer1"
#define PIKA_MAINIMAGE_LAYER1_WIDTH     50
#define PIKA_MAINIMAGE_LAYER1_HEIGHT    51
#define PIKA_MAINIMAGE_LAYER1_FORMAT    babl_format ("R'G'B'A u8")
#define PIKA_MAINIMAGE_LAYER1_OPACITY   PIKA_OPACITY_OPAQUE
#define PIKA_MAINIMAGE_LAYER1_MODE      PIKA_LAYER_MODE_NORMAL_LEGACY

#define PIKA_MAINIMAGE_LAYER2_NAME      "layer2"
#define PIKA_MAINIMAGE_LAYER2_WIDTH     25
#define PIKA_MAINIMAGE_LAYER2_HEIGHT    251
#define PIKA_MAINIMAGE_LAYER2_FORMAT    babl_format ("R'G'B' u8")
#define PIKA_MAINIMAGE_LAYER2_OPACITY   PIKA_OPACITY_TRANSPARENT
#define PIKA_MAINIMAGE_LAYER2_MODE      PIKA_LAYER_MODE_MULTIPLY_LEGACY

#define PIKA_MAINIMAGE_GROUP1_NAME      "group1"

#define PIKA_MAINIMAGE_LAYER3_NAME      "layer3"

#define PIKA_MAINIMAGE_LAYER4_NAME      "layer4"

#define PIKA_MAINIMAGE_GROUP2_NAME      "group2"

#define PIKA_MAINIMAGE_LAYER5_NAME      "layer5"

#define PIKA_MAINIMAGE_VGUIDE1_POS      42
#define PIKA_MAINIMAGE_VGUIDE2_POS      82
#define PIKA_MAINIMAGE_HGUIDE1_POS      3
#define PIKA_MAINIMAGE_HGUIDE2_POS      4

#define PIKA_MAINIMAGE_SAMPLEPOINT1_X   10
#define PIKA_MAINIMAGE_SAMPLEPOINT1_Y   12
#define PIKA_MAINIMAGE_SAMPLEPOINT2_X   41
#define PIKA_MAINIMAGE_SAMPLEPOINT2_Y   49

#define PIKA_MAINIMAGE_RESOLUTIONX      400
#define PIKA_MAINIMAGE_RESOLUTIONY      410

#define PIKA_MAINIMAGE_PARASITE_NAME    "test-parasite"
#define PIKA_MAINIMAGE_PARASITE_DATA    "foo"
#define PIKA_MAINIMAGE_PARASITE_SIZE    4                /* 'f' 'o' 'o' '\0' */

#define PIKA_MAINIMAGE_COMMENT          "Created with code from "\
                                        "app/tests/test-xcf.c in the PIKA "\
                                        "source tree, i.e. it was not created "\
                                        "manually and may thus look weird if "\
                                        "opened and inspected in PIKA."

#define PIKA_MAINIMAGE_UNIT             PIKA_UNIT_PICA

#define PIKA_MAINIMAGE_GRIDXSPACING     25.0
#define PIKA_MAINIMAGE_GRIDYSPACING     27.0

#define PIKA_MAINIMAGE_CHANNEL1_NAME    "channel1"
#define PIKA_MAINIMAGE_CHANNEL1_WIDTH   PIKA_MAINIMAGE_WIDTH
#define PIKA_MAINIMAGE_CHANNEL1_HEIGHT  PIKA_MAINIMAGE_HEIGHT
#define PIKA_MAINIMAGE_CHANNEL1_COLOR   { 1.0, 0.0, 1.0, 1.0 }

#define PIKA_MAINIMAGE_SELECTION_X      5
#define PIKA_MAINIMAGE_SELECTION_Y      6
#define PIKA_MAINIMAGE_SELECTION_W      7
#define PIKA_MAINIMAGE_SELECTION_H      8

#define PIKA_MAINIMAGE_VECTORS1_NAME    "vectors1"
#define PIKA_MAINIMAGE_VECTORS1_COORDS  { { 11.0, 12.0, /* pad zeroes */ },\
                                          { 21.0, 22.0, /* pad zeroes */ },\
                                          { 31.0, 32.0, /* pad zeroes */ }, }

#define PIKA_MAINIMAGE_VECTORS2_NAME    "vectors2"
#define PIKA_MAINIMAGE_VECTORS2_COORDS  { { 911.0, 912.0, /* pad zeroes */ },\
                                          { 921.0, 922.0, /* pad zeroes */ },\
                                          { 931.0, 932.0, /* pad zeroes */ }, }

#define ADD_TEST(function) \
  g_test_add_data_func ("/pika-xcf/" #function, pika, function);


PikaImage        * pika_test_load_image                        (Pika            *pika,
                                                                GFile           *file);
static void        pika_write_and_read_file                    (Pika            *pika,
                                                                gboolean         with_unusual_stuff,
                                                                gboolean         compat_paths,
                                                                gboolean         use_pika_2_8_features);
static PikaImage * pika_create_mainimage                       (Pika            *pika,
                                                                gboolean         with_unusual_stuff,
                                                                gboolean         compat_paths,
                                                                gboolean         use_pika_2_8_features);
static void        pika_assert_mainimage                       (PikaImage       *image,
                                                                gboolean         with_unusual_stuff,
                                                                gboolean         compat_paths,
                                                                gboolean         use_pika_2_8_features);


/**
 * write_and_read_pika_2_6_format:
 * @data:
 *
 * Do a write and read test on a file that could as well be
 * constructed with PIKA 2.6.
 **/
static void
write_and_read_pika_2_6_format (gconstpointer data)
{
  Pika *pika = PIKA (data);

  pika_write_and_read_file (pika,
                            FALSE /*with_unusual_stuff*/,
                            FALSE /*compat_paths*/,
                            FALSE /*use_pika_2_8_features*/);
}

/**
 * write_and_read_pika_2_6_format_unusual:
 * @data:
 *
 * Do a write and read test on a file that could as well be
 * constructed with PIKA 2.6, and make it unusual, like compatible
 * vectors and with a floating selection.
 **/
static void
write_and_read_pika_2_6_format_unusual (gconstpointer data)
{
  Pika *pika = PIKA (data);

  pika_write_and_read_file (pika,
                            TRUE /*with_unusual_stuff*/,
                            TRUE /*compat_paths*/,
                            FALSE /*use_pika_2_8_features*/);
}

/**
 * load_pika_2_6_file:
 * @data:
 *
 * Loads a file created with PIKA 2.6 and makes sure it loaded as
 * expected.
 **/
static void
load_pika_2_6_file (gconstpointer data)
{
  Pika      *pika = PIKA (data);
  PikaImage *image;
  gchar     *filename;
  GFile     *file;

  filename = g_build_filename (g_getenv ("PIKA_TESTING_ABS_TOP_SRCDIR"),
                               "app/tests/files/pika-2-6-file.xcf",
                               NULL);
  file = g_file_new_for_path (filename);
  g_free (filename);

  image = pika_test_load_image (pika, file);

  /* The image file was constructed by running
   * pika_write_and_read_file (FALSE, FALSE) in PIKA 2.6 by
   * copy-pasting the code to PIKA 2.6 and adapting it to changes in
   * the core API, so we can use pika_assert_mainimage() to make sure
   * the file was loaded successfully.
   */
  pika_assert_mainimage (image,
                         FALSE /*with_unusual_stuff*/,
                         FALSE /*compat_paths*/,
                         FALSE /*use_pika_2_8_features*/);
}

/**
 * write_and_read_pika_2_8_format:
 * @data:
 *
 * Writes an XCF file that uses PIKA 2.8 features such as layer
 * groups, then reads the file and make sure no relevant information
 * was lost.
 **/
static void
write_and_read_pika_2_8_format (gconstpointer data)
{
  Pika *pika = PIKA (data);

  pika_write_and_read_file (pika,
                            FALSE /*with_unusual_stuff*/,
                            FALSE /*compat_paths*/,
                            TRUE /*use_pika_2_8_features*/);
}

PikaImage *
pika_test_load_image (Pika  *pika,
                      GFile *file)
{
  PikaPlugInProcedure *proc;
  PikaImage           *image;
  PikaPDBStatusType    unused;

  proc = pika_plug_in_manager_file_procedure_find (pika->plug_in_manager,
                                                   PIKA_FILE_PROCEDURE_GROUP_OPEN,
                                                   file,
                                                   NULL /*error*/);
  image = file_open_image (pika,
                           pika_get_user_context (pika),
                           NULL /*progress*/,
                           file,
                           FALSE /*as_new*/,
                           proc,
                           PIKA_RUN_NONINTERACTIVE,
                           &unused /*status*/,
                           NULL /*mime_type*/,
                           NULL /*error*/);

  return image;
}

/**
 * pika_write_and_read_file:
 *
 * Constructs the main test image and asserts its state, writes it to
 * a file, reads the image from the file, and asserts the state of the
 * loaded file. The function takes various parameters so the same
 * function can be used for different formats.
 **/
static void
pika_write_and_read_file (Pika     *pika,
                          gboolean  with_unusual_stuff,
                          gboolean  compat_paths,
                          gboolean  use_pika_2_8_features)
{
  PikaImage           *image;
  PikaImage           *loaded_image;
  PikaPlugInProcedure *proc;
  gchar               *filename = NULL;
  gint                 file_handle;
  GFile               *file;

  /* Create the image */
  image = pika_create_mainimage (pika,
                                 with_unusual_stuff,
                                 compat_paths,
                                 use_pika_2_8_features);

  /* Assert valid state */
  pika_assert_mainimage (image,
                         with_unusual_stuff,
                         compat_paths,
                         use_pika_2_8_features);

  /* Write to file */
  file_handle = g_file_open_tmp ("pika-test-XXXXXX.xcf", &filename, NULL);
  g_assert (file_handle != -1);
  close (file_handle);
  file = g_file_new_for_path (filename);
  g_free (filename);

  proc = pika_plug_in_manager_file_procedure_find (image->pika->plug_in_manager,
                                                   PIKA_FILE_PROCEDURE_GROUP_SAVE,
                                                   file,
                                                   NULL /*error*/);
  file_save (pika,
             image,
             NULL /*progress*/,
             file,
             proc,
             PIKA_RUN_NONINTERACTIVE,
             FALSE /*change_saved_state*/,
             FALSE /*export_backward*/,
             FALSE /*export_forward*/,
             NULL /*error*/);

  /* Load from file */
  loaded_image = pika_test_load_image (image->pika, file);

  /* Assert on the loaded file. If success, it means that there is no
   * significant information loss when we wrote the image to a file
   * and loaded it again
   */
  pika_assert_mainimage (loaded_image,
                         with_unusual_stuff,
                         compat_paths,
                         use_pika_2_8_features);

  g_file_delete (file, NULL, NULL);
  g_object_unref (file);
}

/**
 * pika_create_mainimage:
 *
 * Creates the main test image, i.e. the image that we use for most of
 * our XCF testing purposes.
 *
 * Returns: The #PikaImage
 **/
static PikaImage *
pika_create_mainimage (Pika     *pika,
                       gboolean  with_unusual_stuff,
                       gboolean  compat_paths,
                       gboolean  use_pika_2_8_features)
{
  PikaImage     *image             = NULL;
  PikaLayer     *layer             = NULL;
  PikaParasite  *parasite          = NULL;
  PikaGrid      *grid              = NULL;
  PikaChannel   *channel           = NULL;
  PikaRGB        channel_color     = PIKA_MAINIMAGE_CHANNEL1_COLOR;
  PikaChannel   *selection         = NULL;
  PikaVectors   *vectors           = NULL;
  PikaCoords     vectors1_coords[] = PIKA_MAINIMAGE_VECTORS1_COORDS;
  PikaCoords     vectors2_coords[] = PIKA_MAINIMAGE_VECTORS2_COORDS;
  PikaStroke    *stroke            = NULL;
  PikaLayerMask *layer_mask        = NULL;

  /* Image size and type */
  image = pika_image_new (pika,
                          PIKA_MAINIMAGE_WIDTH,
                          PIKA_MAINIMAGE_HEIGHT,
                          PIKA_MAINIMAGE_TYPE,
                          PIKA_MAINIMAGE_PRECISION);

  /* Layers */
  layer = pika_layer_new (image,
                          PIKA_MAINIMAGE_LAYER1_WIDTH,
                          PIKA_MAINIMAGE_LAYER1_HEIGHT,
                          PIKA_MAINIMAGE_LAYER1_FORMAT,
                          PIKA_MAINIMAGE_LAYER1_NAME,
                          PIKA_MAINIMAGE_LAYER1_OPACITY,
                          PIKA_MAINIMAGE_LAYER1_MODE);
  pika_image_add_layer (image,
                        layer,
                        NULL,
                        0,
                        FALSE/*push_undo*/);
  layer = pika_layer_new (image,
                          PIKA_MAINIMAGE_LAYER2_WIDTH,
                          PIKA_MAINIMAGE_LAYER2_HEIGHT,
                          PIKA_MAINIMAGE_LAYER2_FORMAT,
                          PIKA_MAINIMAGE_LAYER2_NAME,
                          PIKA_MAINIMAGE_LAYER2_OPACITY,
                          PIKA_MAINIMAGE_LAYER2_MODE);
  pika_image_add_layer (image,
                        layer,
                        NULL,
                        0,
                        FALSE /*push_undo*/);

  /* Layer mask */
  layer_mask = pika_layer_create_mask (layer,
                                       PIKA_ADD_MASK_BLACK,
                                       NULL /*channel*/);
  pika_layer_add_mask (layer,
                       layer_mask,
                       FALSE /*push_undo*/,
                       NULL /*error*/);

  /* Image compression type
   *
   * We don't do any explicit test, only implicit when we read tile
   * data in other tests
   */

  /* Guides, note we add them in reversed order */
  pika_image_add_hguide (image,
                         PIKA_MAINIMAGE_HGUIDE2_POS,
                         FALSE /*push_undo*/);
  pika_image_add_hguide (image,
                         PIKA_MAINIMAGE_HGUIDE1_POS,
                         FALSE /*push_undo*/);
  pika_image_add_vguide (image,
                         PIKA_MAINIMAGE_VGUIDE2_POS,
                         FALSE /*push_undo*/);
  pika_image_add_vguide (image,
                         PIKA_MAINIMAGE_VGUIDE1_POS,
                         FALSE /*push_undo*/);


  /* Sample points */
  pika_image_add_sample_point_at_pos (image,
                                      PIKA_MAINIMAGE_SAMPLEPOINT1_X,
                                      PIKA_MAINIMAGE_SAMPLEPOINT1_Y,
                                      FALSE /*push_undo*/);
  pika_image_add_sample_point_at_pos (image,
                                      PIKA_MAINIMAGE_SAMPLEPOINT2_X,
                                      PIKA_MAINIMAGE_SAMPLEPOINT2_Y,
                                      FALSE /*push_undo*/);

  /* Tattoo
   * We don't bother testing this, not yet at least
   */

  /* Resolution */
  pika_image_set_resolution (image,
                             PIKA_MAINIMAGE_RESOLUTIONX,
                             PIKA_MAINIMAGE_RESOLUTIONY);


  /* Parasites */
  parasite = pika_parasite_new (PIKA_MAINIMAGE_PARASITE_NAME,
                                PIKA_PARASITE_PERSISTENT,
                                PIKA_MAINIMAGE_PARASITE_SIZE,
                                PIKA_MAINIMAGE_PARASITE_DATA);
  pika_image_parasite_attach (image,
                              parasite, FALSE);
  pika_parasite_free (parasite);
  parasite = pika_parasite_new ("pika-comment",
                                PIKA_PARASITE_PERSISTENT,
                                strlen (PIKA_MAINIMAGE_COMMENT) + 1,
                                PIKA_MAINIMAGE_COMMENT);
  pika_image_parasite_attach (image, parasite, FALSE);
  pika_parasite_free (parasite);


  /* Unit */
  pika_image_set_unit (image,
                       PIKA_MAINIMAGE_UNIT);

  /* Grid */
  grid = g_object_new (PIKA_TYPE_GRID,
                       "xspacing", PIKA_MAINIMAGE_GRIDXSPACING,
                       "yspacing", PIKA_MAINIMAGE_GRIDYSPACING,
                       NULL);
  pika_image_set_grid (image,
                       grid,
                       FALSE /*push_undo*/);
  g_object_unref (grid);

  /* Channel */
  channel = pika_channel_new (image,
                              PIKA_MAINIMAGE_CHANNEL1_WIDTH,
                              PIKA_MAINIMAGE_CHANNEL1_HEIGHT,
                              PIKA_MAINIMAGE_CHANNEL1_NAME,
                              &channel_color);
  pika_image_add_channel (image,
                          channel,
                          NULL,
                          -1,
                          FALSE /*push_undo*/);

  /* Selection */
  selection = pika_image_get_mask (image);
  pika_channel_select_rectangle (selection,
                                 PIKA_MAINIMAGE_SELECTION_X,
                                 PIKA_MAINIMAGE_SELECTION_Y,
                                 PIKA_MAINIMAGE_SELECTION_W,
                                 PIKA_MAINIMAGE_SELECTION_H,
                                 PIKA_CHANNEL_OP_REPLACE,
                                 FALSE /*feather*/,
                                 0.0 /*feather_radius_x*/,
                                 0.0 /*feather_radius_y*/,
                                 FALSE /*push_undo*/);

  /* Vectors 1 */
  vectors = pika_vectors_new (image,
                              PIKA_MAINIMAGE_VECTORS1_NAME);
  /* The XCF file can save vectors in two kind of ways, one old way
   * and a new way. Parameterize the way so we can test both variants,
   * i.e. pika_vectors_compat_is_compatible() must return both TRUE
   * and FALSE.
   */
  if (! compat_paths)
    {
      pika_item_set_visible (PIKA_ITEM (vectors),
                             TRUE,
                             FALSE /*push_undo*/);
    }
  /* TODO: Add test for non-closed stroke. The order of the anchor
   * points changes for open strokes, so it's boring to test
   */
  stroke = pika_bezier_stroke_new_from_coords (vectors1_coords,
                                               G_N_ELEMENTS (vectors1_coords),
                                               TRUE /*closed*/);
  pika_vectors_stroke_add (vectors, stroke);
  pika_image_add_vectors (image,
                          vectors,
                          NULL /*parent*/,
                          -1 /*position*/,
                          FALSE /*push_undo*/);

  /* Vectors 2 */
  vectors = pika_vectors_new (image,
                              PIKA_MAINIMAGE_VECTORS2_NAME);

  stroke = pika_bezier_stroke_new_from_coords (vectors2_coords,
                                               G_N_ELEMENTS (vectors2_coords),
                                               TRUE /*closed*/);
  pika_vectors_stroke_add (vectors, stroke);
  pika_image_add_vectors (image,
                          vectors,
                          NULL /*parent*/,
                          -1 /*position*/,
                          FALSE /*push_undo*/);

  /* Some of these things are pretty unusual, parameterize the
   * inclusion of this in the written file so we can do our test both
   * with and without
   */
  if (with_unusual_stuff)
    {
      GList *drawables;

      drawables = pika_image_get_selected_drawables (image);

      /* Floating selection */
      pika_selection_float (PIKA_SELECTION (pika_image_get_mask (image)),
                            drawables,
                            pika_get_user_context (pika),
                            TRUE /*cut_image*/,
                            0 /*off_x*/,
                            0 /*off_y*/,
                            NULL /*error*/);
      g_list_free (drawables);
    }

  /* Adds stuff like layer groups */
  if (use_pika_2_8_features)
    {
      PikaLayer *parent;

      /* Add a layer group and some layers:
       *
       *  group1
       *    layer3
       *    layer4
       *    group2
       *      layer5
       */

      /* group1 */
      layer = pika_group_layer_new (image);
      pika_object_set_name (PIKA_OBJECT (layer), PIKA_MAINIMAGE_GROUP1_NAME);
      pika_image_add_layer (image,
                            layer,
                            NULL /*parent*/,
                            -1 /*position*/,
                            FALSE /*push_undo*/);
      parent = layer;

      /* layer3 */
      layer = pika_layer_new (image,
                              PIKA_MAINIMAGE_LAYER1_WIDTH,
                              PIKA_MAINIMAGE_LAYER1_HEIGHT,
                              PIKA_MAINIMAGE_LAYER1_FORMAT,
                              PIKA_MAINIMAGE_LAYER3_NAME,
                              PIKA_MAINIMAGE_LAYER1_OPACITY,
                              PIKA_MAINIMAGE_LAYER1_MODE);
      pika_image_add_layer (image,
                            layer,
                            parent,
                            -1 /*position*/,
                            FALSE /*push_undo*/);

      /* layer4 */
      layer = pika_layer_new (image,
                              PIKA_MAINIMAGE_LAYER1_WIDTH,
                              PIKA_MAINIMAGE_LAYER1_HEIGHT,
                              PIKA_MAINIMAGE_LAYER1_FORMAT,
                              PIKA_MAINIMAGE_LAYER4_NAME,
                              PIKA_MAINIMAGE_LAYER1_OPACITY,
                              PIKA_MAINIMAGE_LAYER1_MODE);
      pika_image_add_layer (image,
                            layer,
                            parent,
                            -1 /*position*/,
                            FALSE /*push_undo*/);

      /* group2 */
      layer = pika_group_layer_new (image);
      pika_object_set_name (PIKA_OBJECT (layer), PIKA_MAINIMAGE_GROUP2_NAME);
      pika_image_add_layer (image,
                            layer,
                            parent,
                            -1 /*position*/,
                            FALSE /*push_undo*/);
      parent = layer;

      /* layer5 */
      layer = pika_layer_new (image,
                              PIKA_MAINIMAGE_LAYER1_WIDTH,
                              PIKA_MAINIMAGE_LAYER1_HEIGHT,
                              PIKA_MAINIMAGE_LAYER1_FORMAT,
                              PIKA_MAINIMAGE_LAYER5_NAME,
                              PIKA_MAINIMAGE_LAYER1_OPACITY,
                              PIKA_MAINIMAGE_LAYER1_MODE);
      pika_image_add_layer (image,
                            layer,
                            parent,
                            -1 /*position*/,
                            FALSE /*push_undo*/);
    }

  /* Todo, should be tested somehow:
   *
   * - Color maps
   * - Custom user units
   * - Text layers
   * - Layer parasites
   * - Channel parasites
   * - Different tile compression methods
   */

  return image;
}

static void
pika_assert_vectors (PikaImage   *image,
                     const gchar *name,
                     PikaCoords   coords[],
                     gsize        coords_size,
                     gboolean     visible)
{
  PikaVectors *vectors        = NULL;
  PikaStroke  *stroke         = NULL;
  GArray      *control_points = NULL;
  gboolean     closed         = FALSE;
  gint         i              = 0;

  vectors = pika_image_get_vectors_by_name (image, name);
  stroke = pika_vectors_stroke_get_next (vectors, NULL);
  g_assert (stroke != NULL);
  control_points = pika_stroke_control_points_get (stroke,
                                                   &closed);
  g_assert (closed);
  g_assert_cmpint (control_points->len,
                   ==,
                   coords_size);
  for (i = 0; i < control_points->len; i++)
    {
      g_assert_cmpint (coords[i].x,
                       ==,
                       g_array_index (control_points,
                                      PikaAnchor,
                                      i).position.x);
      g_assert_cmpint (coords[i].y,
                       ==,
                       g_array_index (control_points,
                                      PikaAnchor,
                                      i).position.y);
    }

  g_assert (pika_item_get_visible (PIKA_ITEM (vectors)) ? TRUE : FALSE ==
            visible ? TRUE : FALSE);
}

/**
 * pika_assert_mainimage:
 * @image:
 *
 * Verifies that the passed #PikaImage contains all the information
 * that was put in it by pika_create_mainimage().
 **/
static void
pika_assert_mainimage (PikaImage *image,
                       gboolean   with_unusual_stuff,
                       gboolean   compat_paths,
                       gboolean   use_pika_2_8_features)
{
  const PikaParasite *parasite               = NULL;
  gchar              *parasite_data          = NULL;
  guint32             parasite_size          = -1;
  PikaLayer          *layer                  = NULL;
  GList              *iter                   = NULL;
  PikaGuide          *guide                  = NULL;
  PikaSamplePoint    *sample_point           = NULL;
  gint                sample_point_x         = 0;
  gint                sample_point_y         = 0;
  gdouble             xres                   = 0.0;
  gdouble             yres                   = 0.0;
  PikaGrid           *grid                   = NULL;
  gdouble             xspacing               = 0.0;
  gdouble             yspacing               = 0.0;
  PikaChannel        *channel                = NULL;
  PikaRGB             expected_channel_color = PIKA_MAINIMAGE_CHANNEL1_COLOR;
  PikaRGB             actual_channel_color   = { 0, };
  PikaChannel        *selection              = NULL;
  gint                x                      = -1;
  gint                y                      = -1;
  gint                w                      = -1;
  gint                h                      = -1;
  PikaCoords          vectors1_coords[]      = PIKA_MAINIMAGE_VECTORS1_COORDS;
  PikaCoords          vectors2_coords[]      = PIKA_MAINIMAGE_VECTORS2_COORDS;

  /* Image size and type */
  g_assert_cmpint (pika_image_get_width (image),
                   ==,
                   PIKA_MAINIMAGE_WIDTH);
  g_assert_cmpint (pika_image_get_height (image),
                   ==,
                   PIKA_MAINIMAGE_HEIGHT);
  g_assert_cmpint (pika_image_get_base_type (image),
                   ==,
                   PIKA_MAINIMAGE_TYPE);

  /* Layers */
  layer = pika_image_get_layer_by_name (image,
                                        PIKA_MAINIMAGE_LAYER1_NAME);
  g_assert_cmpint (pika_item_get_width (PIKA_ITEM (layer)),
                   ==,
                   PIKA_MAINIMAGE_LAYER1_WIDTH);
  g_assert_cmpint (pika_item_get_height (PIKA_ITEM (layer)),
                   ==,
                   PIKA_MAINIMAGE_LAYER1_HEIGHT);
  g_assert_cmpstr (babl_get_name (pika_drawable_get_format (PIKA_DRAWABLE (layer))),
                   ==,
                   babl_get_name (PIKA_MAINIMAGE_LAYER1_FORMAT));
  g_assert_cmpstr (pika_object_get_name (PIKA_DRAWABLE (layer)),
                   ==,
                   PIKA_MAINIMAGE_LAYER1_NAME);
  g_assert_cmpfloat (pika_layer_get_opacity (layer),
                     ==,
                     PIKA_MAINIMAGE_LAYER1_OPACITY);
  g_assert_cmpint (pika_layer_get_mode (layer),
                   ==,
                   PIKA_MAINIMAGE_LAYER1_MODE);
  layer = pika_image_get_layer_by_name (image,
                                        PIKA_MAINIMAGE_LAYER2_NAME);
  g_assert_cmpint (pika_item_get_width (PIKA_ITEM (layer)),
                   ==,
                   PIKA_MAINIMAGE_LAYER2_WIDTH);
  g_assert_cmpint (pika_item_get_height (PIKA_ITEM (layer)),
                   ==,
                   PIKA_MAINIMAGE_LAYER2_HEIGHT);
  g_assert_cmpstr (babl_get_name (pika_drawable_get_format (PIKA_DRAWABLE (layer))),
                   ==,
                   babl_get_name (PIKA_MAINIMAGE_LAYER2_FORMAT));
  g_assert_cmpstr (pika_object_get_name (PIKA_DRAWABLE (layer)),
                   ==,
                   PIKA_MAINIMAGE_LAYER2_NAME);
  g_assert_cmpfloat (pika_layer_get_opacity (layer),
                     ==,
                     PIKA_MAINIMAGE_LAYER2_OPACITY);
  g_assert_cmpint (pika_layer_get_mode (layer),
                   ==,
                   PIKA_MAINIMAGE_LAYER2_MODE);

  /* Guides, note that we rely on internal ordering */
  iter = pika_image_get_guides (image);
  g_assert (iter != NULL);
  guide = iter->data;
  g_assert_cmpint (pika_guide_get_position (guide),
                   ==,
                   PIKA_MAINIMAGE_VGUIDE1_POS);
  iter = g_list_next (iter);
  g_assert (iter != NULL);
  guide = iter->data;
  g_assert_cmpint (pika_guide_get_position (guide),
                   ==,
                   PIKA_MAINIMAGE_VGUIDE2_POS);
  iter = g_list_next (iter);
  g_assert (iter != NULL);
  guide = iter->data;
  g_assert_cmpint (pika_guide_get_position (guide),
                   ==,
                   PIKA_MAINIMAGE_HGUIDE1_POS);
  iter = g_list_next (iter);
  g_assert (iter != NULL);
  guide = iter->data;
  g_assert_cmpint (pika_guide_get_position (guide),
                   ==,
                   PIKA_MAINIMAGE_HGUIDE2_POS);
  iter = g_list_next (iter);
  g_assert (iter == NULL);

  /* Sample points, we rely on the same ordering as when we added
   * them, although this ordering is not a necessity
   */
  iter = pika_image_get_sample_points (image);
  g_assert (iter != NULL);
  sample_point = iter->data;
  pika_sample_point_get_position (sample_point,
                                  &sample_point_x, &sample_point_y);
  g_assert_cmpint (sample_point_x,
                   ==,
                   PIKA_MAINIMAGE_SAMPLEPOINT1_X);
  g_assert_cmpint (sample_point_y,
                   ==,
                   PIKA_MAINIMAGE_SAMPLEPOINT1_Y);
  iter = g_list_next (iter);
  g_assert (iter != NULL);
  sample_point = iter->data;
  pika_sample_point_get_position (sample_point,
                                  &sample_point_x, &sample_point_y);
  g_assert_cmpint (sample_point_x,
                   ==,
                   PIKA_MAINIMAGE_SAMPLEPOINT2_X);
  g_assert_cmpint (sample_point_y,
                   ==,
                   PIKA_MAINIMAGE_SAMPLEPOINT2_Y);
  iter = g_list_next (iter);
  g_assert (iter == NULL);

  /* Resolution */
  pika_image_get_resolution (image, &xres, &yres);
  g_assert_cmpint (xres,
                   ==,
                   PIKA_MAINIMAGE_RESOLUTIONX);
  g_assert_cmpint (yres,
                   ==,
                   PIKA_MAINIMAGE_RESOLUTIONY);

  /* Parasites */
  parasite = pika_image_parasite_find (image,
                                       PIKA_MAINIMAGE_PARASITE_NAME);
  parasite_data = (gchar *) pika_parasite_get_data (parasite, &parasite_size);
  parasite_data = g_strndup (parasite_data, parasite_size);
  g_assert_cmpint (parasite_size,
                   ==,
                   PIKA_MAINIMAGE_PARASITE_SIZE);
  g_assert_cmpstr (parasite_data,
                   ==,
                   PIKA_MAINIMAGE_PARASITE_DATA);
  g_free (parasite_data);

  parasite = pika_image_parasite_find (image,
                                       "pika-comment");
  parasite_data = (gchar *) pika_parasite_get_data (parasite, &parasite_size);
  parasite_data = g_strndup (parasite_data, parasite_size);
  g_assert_cmpint (parasite_size,
                   ==,
                   strlen (PIKA_MAINIMAGE_COMMENT) + 1);
  g_assert_cmpstr (parasite_data,
                   ==,
                   PIKA_MAINIMAGE_COMMENT);
  g_free (parasite_data);

  /* Unit */
  g_assert_cmpint (pika_image_get_unit (image),
                   ==,
                   PIKA_MAINIMAGE_UNIT);

  /* Grid */
  grid = pika_image_get_grid (image);
  g_object_get (grid,
                "xspacing", &xspacing,
                "yspacing", &yspacing,
                NULL);
  g_assert_cmpint (xspacing,
                   ==,
                   PIKA_MAINIMAGE_GRIDXSPACING);
  g_assert_cmpint (yspacing,
                   ==,
                   PIKA_MAINIMAGE_GRIDYSPACING);


  /* Channel */
  channel = pika_image_get_channel_by_name (image,
                                            PIKA_MAINIMAGE_CHANNEL1_NAME);
  pika_channel_get_color (channel, &actual_channel_color);
  g_assert_cmpint (pika_item_get_width (PIKA_ITEM (channel)),
                   ==,
                   PIKA_MAINIMAGE_CHANNEL1_WIDTH);
  g_assert_cmpint (pika_item_get_height (PIKA_ITEM (channel)),
                   ==,
                   PIKA_MAINIMAGE_CHANNEL1_HEIGHT);
  g_assert (memcmp (&expected_channel_color,
                    &actual_channel_color,
                    sizeof (PikaRGB)) == 0);

  /* Selection, if the image contains unusual stuff it contains a
   * floating select, and when floating a selection, the selection
   * mask is cleared, so don't test for the presence of the selection
   * mask in that case
   */
  if (! with_unusual_stuff)
    {
      selection = pika_image_get_mask (image);
      pika_item_bounds (PIKA_ITEM (selection), &x, &y, &w, &h);
      g_assert_cmpint (x,
                       ==,
                       PIKA_MAINIMAGE_SELECTION_X);
      g_assert_cmpint (y,
                       ==,
                       PIKA_MAINIMAGE_SELECTION_Y);
      g_assert_cmpint (w,
                       ==,
                       PIKA_MAINIMAGE_SELECTION_W);
      g_assert_cmpint (h,
                       ==,
                       PIKA_MAINIMAGE_SELECTION_H);
    }

  /* Vectors 1 */
  pika_assert_vectors (image,
                       PIKA_MAINIMAGE_VECTORS1_NAME,
                       vectors1_coords,
                       G_N_ELEMENTS (vectors1_coords),
                       ! compat_paths /*visible*/);

  /* Vectors 2 (always visible FALSE) */
  pika_assert_vectors (image,
                       PIKA_MAINIMAGE_VECTORS2_NAME,
                       vectors2_coords,
                       G_N_ELEMENTS (vectors2_coords),
                       FALSE /*visible*/);

  if (with_unusual_stuff)
    g_assert (pika_image_get_floating_selection (image) != NULL);
  else /* if (! with_unusual_stuff) */
    g_assert (pika_image_get_floating_selection (image) == NULL);

  if (use_pika_2_8_features)
    {
      /* Only verify the parent relationships, the layer attributes
       * are tested above
       */
      PikaItem *group1 = PIKA_ITEM (pika_image_get_layer_by_name (image, PIKA_MAINIMAGE_GROUP1_NAME));
      PikaItem *layer3 = PIKA_ITEM (pika_image_get_layer_by_name (image, PIKA_MAINIMAGE_LAYER3_NAME));
      PikaItem *layer4 = PIKA_ITEM (pika_image_get_layer_by_name (image, PIKA_MAINIMAGE_LAYER4_NAME));
      PikaItem *group2 = PIKA_ITEM (pika_image_get_layer_by_name (image, PIKA_MAINIMAGE_GROUP2_NAME));
      PikaItem *layer5 = PIKA_ITEM (pika_image_get_layer_by_name (image, PIKA_MAINIMAGE_LAYER5_NAME));

      g_assert (pika_item_get_parent (group1) == NULL);
      g_assert (pika_item_get_parent (layer3) == group1);
      g_assert (pika_item_get_parent (layer4) == group1);
      g_assert (pika_item_get_parent (group2) == group1);
      g_assert (pika_item_get_parent (layer5) == group2);
    }
}


/**
 * main:
 * @argc:
 * @argv:
 *
 * These tests intend to
 *
 *  - Make sure that we are backwards compatible with files created by
 *    older version of PIKA, i.e. that we can load files from earlier
 *    version of PIKA
 *
 *  - Make sure that the information put into a #PikaImage is not lost
 *    when the #PikaImage is written to a file and then read again
 **/
int
main (int    argc,
      char **argv)
{
  Pika *pika;
  int   result;

  g_test_init (&argc, &argv, NULL);

  pika_test_utils_set_pika3_directory ("PIKA_TESTING_ABS_TOP_SRCDIR",
                                       "app/tests/pikadir");

  /* We share the same application instance across all tests. We need
   * the GUI variant for the file procs
   */
  pika = pika_init_for_testing ();

  /* Add tests */
  ADD_TEST (write_and_read_pika_2_6_format);
  ADD_TEST (write_and_read_pika_2_6_format_unusual);
  ADD_TEST (load_pika_2_6_file);
  ADD_TEST (write_and_read_pika_2_8_format);

  /* Don't write files to the source dir */
  pika_test_utils_set_pika3_directory ("PIKA_TESTING_ABS_TOP_BUILDDIR",
                                       "app/tests/pikadir-output");

  /* Run the tests */
  result = g_test_run ();

  /* Exit so we don't break script-fu plug-in wire */
  pika_exit (pika, TRUE);

  return result;
}
