/*
 *  Copyright 1994-2022 Olivier Girondel
 *  Copyright 2019-2022 Laurent Marsac
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou 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 lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include "globals.h"
#include "images.h"
#include "colormaps.h"

#define SEQ_VERSION_MIN 0
#define SEQ_VERSION_MAX 4

gchar *sequences_dir = NULL; // Sequences directory


static void camel_case_params3d(json_t *);


const gchar *
Sequences_get_dir(void)
{
  if (NULL == sequences_dir) {
    sequences_dir = g_strdup_printf("%s/%s", g_get_home_dir(), SEQUENCES_DIR);
    xdebug("[i] Setting default sequences directory: '%s'\n", sequences_dir);
  } else {
    xdebug("[i] Using sequences directory: '%s'\n", sequences_dir);
  }

  return sequences_dir;
}


Sequence_t *
Sequence_from_json(const json_t *parsed_json)
{
  Sequence_t *s = NULL;
  json_t *j_plugins;
  json_t *j_sequence_version = json_object_get(parsed_json, "version");
  assert(NULL != j_sequence_version);
  int sequence_version = json_integer_value(j_sequence_version);

  if ((sequence_version < SEQ_VERSION_MIN) || (sequence_version > SEQ_VERSION_MAX)) {
    VERBOSE(printf("[!] Sequence version '%d' not supported\n", sequence_version));
    goto error;
  }

  json_t *j_id = json_object_get(parsed_json, "id");
  assert(NULL != j_id);
  if (sequence_version < 4) {
    // timestamps where in seconds
    s = Sequence_new(json_integer_value(j_id) * 1000);
  } else {
    // now they are in milliseconds
    s = Sequence_new(json_integer_value(j_id));
  }

  /* get auto_colormaps */
  json_t *j_auto_colormaps = NULL;
  if (sequence_version < 2) {
    j_auto_colormaps = json_object_get(parsed_json, "auto_colormaps");
  } else {
    j_auto_colormaps = json_object_get(parsed_json, "autoColormaps");
  }
  if (NULL != j_auto_colormaps) {
    s->auto_colormaps = json_is_boolean(j_auto_colormaps) ?
                        json_boolean_value(j_auto_colormaps) : json_integer_value(j_auto_colormaps);
  } else {
    s->auto_colormaps = 0;
    goto bare_sequence;
  }
  assert((s->auto_colormaps == 0) || (s->auto_colormaps == 1));
  xdebug("[i] Random colormaps: %s\n", (s->auto_colormaps ? "on" : "off"));

  /* get colormap name */
  json_t *j_cmap = json_object_get(parsed_json, "colormap");
  assert(NULL != j_cmap);
  const char *cmap = json_string_value(j_cmap);
  xdebug("[i] Need colormap: '%s'\n", cmap);
  s->cmap_id = Colormaps_find(cmap);

  /* get auto_images */
  json_t *j_auto_images = NULL;
  if (sequence_version < 2) {
    j_auto_images = json_object_get(parsed_json, "auto_images");
  } else {
    j_auto_images = json_object_get(parsed_json, "autoImages");
  }
  if (NULL != j_auto_images) {
    s->auto_images = json_is_boolean(j_auto_images) ?
                     json_boolean_value(j_auto_images) : json_integer_value(j_auto_colormaps);
  } else {
    s->auto_images = 0;
  }
  assert((s->auto_images == 0) || (s->auto_images == 1));
  xdebug("[i] Random images: %s\n", (s->auto_images ? "on" : "off"));

  /* get image name */
  json_t *j_image = json_object_get(parsed_json, "image");
  assert(NULL != j_image);
  const char *image = json_string_value(j_image);
  xdebug("[i] Need image: '%s'\n", image);
  if (NULL == images) {
    VERBOSE(printf("[!] No images are loaded, won't find '%s'\n", image));
    goto error;
  } else {
    s->image_id = Images_find(image);
  }

