/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  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 "biniou.h"
#include "plugin.h"


json_t *
plugin_parameters_to_saved_parameters(json_t *in_parameters)
{
  json_t *out_parameters = json_object();
  const char *in_name;
  const json_t *in_param;

  json_object_foreach(in_parameters, in_name, in_param) {
#ifdef DEBUG_PARAMETERS
    printf("param name : %s\n", in_name);
#endif
    json_t *value = json_object_get(in_param, "value");

    assert(NULL != value);
    json_object_set_new(out_parameters, in_name, json_pack("{sO}", "value", value));
  }

  return out_parameters;
}


uint8_t
plugin_parameter_number(json_t *in_parameters)
{
  uint8_t n = 0;

  for (void *iter = json_object_iter(in_parameters); NULL != iter; n++) {
    iter = json_object_iter_next(in_parameters, iter);
  }

  return n;
}


void
plugin_parameters_add_boolean(json_t *params, const char *name, const int v, const char *description)
{
  json_object_set_new(params, name, json_pack("{sssbss?}", "type", "boolean", "value", v, "description", description));
}


void
plugin_parameters_add_playlist(json_t *params, const char *name, json_t *value, const char *description)
{
  json_object_set_new(params, name, json_pack("{sssOss?}", "type", "playlist", "value", value, "description", description));
}


void
plugin_parameters_add_int(json_t *params, const char *name, const int v, const int min, const int max, const int step, const char *description)
{
  json_object_set_new(params, name, json_pack("{sssisisisiss?}",
                      "type", "integer",
                      "min", min,
                      "max", max,
                      "step", step,
                      "value", v,
                      "description", description));
}


void
plugin_parameters_add_double(json_t *params, const char *name, const double v, const double min, const double max, const double step, const char *description)
{
  json_object_set_new(params, name, json_pack("{sssfsfsfsfss?}",
                      "type", "double",
                      "min", min,
                      "max", max,
                      "step", step,
                      "value", v,
                      "description", description));
}


void
plugin_parameters_add_string_list(json_t *params, const char *name, const uint32_t nb_elems, const char **elems, const uint32_t elem_id, const int max, const char *description)
{
  json_t *param = json_pack("{sisisissssss?}",
                            "min", 0,
                            "max", max,
                            "step", 1,
                            "type", "string_list",
                            "value", elems[elem_id],
                            "description", description);
  json_t *value_list = json_array();

  for (uint32_t n = 0; n < nb_elems; n++) {
    json_array_append_new(value_list, json_string(elems[n]));
  }
  json_object_set_new(param, "value_list", value_list);
  json_object_set_new(params, name, param);
}


uint8_t
plugin_parameter_parse_boolean(const json_t *in_parameters, const char *name, int *value)
{
  int ret = 0;
  json_t *p = json_object_get(in_parameters, name);

  if (NULL != p) {
    json_t *j_value = json_object_get(p, "value");

    if (json_is_boolean(j_value) && (*value != json_boolean_value(j_value))) {
      ret |= PLUGIN_PARAMETER_CHANGED;
    }
    *value = json_boolean_value(j_value);
  }

  return ret;
}


uint8_t
plugin_parameter_parse_int(const json_t *in_parameters, const char *name, int *value)
{
  json_t *p = json_object_get(in_parameters, name);

  if (NULL != p) {
    json_t *j_value = json_object_get(p, "value");

    if (json_is_integer(j_value)) {
      *value = json_integer_value(j_value);

      return 1;
    }
  }

  return 0;
}


uint8_t
plugin_parameter_parse_int_range(const json_t *in_parameters, const char *name, int *value)
{
  int new_value = 0;
  uint8_t ret = plugin_parameter_parse_int(in_parameters, name, &new_value);
  json_t *param = json_object_get(in_parameters, name);
  int vmin = json_integer_value(json_object_get(param, "min"));
  int vmax = json_integer_value(json_object_get(param, "max"));

  if (new_value >= vmin && new_value <= vmax && ret == 1) {
    if (*value != new_value) {
      ret |= PLUGIN_PARAMETER_CHANGED;
    }
    *value = new_value;
  }

  return ret;
}


