/*
 *  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/>.
 */

#ifdef MAGICKWAND7
#include <MagickWand/MagickWand.h>
#else
#include <wand/magick_wand.h>
#endif
#include "biniou.h"
#include "sequence.h"
#include "brandom.h"
#include "globals.h"
#include "images.h"
#include "colormaps.h"
#include "paths.h"
#include "defaults.h"
#include "pthread_utils.h"


extern enum startModes start_mode;
extern char *input_plugin;

static pthread_t images_thread;
static struct Images_thread_args {
  gchar *buf;
  const char *themes;
} images_thread_args;
extern char *playlist_filename;
extern uint8_t use_hard_timers;
extern uint8_t usage_statistics;


static void *
biniou_images_thread(void *_args)
{
  struct Images_thread_args *args = (struct Images_thread_args *)_args;

  MagickWandGenesis();
  Images_new(args->buf, args->themes);
  g_free(args->buf);
  MagickWandTerminus();

  return NULL;
}


static pthread_t colormaps_thread;
static struct Colormaps_thread_args {
  gchar *buf;
} colormaps_thread_args;


static void *
biniou_colormaps_thread(void *_args)
{
  struct Colormaps_thread_args *args = (struct Colormaps_thread_args *)_args;

  Colormaps_new(args->buf);
  g_free(args->buf);

  return NULL;
}


static pthread_t paths_thread;
static struct Paths_thread_args {
  gchar *buf;
} paths_thread_args;


static void *
biniou_paths_thread(void *_args)
{
  struct Paths_thread_args *args = (struct Paths_thread_args *)_args;

  Paths_new(args->buf);
  g_free(args->buf);

  return NULL;
}


void
biniou_new(const char *datadir, const char *pluginsdir,
           const char *schemes_file, const char *themes,
           const uint8_t options,
           const uint32_t input_size,
           const int webcams)
{
  if (options & B_INIT_VERBOSE) {
    libbiniou_verbose = 1;
  }

  if (NULL != datadir) {
    VERBOSE(printf("[+] Loading data files\n"));

    /* Images */
    images_thread_args.buf = g_strdup_printf("%s/images/", datadir);
    images_thread_args.themes = themes;
    xpthread_create(&images_thread, NULL, biniou_images_thread, (void *)&images_thread_args);

    /* Colormaps */
    colormaps_thread_args.buf = g_strdup_printf("%s/colormaps/", datadir);
    xpthread_create(&colormaps_thread, NULL, biniou_colormaps_thread, (void *)&colormaps_thread_args);

    /* Paths */
    paths_thread_args.buf = g_strdup_printf("%s/paths", datadir);
    xpthread_create(&paths_thread, NULL, biniou_paths_thread, (void *)&paths_thread_args);

    xpthread_join(images_thread, NULL);
    xpthread_join(colormaps_thread, NULL);
    xpthread_join(paths_thread, NULL);
  } else {
    Colormaps_new(NULL);
  }

  // Create timers
  Context_create_timers(context);

  /* Schemes */
  if (options & B_INIT_SCHEMES) {
    VERBOSE(printf("[+] Loading schemes from %s\n", schemes_file));
    Schemes_new(schemes_file);
  }

  /* Plugins */
  VERBOSE(printf("[+] Loading plugins\n"));
  plugins = Plugins_new(pluginsdir);
  Plugins_load(plugins, context);
  Plugins_init(plugins);


  /* Sequences */
  if (options & B_INIT_SEQUENCES) {
    VERBOSE(printf("[+] Loading sequences\n"));
    Sequences_new();
  }

  /* playlist */
  if (NULL != playlist_filename) {
    Context_init_playlist(context);
  }

  Timer_set_mode(use_hard_timers ? BT_HARD : BT_SOFT);

  VERBOSE(printf("[+] Biniou initialized\n"));
}