bare_sequence:
  /* get plugins */
  j_plugins = json_object_get(parsed_json, "plugins");
  assert(NULL != j_plugins);
  unsigned n_plugins = json_array_size(j_plugins);

  for (uint16_t n = 0; n < n_plugins; n++) {
    json_t *j_p = json_array_get(j_plugins, n);

    json_t *j_p_name = json_object_get(j_p, "name");
    assert(NULL != j_p_name);

    json_t *j_p_version = json_object_get(j_p, "version");
    assert(NULL != j_p_version);
    uint32_t version = json_integer_value(j_p_version);

    Plugin_t *p = Plugins_find(json_string_value(j_p_name));
    if (NULL == p) {
      VERBOSE(printf("[!] Could not find plugin %s\n", json_string_value(j_p_name)));
      goto error;
    }

    if (p->version != version) {
      if ((NULL != p->check_version) && p->check_version(version) == 0) {
        VERBOSE(printf("[!] Plugin %s version is %d but trying to load from incompatible version %d\n",
                       p->name, p->version, version));
        goto error;
      }
    }

    json_t *j_p_mode = json_object_get(j_p, "mode");
    assert(NULL != j_p_mode);
    const char *mode_str = json_string_value(j_p_mode);

    Layer_t *layer = Layer_new(p);
    layer->mode    = LayerMode_from_string(mode_str);

    json_t *j_p_lens = json_object_get(j_p, "lens");
    assert(NULL != j_p_lens);
    if (json_is_boolean(j_p_lens) && (json_is_true(j_p_lens))) {
      s->lens = p;
    }
    if (json_is_integer(j_p_lens) && (json_integer_value(j_p_lens))) {
      s->lens = p;
    }

    json_t *parameters = json_copy(json_object_get(j_p, "parameters"));
    if ((NULL != parameters) && (NULL != p->parameters)) {
#ifdef DEBUG_SEQUENCE_V0
      DEBUG_JSON("old params", parameters);
#endif
      json_t *new_params = p->parameters(NULL, NULL, 1); // fetch all parameters
      assert(NULL != new_params);
#ifdef DEBUG_SEQUENCE_V0
      DEBUG_JSON("all params", new_params);
#endif
      const char *key;
      json_t *value;

      json_object_foreach(new_params, key, value) {
        const char *key2;
        json_t *value2;

        json_object_foreach(parameters, key2, value2) {
          if (is_equal(key, key2)) {
            json_t *new_value = json_object_get(value2, "value");
            assert(NULL != new_value);
            // Fix for v1 API boolean parameters which were stored as 0/1 instead of false/true
            if (is_equal(json_string_value(json_object_get(value, "type")), "boolean") && !sequence_version) {
#ifdef DEBUG_SEQUENCE_V0
              xdebug("%s: %s is a boolean parameter, sequence version %d\n", __func__, key, sequence_version);
              DEBUG_JSON("value2", value2);
              assert(json_is_integer(new_value) || json_is_boolean(new_value));
              xdebug("new_value: integer: %d, boolean: %d, %s value: %d\n",
                     json_is_integer(new_value), json_is_boolean(new_value),
                     json_is_integer(new_value) ? "integer" : "boolean",
                     json_is_integer(new_value) ? json_integer_value(new_value) : json_boolean_value(new_value));
#endif
              int int_new_value = json_integer_value(new_value);
              json_decref(new_value);
              new_value = json_boolean(int_new_value);
#ifdef DEBUG_SEQUENCE_V0
              xdebug("fixed:     integer: %d, boolean: %d, %s value: %d\n",
                     json_is_integer(new_value), json_is_boolean(new_value),
                     json_is_integer(new_value) ? "integer" : "boolean",
                     json_is_integer(new_value) ? json_integer_value(new_value) : json_boolean_value(new_value));
#endif
            }
            // update value
            json_object_del(value, "value");
            json_object_set(value, "value", new_value);
          }
        }
      }
#ifdef DEBUG_SEQUENCE_V0
      DEBUG_JSON("new params", new_params);
#endif
      layer->plugin_parameters = new_params;
      json_decref(parameters);
    }
    s->layers = g_list_append(s->layers, (gpointer)layer);
  }

  // Get 3D parameters
  s->params3d = json_deep_copy(json_object_get(parsed_json, "params3d"));
  if ((NULL != s->params3d) && (sequence_version < 3)) {
    camel_case_params3d(s->params3d);
  }

  // Upgrade bandpass
  if (sequence_version < 2) {
    json_t *bandpass = json_object_get(parsed_json, "bandpass_min");

    if (NULL != bandpass) {
      s->bandpass_min = json_integer_value(bandpass);
      bandpass = json_object_get(parsed_json, "bandpass_max");
      s->bandpass_max = json_integer_value(bandpass);
    }
  } else {
    json_t *bandpass = json_object_get(parsed_json, "bandpassMin");

    if (NULL != bandpass) {
      s->bandpass_min = json_integer_value(bandpass);
      bandpass = json_object_get(parsed_json, "bandpassMax");
      s->bandpass_max = json_integer_value(bandpass);
    }
  }

  return s;

error:
  VERBOSE(printf("[!] Failed to load sequence\n"));
  Sequence_delete(s);

  return NULL;
}


Sequence_t *
Sequence_load_json(const char *file)
{
  if (NULL == file) {
    xerror("Attempt to load a sequence with a NULL filename\n");
  }

  char *dot = strrchr(file, '.');
  if ((NULL == dot) || strcasecmp(dot, ".json")) {
#ifdef DEBUG
    VERBOSE(printf("[!] Not a sequence filename: '%s'\n", file));
#endif
    return NULL;
  }

#ifdef DEBUG
  VERBOSE(printf("[i] Loading sequence from file '%s'\n", file));
#endif

  gchar *file_with_path = g_strdup_printf("%s/%s", Sequences_get_dir(), file);
  json_t *parsed_json = json_load_file(file_with_path, 0, NULL);

  if (NULL == parsed_json) {
    VERBOSE(printf("[!] Failed to parse JSON from '%s'\n", file));
    g_free(file_with_path);

    return NULL;
  }
  g_free(file_with_path);

  Sequence_t *s = Sequence_from_json(parsed_json);
  json_decref(parsed_json);

  if (NULL != s) {
    *dot = '\0';
    s->name = strdup(file);
    *dot = '.';
  }

  return s;
}


Sequence_t *
Sequence_load(const char *filename)
{
  char *dot = strrchr(filename, '.');

  if (NULL != dot) {
    if (strncasecmp(dot, ".json", 5 * sizeof(char)) == 0) {
      return Sequence_load_json(filename);
    }
  }

  return NULL;
}


static void
camel_case_params3d(json_t *params3d)
{
  json_t *tmp = json_object_get(params3d, "rotate_amount");

  if (NULL != tmp) {
    json_object_set(params3d, "rotateAmount", tmp);
    json_object_del(params3d, "rotate_amount");
  }

  tmp = json_object_get(params3d, "rotate_factor");
  if (NULL != tmp) {
    json_object_set(params3d, "rotateFactor", tmp);
    json_object_del(params3d, "rotate_factor");
  }

  tmp = json_object_get(params3d, "scale_factor_coeff");
  if (NULL != tmp) {
    json_object_set(params3d, "scaleFactorCoeff", tmp);
    json_object_del(params3d, "scale_factor_coeff");
  }
}