uint8_t
plugin_parameter_parse_double(const json_t *in_parameters, const char *name, double *value)
{
  json_t *p = json_object_get(in_parameters, name);

  if (NULL != p) {
    json_t *j_value = json_object_get(p, "value");

    if (json_is_real(j_value)) {
      *value = json_real_value(j_value);

      return 1;
    }
  }

  return 0;
}


uint8_t
plugin_parameter_parse_double_range(const json_t *in_parameters, const char *name, double *value)
{
  double new_value = 0;
  uint8_t ret = plugin_parameter_parse_double(in_parameters, name, &new_value);
  json_t *param = json_object_get(in_parameters, name);
  double vmin = json_real_value(json_object_get(param, "min"));
  double vmax = json_real_value(json_object_get(param, "max"));

  if (new_value >= vmin && new_value <= vmax && ret == 1) {
    if (*value != new_value) {
      ret |= PLUGIN_PARAMETER_CHANGED;
    }
    *value = new_value;
  }

  return ret;
}


uint8_t
plugin_parameter_parse_playlist(const json_t *in_parameters, const char *name, json_t **value)
{
  int ret = 0;
  json_t *p = json_object_get(in_parameters, name);

  if (NULL != p) {
    json_t *j_value = json_object_get(p, "value");

    if (!json_equal(j_value, *value)) {
      ret |= PLUGIN_PARAMETER_CHANGED;
      json_decref(*value);
      *value = json_deep_copy(j_value);
    }
  }

  return ret;
}


uint8_t
plugin_parameter_parse_string(const json_t *in_parameters, const char *name, char **value)
{
  json_t *p = json_object_get(in_parameters, name);

  if (NULL != p) {
    json_t *j_value = json_object_get(p, "value");
    if (json_is_string(j_value)) {
      const char *str = json_string_value(j_value);
      *value = (char *)str;
      return 1;
    }
  }

  return 0;
}


uint8_t
plugin_parameter_parse_string_list_as_int_range(const json_t *in_parameters, const char *name,
                                                const uint32_t nb_elems, const char **elems, int *value)
{
  uint8_t ret       = 0;
  int     new_value = 0;
  char   *str       = NULL;
  json_t *param     = json_object_get(in_parameters, name);
  int     vmax      = json_integer_value(json_object_get(param, "max"));

  if (plugin_parameter_parse_string(in_parameters, name, &str)) {
    for (uint32_t n = 0; n < nb_elems; n++) {
      if (is_equal(elems[n], str)) {
        new_value = n;

        if (new_value >= 0 && new_value <= vmax) {
          ret = 1;
          if (*value != new_value) {
            ret |= PLUGIN_PARAMETER_CHANGED;
          }

          *value = new_value;
        }
      }
    }
  }

  return ret;
}


uint8_t
plugin_parameter_find_string_in_list(const json_t *in_parameters, const char *name, int *value)
{
  uint8_t ret = 0;

  json_t *j_value = json_object_get(in_parameters, "value");
  const char *str = json_string_value(j_value);

  json_t *j_value_list = json_object_get(in_parameters, "value_list");
  if (json_is_array(j_value_list) && (NULL != str)) {
    for (uint32_t n = 0; n < (uint32_t)json_array_size(j_value_list); n++) {
      const char *list_str = json_string_value(json_array_get(j_value_list, n));

      if (is_equal(list_str, str)) {
        *value = n;
        ret = 1;

        break;
      }
    }
  }

  return ret;
}


static void
plugin_parameter_update_sequence(Context_t *ctx, const json_t *params)
{
  const GList *ptr = Sequence_find(ctx->sm->cur, plugins->selected);

  if (NULL != ptr) {
    Layer_t *layer = (Layer_t *)ptr->data;

    json_decref(layer->plugin_parameters);
    layer->plugin_parameters = json_deep_copy(params);
  }
}


