/*
 *  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 "globals.h"
#include "context.h"
#include "brandom.h"
#include "colormaps.h"
#include "images.h"
#include "sequences.h"
#include "defaults.h"
#include "biniou.h"


// start mp4 encoding automatically
extern uint8_t encoding;
// video plugin
extern int override_webcam;
// playlist
extern char *playlist_filename;


Images_t *images = NULL;


static enum ShufflerMode random_modes[MAX_TIMERS] = {
  BS_RANDOM,  // colormaps
  BS_RANDOM,  // images
  BS_SHUFFLE  // sequences
#ifdef WITH_WEBCAM
  , BS_CYCLE  // webcams
#endif
};


void
Context_update_auto(Context_t *ctx)
{
  /* figure out auto_colormaps */
#ifdef DEBUG_LOCK
  printf("%s: random colormap, lock: %d\n", __func__, ctx->lock_colormap);
#endif
  if ((NULL != ctx->cf) && !ctx->lock_colormap && ctx->allow_auto_colormaps) {
    if (ctx->sm->cur->auto_colormaps == -1) { // using a bare sequence
      ctx->cf->on = ctx->auto_colormaps;
    } else { // using a full sequence
      ctx->cf->on = ctx->sm->cur->auto_colormaps;
    }

    if (ctx->cf->on && (colormaps->size > 1)) {
      /* reinitialize timer */
      Alarm_init(ctx->a_cmaps);
    }

    /* set as current */
    ctx->auto_colormaps = ctx->cf->on;
  }

  /* figure out auto_images */
#ifdef DEBUG_LOCK
  printf("%s: random image, lock: %d\n", __func__, ctx->lock_image);
#endif
  if ((NULL != ctx->imgf) && !ctx->lock_image && ctx->allow_auto_images) {
    if (ctx->sm->cur->auto_images == -1) { // using a bare sequence
      ctx->imgf->on = ctx->auto_images;
    } else { // using a full sequence
      ctx->imgf->on = ctx->sm->cur->auto_images;
    }

    if (ctx->imgf->on && (NULL != images) && (images->size > 1)) {
      /* reinitialize timer */
      Alarm_init(ctx->a_images);
    }

    /* set as current */
    ctx->auto_images = ctx->imgf->on;
  }
}


int
Context_add_rand(Sequence_t *seq, const enum PluginOptions options, const Plugin_t *locked)
{
  Plugin_t *p = NULL;

  do {
    p = Plugins_get_random(options, locked);
    if (NULL == p) {
      return -1;
    }
  } while (NULL != Sequence_find(seq, p));

  Sequence_insert(seq, p);

  if ((options & BO_NOT_LENS) && (NULL != seq->lens)) {
    seq->lens = NULL;
  }

  return 0;
}


void
Context_insert_plugin(Context_t *ctx, Plugin_t *p)
{
  /* switch the plugin on */
  if (NULL != p->on_switch_on) {
    VERBOSE(printf("[i] on_switch_on '%s' (%s)\n", p->name, p->dname));
    p->on_switch_on(ctx);
  }

  Sequence_insert(ctx->sm->cur, p);
}


void
Context_remove_plugin(Context_t *ctx, Plugin_t *p)
{
  /* switch the plugin off */
  if (NULL != p->on_switch_off) {
    VERBOSE(printf("[i] on_switch_off '%s' (%s)\n", p->name, p->dname));
    p->on_switch_off(ctx);
  }

  Sequence_remove(ctx->sm->cur, p);
}


void
Context_set_max_fps(Context_t *ctx, const uint8_t _max_fps)
{
  if (_max_fps) {
    ctx->max_fps = _max_fps;
    ctx->i_max_fps = 1.0 / ctx->max_fps;
  }
}


void
Context_set_engine_random_mode(Context_t *ctx, const enum RandomMode r)
{
  ctx->random_mode = r;
}


int
Context_fps(const Context_t *ctx)
{
  float mfps = 0.0;
  int i;

  for (i = 0; i < NFPS; i++) {
    mfps += ctx->fps[i];
  }
  return (int)(mfps / (float)NFPS);
}


void
Context_first_sequence(Context_t *ctx)
{
  if (NULL != ctx->sm->curseq) {
    Sequence_t *s;

    ctx->sm->curseq = g_list_first(sequences->seqs);
    s = (Sequence_t *)ctx->sm->curseq->data;
    Sequence_copy(ctx, s, ctx->sm->next);

    Context_set(ctx);
  }
}


void
Context_previous_sequence(Context_t *ctx)
{
  if (NULL != ctx->sm->curseq) {
    Sequence_t *s;

    if (NULL != ctx->sm->curseq->prev) {
      ctx->sm->curseq = ctx->sm->curseq->prev;
    } else {
      ctx->sm->curseq = g_list_last(sequences->seqs);
    }

    s = (Sequence_t *)ctx->sm->curseq->data;
    Sequence_copy(ctx, s, ctx->sm->next);

    Context_set(ctx);
  }
}