void
biniou_start(void)
{
  if (NULL != context->playlist) {
    Context_next_track(context);
  } else if ((NULL == sequences) || !sequences->size) {
    /* Random boot sequence */
    VERBOSE(printf("[i] No user sequences found\n"));
    Schemes_random(context, 0);
  } else {
    GList *item;
    Sequence_t *start;

    switch (start_mode) {
    case SM_FIRST:
      item = g_list_first(sequences->seqs);
      break;

    case SM_LAST_UPDATED:
      item = Sequences_find_last_updated(sequences->seqs);
      break;

    case SM_LAST:
    default:
      item = g_list_last(sequences->seqs);
      break;
    }
    context->sm->curseq = item;
    start = (Sequence_t *)item->data;

    Sequence_copy(context, start, context->sm->next);
    Shuffler_used(sequences->shuffler, 0);
  }

  assert(NULL != context->sm->cur);
  assert(NULL != context->sm->next);
  Context_set(context);

  if (context->fullscreen)
    for (GSList *outputs = context->outputs; NULL != outputs; outputs = g_slist_next(outputs)) {
      Plugin_t *output = (Plugin_t *)outputs->data;

      if (NULL != output->fullscreen) {
        output->fullscreen(context->fullscreen);
      }
    }
}


void
biniou_delete(void)
{
  if (NULL == context) {
    xerror("biniou_end() called but context is NULL\n");
  } else {
    context->running = 0;  /* This will stop plugins running as threads */
  }

  xfree(input_plugin);

  VERBOSE(printf("[+] Freeing context\n"));
  Context_delete(context);

  VERBOSE(printf("[+] Freeing schemes\n"));
  Schemes_delete();

  VERBOSE(printf("[+] Freeing sequences\n"));
  Sequences_delete();

  VERBOSE(printf("[+] Freeing plugins\n"));
  json_t *fields = Plugins_delete(plugins);
  // DEBUG_JSON("fields", fields);
  json_t *tags = json_pack("{ss}", "version", LEBINIOU_VERSION);
  bulfius_post_report("/plugins", fields, tags);
  json_decref(fields);
  json_decref(tags);

  if (NULL != images) {
    VERBOSE(printf("[+] Freeing %d images(s)\n", images->size));
    Images_delete();
  }

  if (NULL != colormaps) {
    VERBOSE(printf("[+] Freeing %d colormap(s)\n", colormaps->size));
    Colormaps_delete();
  }

  if (NULL != paths) {
    VERBOSE(printf("[+] Freeing %d paths(s)\n", paths->size));
    Paths_delete();
  }

  VERBOSE(printf("[+] Freeing PRNG\n"));
  b_rand_free();
}


void
biniou_loop(void)
{
  biniou_go(0);
}


void
biniou_run(void)
{
  biniou_start();
  biniou_loop();
}


int
biniou_stop(void)
{
  if (NULL == context) {
    return 0;
  } else {
    context->running = 0;

    return 1;
  }
}


void
biniou_load_input(const char *dir, const char *name, const double volume_scale)
{
  Plugin_t *p = NULL;

  p = Plugin_new(dir, name, PL_INPUT);
  if (NULL != p) {
    if (Plugin_init(p)) {
      context->input_plugin = p;
      Context_set_volume_scale(context, volume_scale);
    } else {
      fprintf(stderr, "[!] Failed to initialize %s input\n", name);
    }
  }
}


void
biniou_load_output(const char *dir, const char *name)
{
  Plugin_t *p = NULL;
  gchar **tokens, **output;

  assert(NULL != context);

  tokens = g_strsplit(name, ",", 0);
  output = tokens;

  for ( ; NULL != *output; output++) {
    p = Plugin_new(dir, *output, PL_OUTPUT);
    if (NULL != p) {
      if (Plugin_init(p)) {
        context->outputs = g_slist_append(context->outputs, p);
      } else {
        fprintf(stderr, "Failed to initialize %s output\n", name);
      }
    }
  }

  g_strfreev(tokens);
}


void
biniou_set_full_screen(const uint8_t full_screen)
{
  assert(NULL != context);
  context->fullscreen = full_screen;
}


void
biniou_set_max_fps(const uint8_t fps)
{
  Context_set_max_fps(context, fps);
}


void
biniou_set_random_mode(const enum RandomMode r)
{
  Context_set_engine_random_mode(context, r);
}


void
biniou_go(const uint32_t nb_frames)
{
  uint32_t frames = nb_frames;

  while (context->running && ((nb_frames == 0) || frames)) {
    Context_run(context);

    if (nb_frames != 0) {
      frames--;
      if (!frames) {
        context->running = 0;
      }
    }
  }
}