json_t *
plugin_parameter_change(Context_t *ctx, const float factor)
{
  const GList *ptr = Sequence_find(ctx->sm->cur, plugins->selected);

  if (NULL != ptr) {
    json_t *params = plugin_parameter_change_selected(ctx, factor);

    plugin_parameter_update_sequence(ctx, params);

    return params;
  } else {
    return NULL;
  }
}


json_t *
plugin_parameter_change_selected(Context_t *ctx, const float factor)
{
  json_t *ret = NULL;

  if (NULL != plugins->selected->parameters) {
    json_t *j_params = plugins->selected->parameters(ctx, NULL, 0);
    void *params_iter = json_object_iter(j_params);

    for (uint8_t n = 0; n < plugins->selected->selected_param; n++) {
      params_iter = json_object_iter_next(j_params, params_iter);
    }

    json_t *p = json_object_iter_value(params_iter);
    if (NULL == p) {
      json_decref(j_params);

      return ret;
    }
    json_t *j_step = json_object_get(p, "step");
    json_t *j_type = json_object_get(p, "type");
    json_t *j_value = json_object_get(p, "value");

    assert(NULL != j_value);
    if (is_equal("boolean", json_string_value(j_type))) {
      json_object_del(p, "value");
      json_object_set_new(p, "value", json_boolean(!json_boolean_value(j_value)));

      ret = plugins->selected->parameters(ctx, j_params, 0);
    } else if (is_equal("integer", json_string_value(j_type))) {
      assert(NULL != j_step);
      int step = json_integer_value(j_step) * factor;
      int value = json_integer_value(j_value);

      json_object_del(p, "value");
      json_object_set_new(p, "value", json_integer(value + step));

      ret = plugins->selected->parameters(ctx, j_params, 0);
    } else if (is_equal("double", json_string_value(j_type))) {
      assert(NULL != j_step);
      double step = json_real_value(j_step) * factor;
      double value = json_real_value(j_value);

      json_object_del(p, "value");
      json_object_set_new(p, "value", json_real(value + step));

      ret = plugins->selected->parameters(ctx, j_params, 0);
    } else if (is_equal("string_list", json_string_value(j_type))) {
      assert(NULL != j_step);
      int step = json_integer_value(j_step) * factor;
      int list_index = 0;

      if (plugin_parameter_find_string_in_list(p, "value", &list_index)) {
        json_t *j_value_list = json_object_get(p, "value_list");
        int N = json_array_size(j_value_list);

        int new_value = list_index + step;
        if (new_value >= N) {
          new_value = new_value % N;
        } else if (new_value < 0) {
          new_value = N + new_value % N;
        }

        const char *str = json_string_value(json_array_get(j_value_list, new_value));
        json_object_del(p, "value");
        json_object_set_new(p, "value", json_string(str));
        plugin_parameter_update_sequence(ctx, j_params);
        ret = plugins->selected->parameters(ctx, j_params, 0);
      }
    }
    json_decref(j_params);
  }

  return ret;
}


/*
 * This function is used for updates made via the web UI
 * using UI_CMD_SEQ_SET_PARAM_CHECKBOX_VALUE
 */
json_t *
plugin_parameter_set_selected_from_checkbox(Context_t *ctx, const int value)
{
  json_t *ret = NULL;

  if (NULL != plugins->selected->parameters) {
    json_t *j_params = plugins->selected->parameters(ctx, NULL, 0);
    void *params_iter = json_object_iter(j_params);

    for (uint8_t n = 0; n < plugins->selected->selected_param; n++) {
      params_iter = json_object_iter_next(j_params, params_iter);
    }

    json_t *p = json_object_iter_value(params_iter);
    const char *type = json_string_value(json_object_get(p, "type"));

    if (is_equal(type, "boolean")) {
      json_object_del(p, "value");
      json_object_set_new(p, "value", json_boolean(value));
      plugin_parameter_update_sequence(ctx, j_params);
      ret = plugins->selected->parameters(ctx, j_params, 0);
    }
    json_decref(j_params);
  }

  return ret;
}


