/*
 *  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 <glib/gstdio.h>
#include "settings.h"
#include "utils.h"
#include "constants.h"
#include "main.h"
#include "biniou.h"
#include "pthread_utils.h"


extern char *data_dir;
static json_t *settings = NULL;
static gchar *json_settings = NULL;
uint8_t json_settings_type = CFG_DEFAULT;
extern double volume_scale;
extern enum startModes start_mode;
const char *start_modes[SM_NB] = { "last_created", "first_created", "last_updated" };
#ifdef WITH_WEBCAM
extern uint8_t hflip, vflip;
#endif
extern uint8_t usage_statistics;
static pthread_mutex_t settings_mutex;


static enum startModes
str2startModes(const char *str)
{
  if (is_equal(str, start_modes[SM_LAST])) {
    return SM_LAST;
  } else if (is_equal(str, start_modes[SM_FIRST])) {
    return SM_FIRST;
  } else if (is_equal(str, start_modes[SM_LAST_UPDATED])) {
    return SM_LAST_UPDATED;
  } else {
    fprintf(stderr, "[!] Invalid startMode '%s'\n", str);
    return SM_LAST;
  }
}


void
Settings_new(void)
{
  gchar *settings_dir = g_strdup_printf("%s/.%s", g_get_home_dir(), PACKAGE_NAME);

  mkdir(settings_dir, DIRECTORY_MODE);
  g_free(settings_dir);
  json_settings = g_strdup_printf("%s/.%s/%s", g_get_home_dir(), PACKAGE_NAME, JSON_SETTINGS);
  settings = json_pack("{s [sss]}", "themes", "biniou", "covid-19", "zebulon");
  xpthread_mutex_init(&settings_mutex, NULL);
}


void
Settings_delete(void)
{
  json_decref(settings);
  g_free(json_settings);
  xpthread_mutex_destroy(&settings_mutex);
}


json_t *
Settings_get(void)
{
  // DEBUG_JSON("settings", settings);
  return json_deep_copy(settings);
}


json_t *
Settings_get_input(void)
{
  return json_object_get(settings, "input");
}


json_t *
Settings_get_plugins(void)
{
  return json_copy(json_object_get(settings, "plugins"));
}


void
Settings_set_plugins(json_t *plugins)
{
  json_object_set(settings, "plugins", plugins);
}


static void
update_plugins(const json_t *new_plugins, json_t **old_plugins, uint8_t *restart)
{
  size_t index1, index2;
  json_t *obj;
  uint16_t old_enabled = 0, new_enabled = 0;

  // Disable all plugins
  json_array_foreach(*old_plugins, index1, obj) {
    if (json_boolean_value(json_object_get(obj, "enabled"))) {
      old_enabled++;
    }
    json_object_set(obj, "enabled", json_false());
  }

  // Look for enabled plugins
  json_array_foreach(new_plugins, index1, obj) {
    json_t *plugin;
    // Update array
    json_array_foreach(*old_plugins, index2, plugin) {
      if (is_equal(json_string_value(json_object_get(plugin, "name")), json_string_value(obj))) {
        json_object_set(plugin, "enabled", json_true());
        new_enabled++;
      }
    }
  }

  if (old_enabled != new_enabled) {
    *restart = 1;
  }
}


static json_t *
get_themes(gchar *themes_dir, const uint8_t required)
{
  json_t *themes = json_array();
  GDir *dir = g_dir_open(themes_dir, 0, NULL);

  if (NULL != dir) {
    const gchar *entry = g_dir_read_name(dir);

    while (NULL != entry) {
      gchar *entry_dir = g_strdup_printf("%s/%s", themes_dir, entry);
      GStatBuf sbuf;

      if (!g_stat(entry_dir, &sbuf)) {
        if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {
          json_array_append_new(themes, json_string(entry));
        }
      }
      g_free(entry_dir);
      entry = g_dir_read_name(dir);
    }
    g_dir_close(dir);
  } else {
    if (required) {
      xerror("%s: failed to read images directory %s\n", __FILE__, themes_dir);
    }
  }
  g_free(themes_dir);

  return themes;
}


static json_t *
get_base_themes(void)
{
  gchar *themes_dir = g_strdup_printf("%s/images", data_dir);

  return get_themes(themes_dir, 1);
}


static json_t *
get_user_themes(void)
{
  gchar *themes_dir = g_strdup_printf("%s/." PACKAGE_NAME "/images", g_get_home_dir());

  return get_themes(themes_dir, 0);
}


json_t *
Settings_get_themes(void)
{
  return json_deep_copy(json_object_get(settings, "themes"));
}


json_t *
Settings_get_all_themes(void)
{
  return json_pack("{so so so}",
                   "baseThemes", get_base_themes(),
                   "selectedThemes", Settings_get_themes(),
                   "userThemes", get_user_themes());
}


static void
Settings_save(void)
{
  json_dump_file(settings, json_settings, JSON_INDENT(4));
}


static void
Settings_plugins(uint16_t *enabled, uint16_t *disabled)
{
  size_t index;
  json_t *plugin;
  json_t *plugins = Settings_get_plugins();

  json_array_foreach(plugins, index, plugin) {
    if (json_boolean_value(json_object_get(plugin, "enabled"))) {
      ++*enabled;
    } else {
      ++*disabled;
    }
  }
  json_decref(plugins);
}


uint8_t
Settings_parse_post(Context_t *ctx, json_t *new_settings)
{
  uint8_t restart = 0;
  json_t *old_plugins = Settings_get_plugins();
  json_t *old_themes = Settings_get_themes();
  int sequencesMin = DELAY_MIN, sequencesMax = DELAY_MAX;
  int colormapsMin = DELAY_MIN, colormapsMax = DELAY_MAX;
  int imagesMin = DELAY_MIN, imagesMax = DELAY_MAX;
#ifdef WITH_WEBCAM
  int old_webcams = ctx->webcams;
  int webcamsMin = DELAY_MIN, webcamsMax = DELAY_MAX;
#endif

  const json_t *input = json_object_get(new_settings, "input");
  const char *old_input_name = json_string_value(json_object_get(json_object_get(settings, "input"), "name"));
  const char *new_input_name = json_string_value(json_object_get(input, "name"));
  if (!is_equal(old_input_name, new_input_name)) {
    restart = 1;
  }
  Context_set_volume_scale(ctx, json_number_value(json_object_get(input, "volumeScale")));

  const json_t *engine = json_object_get(new_settings, "engine");
#ifdef WITH_WEBCAM
  int new_webcams = json_integer_value(json_object_get(engine, "webcams"));
  if (old_webcams != new_webcams) {
    restart = 1;
  }
#endif
  ctx->random_mode = json_integer_value(json_object_get(engine, "randomMode"));
  ctx->max_fps = json_integer_value(json_object_get(engine, "maxFps"));
  fade_delay = json_number_value(json_object_get(engine, "fadeDelay"));
  colormapsMin = json_integer_value(json_object_get(engine, "colormapsMin"));
  colormapsMax = json_integer_value(json_object_get(engine, "colormapsMax"));
  imagesMin = json_integer_value(json_object_get(engine, "imagesMin"));
  imagesMax = json_integer_value(json_object_get(engine, "imagesMax"));
  sequencesMin = json_integer_value(json_object_get(engine, "sequencesMin"));
  sequencesMax = json_integer_value(json_object_get(engine, "sequencesMax"));
  biniou_set_delay(BD_SEQUENCES, sequencesMin, sequencesMax);
  Alarm_update(ctx->a_random, sequencesMin, sequencesMax);
  biniou_set_delay(BD_COLORMAPS, colormapsMin, colormapsMax);
  Alarm_update(ctx->a_cmaps, colormapsMin, colormapsMax);
  biniou_set_delay(BD_IMAGES, imagesMin, imagesMax);
  if (NULL != ctx->a_images) {
    Alarm_update(ctx->a_images, imagesMin, imagesMax);
  }
  sequences->shuffler->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoSequencesMode")));
  ctx->cf->shf->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoColormapsMode")));
  if (NULL != ctx->imgf) {
    ctx->imgf->shf->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoImagesMode")));
  }
#ifdef WITH_WEBCAM
  hflip = json_boolean_value(json_object_get(engine, "hFlip"));
  vflip = json_boolean_value(json_object_get(engine, "vFlip"));
  webcamsMin = json_integer_value(json_object_get(engine, "webcamsMin"));
  webcamsMax = json_integer_value(json_object_get(engine, "webcamsMax"));
  biniou_set_delay(BD_WEBCAMS, webcamsMin, webcamsMax);
  if (old_webcams > 1) {
    ctx->webcams_shuffler->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoWebcamsMode")));
    Alarm_update(ctx->a_webcams, webcamsMin, webcamsMax);
  }
#endif

  enum startModes new_start_mode = str2startModes(json_string_value(json_object_get(engine, "startMode")));
  if (new_start_mode != start_mode) {
    restart = 1;
    start_mode = new_start_mode;
  }
  usage_statistics = json_boolean_value(json_object_get(new_settings, "statistics"));
  uint8_t create_symlink = json_boolean_value(json_object_get(new_settings, "createSymlink"));

  json_t *new_themes = json_object_get(new_settings, "themes");
  if (!json_equal(new_themes, old_themes)) {
    restart = 1;
  }
  json_decref(old_themes);

  if (create_symlink) {
    int8_t ret = create_dirs();

    if (!ret) {
      printf("[i] Created symbolic link and directories\n");
    } else {
      fprintf(stderr, ("[!] Failed to create symbolic link and directories\n"));
    }
  }

  json_object_set_new(new_settings, "version", json_string(LEBINIOU_VERSION));
  update_plugins(json_object_get(new_settings, "plugins"), &old_plugins, &restart);
  json_object_del(new_settings, "plugins");
  json_object_set_new(new_settings, "plugins", old_plugins);

  // DEBUG_JSON("new settings", new_settings, 1);
  json_decref(settings);
  settings = json_deep_copy(new_settings);
  Settings_save();

  if (usage_statistics) {
    uint16_t enabled = 0, disabled = 0;
    Settings_plugins(&enabled, &disabled);
    json_t *fields = json_pack("{sisisisisisi}",
                               "themes", json_array_size(new_themes),
                               "maxFps", ctx->max_fps,
                               "randomMode", ctx->random_mode,
                               "webcams", ctx->webcams,
                               "enabled", enabled,
                               "disabled", disabled);
    // DEBUG_JSON("fields", fields, 1);

    gchar *resolution = g_strdup_printf("%dx%d", WIDTH, HEIGHT);
    json_t *tags = json_pack("{sbsssssssb}",
                             "flatpak", FLATPAK_AS_BOOLEAN,
                             "resolution", resolution,
                             "input", new_input_name,
                             "version", LEBINIOU_VERSION,
                             "optIn", usage_statistics);
    g_free(resolution);
    // DEBUG_JSON("tags", tags, 1);

    bulfius_post_report("/settings", fields, tags);
    json_decref(fields);
    json_decref(tags);
  }

  json_t *state = json_pack("{sO}", "newSettings", settings);
  bulfius_websocket_broadcast_json_message(ctx, state, NULL);
  json_decref(state);

  return restart;
}


const char *
Settings_get_configuration_file(void)
{
  return json_settings;
}


void
Settings_set_configuration_file(const char *file)
{
  g_free(json_settings);
  json_settings = g_strdup(file);
  json_settings_type = CFG_CUSTOM;
}


static uint16_t
get_integer(const json_t *o, const char *k)
{
  return json_integer_value(json_object_get(o, k));
}


#ifndef FIXED
static void
set_screen(const json_t *screen)
{
  width = get_integer(screen, "width");
  if (!width || (width > 3840)) { // 3840 = 4K UHD width
    fprintf(stderr, "[!] Settings have an invalid display width, using default: %d\n", DEFAULT_WIDTH);
    width = DEFAULT_WIDTH;
  }
  height = get_integer(screen, "height");
  if (!height || (height > 2160)) { // 2160 = 4K UHD height
    fprintf(stderr, "[!] Settings have an invalid display height, using default: %d\n", DEFAULT_HEIGHT);
    height = DEFAULT_HEIGHT;
  }
}
#endif


static void
set_input(const json_t *input)
{
  const char *name = json_string_value(json_object_get(input, "name"));
  const double volumeScale = json_real_value(json_object_get(input, "volumeScale"));

  if (NULL != name) {
    xfree(input_plugin);
    input_plugin = strdup(name);
  }
  if (volumeScale > 0) {
    volume_scale = volumeScale;
  }
}


static void
set_engine(const json_t *engine)
{
  max_fps = get_integer(engine, "maxFps");
  fade_delay = json_number_value(json_object_get(engine, "fadeDelay"));
  if (fade_delay < 0.01) {
    fprintf(stderr, "[!] Invalid fadeDelay, using default\n");
    fade_delay = DEFAULT_FADE_DELAY;
  }
  start_mode = str2startModes(json_string_value(json_object_get(engine, "startMode")));
  random_mode = get_integer(engine, "randomMode");
  if ((random_mode < BR_NONE) || (random_mode > BR_BOTH)) {
    fprintf(stderr, "[!] Invalid randomMode, using default\n");
    random_mode = DEFAULT_RANDOM_MODE;
  }
  biniou_set_delay(BD_SEQUENCES, get_integer(engine, "sequencesMin"), get_integer(engine, "sequencesMax"));
  biniou_set_delay(BD_COLORMAPS, get_integer(engine, "colormapsMin"), get_integer(engine, "colormapsMax"));
  biniou_set_delay(BD_IMAGES, get_integer(engine, "imagesMin"), get_integer(engine, "imagesMax"));
#ifdef WITH_WEBCAM
  hflip = json_boolean_value(json_object_get(engine, "hFlip"));
  vflip = json_boolean_value(json_object_get(engine, "vFlip"));
  desired_webcams = get_integer(engine, "webcams");
#endif
}


void
Settings_finish(Context_t *ctx)
{
  json_t *engine = json_object_get(settings, "engine");
  if (NULL != engine) {
    sequences->shuffler->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoSequencesMode")));
    ctx->cf->shf->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoColormapsMode")));
    if (NULL != ctx->imgf) {
      ctx->imgf->shf->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoImagesMode")));
    }
  } else {
    sequences->shuffler->mode = ctx->cf->shf->mode = BS_SHUFFLE;
    if (NULL != ctx->imgf) {
      ctx->imgf->shf->mode = BS_SHUFFLE;
    }
  }
#ifdef WITH_WEBCAM
  if (NULL != engine) {
    biniou_set_delay(BD_WEBCAMS, get_integer(engine, "webcamsMin"), get_integer(engine, "webcamsMax"));
    if (ctx->webcams > 1) {
      ctx->webcams_shuffler->mode = Shuffler_parse_mode(json_string_value(json_object_get(engine, "autoWebcamsMode")));
    }
  } else {
    biniou_set_delay(BD_WEBCAMS, DELAY_MIN, DELAY_MAX);
    if (ctx->webcams > 1) {
      ctx->webcams_shuffler->mode = BS_CYCLE;
    }
  }
#endif
  Context_set_max_fps(ctx, max_fps);
}


void
Settings_load(void)
{
  json_error_t error;
  json_t *loaded_settings = json_load_file(json_settings, 0, &error);

  if (NULL == loaded_settings) {
    xdebug("Failed to load settings: %s\n", error.text);

    return;
  }
  json_decref(settings);
  settings = loaded_settings;
  json_t *statistics = json_object_get(settings, "statistics");
  if (NULL != statistics) {
    usage_statistics = json_boolean_value(statistics);
  }
#ifndef FIXED
  set_screen(json_object_get(settings, "screen"));
#endif
  set_input(json_object_get(settings, "input"));
  set_engine(json_object_get(settings, "engine"));

  if (json_integer_value(json_object_get(settings, "version")) == 1) {
    // Settings v2 require a displayName, not display_name
    json_t *plugin;
    const char *key;

    json_object_foreach(json_object_get(settings, "plugins"), key, plugin) {
      json_object_set_new(plugin, "displayName", json_object_get(plugin, "display_name"));
    }
  }

  if (NULL == json_object_get(settings, "themes")) {
    json_object_set_new(settings, "themes", json_pack("[sss]", "biniou", "covid-19", "zebulon"));
  }
}


void
Settings_lock(void)
{
  xpthread_mutex_lock(&settings_mutex);
}


void
Settings_unlock(void)
{
  xpthread_mutex_unlock(&settings_mutex);
}


uint8_t
Settings_is_favorite(const char *name)
{
  size_t index;
  json_t *value;
  uint8_t ret = 0;
  json_t *plugins = json_object_get(settings, "plugins");

  json_array_foreach(plugins, index, value) {
    if (is_equal(json_string_value(json_object_get(value, "name")), name)) {
      json_t *fav = json_object_get(value, "favorite");

      if (json_is_boolean(fav)) {
        ret = json_boolean_value(fav);
      } else { // no favorite status
        ret = 0;
      }

      return ret;
    }
  }

  return ret;
}


uint8_t
Settings_switch_favorite(const char *name)
{
  size_t index;
  json_t *value;
  uint8_t ret = 0;

  Settings_lock();
  json_t *plugins = json_object_get(settings, "plugins");

  json_array_foreach(plugins, index, value) {
    if (is_equal(json_string_value(json_object_get(value, "name")), name)) {
      json_t *fav = json_object_get(value, "favorite");

      if (json_is_boolean(fav)) { // switch value
        uint8_t is_favorite = json_boolean_value(fav);
        ret = !is_favorite;
        json_object_set(value, "favorite", json_boolean(ret));
      } else { // no favorite status, set to true
        json_object_set_new(value, "favorite", json_true());
        ret = 1;
      }
    }
  }
  Settings_save();
  Settings_unlock();

  return ret;
}