void
Context_next_sequence(Context_t *ctx)
{
  if (NULL != ctx->sm->curseq) {
    Sequence_t *s;

    if (NULL != ctx->sm->curseq->next) {
      ctx->sm->curseq = ctx->sm->curseq->next;
    } else {
      ctx->sm->curseq = g_list_first(sequences->seqs);
    }

    s = (Sequence_t *)ctx->sm->curseq->data;
    Sequence_copy(ctx, s, ctx->sm->next);

    Context_set(ctx);
  }
}


void
Context_last_sequence(Context_t *ctx)
{
  if (NULL != ctx->sm->curseq) {
    Sequence_t *s;

    ctx->sm->curseq = g_list_last(sequences->seqs);

    s = (Sequence_t *)ctx->sm->curseq->data;
    Sequence_copy(ctx, s, ctx->sm->next);

    Context_set(ctx);
  }
}


void
Context_random_sequence(Context_t *ctx)
{
  uint16_t rand = Shuffler_get(sequences->shuffler);
  VERBOSE(printf("[s] Random sequence: %d\n", rand));
  GList *tmp = g_list_nth(sequences->seqs, rand);
  assert(NULL != tmp);
  ctx->sm->curseq = tmp;
  Sequence_copy(ctx, tmp->data, ctx->sm->next);

  Context_set(ctx);
}


void
Context_set_sequence(Context_t *ctx, const uint64_t id)
{
  GList *seq = Sequences_find(id);

  if (NULL != seq) {
    Sequence_t *s = (Sequence_t *)seq->data;
    ctx->sm->curseq = seq;
    Sequence_copy(ctx, s, ctx->sm->next);

    Context_set(ctx);
  }
}


Buffer8_t *
active_buffer(const Context_t *ctx)
{
  return ctx->buffers[ACTIVE_BUFFER];
}


Buffer8_t *
passive_buffer(const Context_t *ctx)
{
  return ctx->buffers[PASSIVE_BUFFER];
}


#ifdef WITH_WEBCAM
void
Context_push_webcam(Context_t *ctx, Buffer8_t *buff, const int cam)
{
  int i;

  Buffer8_delete(ctx->cam_save[cam][CAM_SAVE-1]);
  for (i = CAM_SAVE-1; i >= 1; i--) {
    ctx->cam_save[cam][i] = ctx->cam_save[cam][i-1];
  }
  ctx->cam_save[cam][0] = buff;
}
#endif


enum ShufflerMode
Context_get_shuffler_mode(const enum RandomDelays what) {
  return random_modes[what];
}


void
Context_set_input_size(Context_t *ctx, const uint32_t input_size)
{
  ctx->input_size = input_size;
}


void
Context_set_volume_scale(Context_t *ctx, const double volume_scale)
{
  if (NULL != ctx->input) {
    ctx->input->volume_scale = volume_scale;
  }
}


double
Context_get_volume_scale(const Context_t *ctx)
{
  return (NULL != ctx->input) ? ctx->input->volume_scale : 0;
}


void
Context_mix_buffers(const Context_t *ctx, Buffer8_t *buffs[2])
{
  Pixel_t *d = buffs[0]->buffer;

  uint32_t rnd_offset = b_rand_uint32_range(0, BUFFSIZE-1);
  const Pixel_t *random = ctx->random->buffer + rnd_offset;

  uint32_t i = 0;
  for (; i < BUFFSIZE - rnd_offset; i++, d++, random++) {
    *d = buffs[*random]->buffer[i];
  }
  random = ctx->random->buffer;
  for (; i < BUFFSIZE; i++, d++, random++) {
    *d = buffs[*random]->buffer[i];
  }
}


void
Context_interleave_buffers(const Context_t *ctx)
{
  const Pixel_t *src = passive_buffer(ctx)->buffer;
  Pixel_t *dst = active_buffer(ctx)->buffer;

  for (uint16_t l = 0; l < HEIGHT; l += 2) {
    memcpy(dst, src, WIDTH * sizeof(Pixel_t));
    src += 2 * WIDTH * sizeof(Pixel_t);
    dst += 2 * WIDTH * sizeof(Pixel_t);
  }
}


json_t *
Context_output_plugins(const Context_t *ctx)
{
  if (NULL == ctx->outputs) {
    return json_null();
  } else {
    json_t *a = json_array();

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

      json_array_append_new(a, json_string(output->name));
    }

    return a;
  }
}


void
Context_push_video(Context_t *ctx, Buffer8_t *buff)
{
  Buffer8_t *first = ctx->video_save[0];

  for (uint8_t i = 0; i < CAM_SAVE-1; ++i) {
    ctx->video_save[i] = ctx->video_save[i+1];
  }
  Buffer8_copy(buff, first);
  ctx->video_save[CAM_SAVE-1] = first;
}


void
Context_push_command(Context_t *ctx, Command_t *cmd)
{
  assert((NULL != ctx->commands) && (NULL != cmd));
#ifdef DEBUG_COMMANDS_QUEUE
  xdebug("%s: cmd= %p, cmd->cmd= %d (%s)\n", __func__, cmd, cmd->cmd, command2str(cmd->cmd));
#endif
  g_async_queue_push(ctx->commands, cmd);
}