/*
 * This function is used for updates made via the web UI
 * using UI_CMD_SEQ_SET_PARAM_PLAYLIST_VALUE
 */
json_t *
plugin_parameter_set_selected_from_playlist(Context_t *ctx, const json_t *value)
{
  json_t *ret = NULL;

  if (NULL != plugins->selected->parameters) {
    json_t *j_params = plugins->selected->parameters(ctx, NULL, 0);
    void *params_iter = json_object_iter(j_params);

    for (uint8_t n = 0; n < plugins->selected->selected_param; n++) {
      params_iter = json_object_iter_next(j_params, params_iter);
    }

    json_t *p = json_object_iter_value(params_iter);
    const char *type = json_string_value(json_object_get(p, "type"));

    if (is_equal(type, "playlist")) {
      json_object_del(p, "value");
      json_object_set_new(p, "value", json_deep_copy(value));
      plugin_parameter_update_sequence(ctx, j_params);
      ret = plugins->selected->parameters(ctx, j_params, 0);
    }
    json_decref(j_params);
  }

  return ret;
}


/*
 * This function is used for updates made via the web UI
 * using UI_CMD_SEQ_SET_PARAM_SELECT_VALUE
 */
json_t *
plugin_parameter_set_selected_from_select(Context_t *ctx, const char *value)
{
  json_t *ret = NULL;

  if (NULL != plugins->selected->parameters) {
    json_t *j_params = plugins->selected->parameters(ctx, NULL, 0);
    void *params_iter = json_object_iter(j_params);

    for (uint8_t n = 0; n < plugins->selected->selected_param; n++) {
      params_iter = json_object_iter_next(j_params, params_iter);
    }

    json_t *p = json_object_iter_value(params_iter);
    const char *type = json_string_value(json_object_get(p, "type"));

    if (is_equal(type, "string_list")) {
      json_object_del(p, "value");
      json_object_set_new(p, "value", json_string(value));
      plugin_parameter_update_sequence(ctx, j_params);
      ret = plugins->selected->parameters(ctx, j_params, 0);
    }
    json_decref(j_params);
  }

  return ret;
}


/*
 * This function is used for updates made via the web UI
 * using UI_CMD_SEQ_SET_PARAM_SLIDER_VALUE
 *
 * So, double values are sent as integer*1000
 */
json_t *
plugin_parameter_set_selected_from_slider(Context_t *ctx, const int value)
{
  json_t *ret = NULL;

  if (NULL != plugins->selected->parameters) {
    json_t *j_params = plugins->selected->parameters(ctx, NULL, 0);
    void *params_iter = json_object_iter(j_params);

    for (uint8_t n = 0; n < plugins->selected->selected_param; n++) {
      params_iter = json_object_iter_next(j_params, params_iter);
    }

    json_t *p = json_object_iter_value(params_iter);
    json_t *j_type = json_object_get(p, "type");
    const char *type = json_string_value(j_type);

    if (is_equal(type, "integer") || is_equal(type, "double")) {
      if (is_equal("integer", type)) {
        json_object_del(p, "value");
        json_object_set_new(p, "value", json_integer(value));
        plugin_parameter_update_sequence(ctx, j_params);
        ret = plugins->selected->parameters(ctx, j_params, 0);
      } else if (is_equal("double", type)) {
        json_object_del(p, "value");
        json_object_set_new(p, "value", json_real(value / 1000.0));
        plugin_parameter_update_sequence(ctx, j_params);
        ret = plugins->selected->parameters(ctx, j_params, 0);
      }
    }
    json_decref(j_params);
  }

  return ret;
}
