/*
 *  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 "schemes.h"
#include "globals.h"
#include "brandom.h"
#include "colormaps.h"

// number of times to get a random sequence including the locked plugin
#define LOCKED_MAX_TRIES 1000 // max: uint16_t


static uint8_t
contains_at_least_one_BO_SCHEMES(const Sequence_t *s)
{
  GList *tmp = g_list_first(s->layers);

  while (NULL != tmp) {
    const Layer_t *l = (const Layer_t *)tmp->data;
    const Plugin_t *p = l->plugin;

    if ((*p->options & BO_SCHEMES) == BO_SCHEMES) {
      return 1;
    }
    tmp = g_list_next(tmp);
  }

  return 0;
}


static void
randomize_colormap(Context_t *ctx, const uint8_t disable_auto_modes,
                   const enum AutoMode auto_colormaps)
{
  Sequence_t *seq = ctx->sm->next;

  /* Use random colormap */
#ifdef DEBUG_LOCK
  printf("%s: random colormap, lock: %d\n", __func__, ctx->lock_colormap);
#endif
  assert(NULL != colormaps);
  if (colormaps->size > 1) {
    /* select random colormap and reinitialize timer */
    CmapFader_random(ctx->cf);
    seq->cmap_id = ctx->cf->dst->id;
    Alarm_init(ctx->a_cmaps);
    if (disable_auto_modes || !ctx->allow_auto_colormaps) {
      seq->auto_colormaps = 0;
    } else switch (auto_colormaps) {
      case AM_RANDOM:
        seq->auto_colormaps = b_rand_boolean();
        break;

      case AM_ENABLE:
        seq->auto_colormaps = 1;
        break;

      case AM_DISABLE:
        seq->auto_colormaps = 0;
        break;
      }
  } else {
    seq->cmap_id = colormaps->cmaps[0]->id;
    seq->auto_colormaps = 0;
  }
  ctx->auto_colormaps = seq->auto_colormaps;
}


static void
randomize_image(Context_t *ctx, const uint8_t disable_auto_modes,
                const enum AutoMode auto_images)
{
  Sequence_t *seq = ctx->sm->next;

  /* Use random image */
#ifdef DEBUG_LOCK
  printf("%s: random image, lock: %d\n", __func__, ctx->lock_image);
#endif
  if (NULL != images) {
    if (images->size > 1) {
      /* select random image and reinitialize timer */
      ImageFader_random(ctx->imgf);
      seq->image_id = ctx->imgf->dst->id;
      Alarm_init(ctx->a_images);
      if (disable_auto_modes || !ctx->allow_auto_images) {
        seq->auto_images = 0;
      } else switch (auto_images) {
        case AM_RANDOM:
          seq->auto_images = b_rand_boolean();
          break;

        case AM_ENABLE:
          seq->auto_images = 1;
          break;

        case AM_DISABLE:
          seq->auto_images = 0;
          break;
        }
    } else {
      seq->image_id = images->imgs[0]->id;
      seq->auto_images = 0;
    }
    ctx->auto_images = seq->auto_images;
  }
}


void
Schemes_random(Context_t *ctx, const uint8_t disable_auto_modes)
{
  Sequence_t *new = ctx->sm->next;
  uint16_t random;
  char ok;
  uint16_t try = 0;

  if ((NULL == schemes) || !schemes->size) {
    printf("[!] No schemes available, leaving unchanged\n");
    return;
  }

#ifdef DEBUG_AUTO_MODES
  xdebug("\n");
#endif
  do {
    int i;
    struct timeval t;

    ok = 1;
    if ((NULL != schemes->shuffler) && !Shuffler_ok(schemes->shuffler)) {
      printf("[!] Can not create a scheme, leaving unchanged\n");
      return;
    }

    gettimeofday(&t, NULL);
    Sequence_clear(new, t.tv_sec);

    random = (NULL != schemes->shuffler) ? Shuffler_get(schemes->shuffler) : 0;
    const Scheme_t *scheme = schemes->schemes[random];

    for (i = 0; ok && (i < scheme->size); i++) {
      /* check if we should insert a lens or a normal plugin */
      if (drand48() <= schemes->schemes[random]->items[i]->p) {
        const int res = Context_add_rand(new, schemes->schemes[random]->items[i]->options, ctx->locked);

        /* if ((res == -1) && (schemes->schemes[random][i].p == 1.0)) { */
        /* this is the correct way to check with floats: */
        if ((res == -1) && schemes->schemes[random]->items[i]->mandatory) {
          /* could not find a mandatory plugin */
          xdebug("[!] Marking scheme %d as invalid.\n", random);
          Shuffler_disable(schemes->shuffler, random);
          ok = 0;
        }
      }
    }

    if (ok) {
      if (NULL != ctx->locked) {
        try++;
#ifdef DEBUG_LOCKED_PLUGIN
        xdebug("=== Checking for locked plugin '%s' (try: %d)... ", ctx->locked->name, try);
#endif
        if (Sequence_find_position(new, ctx->locked) == -1) {
#ifdef DEBUG_LOCKED_PLUGIN
          xdebug("NOT FOUND... ");
#endif
          if (try == LOCKED_MAX_TRIES) {
#ifdef DEBUG_LOCKED_PLUGIN
            xdebug("max tries (%d) reached, giving up !\n", LOCKED_MAX_TRIES);
#endif
            } else {
#ifdef DEBUG_LOCKED_PLUGIN
            xdebug("Retrying...\n");
#endif
            ok = 0;
          }
        } else {
#ifdef DEBUG_LOCKED_PLUGIN
          xdebug("FOUND\n");
#endif
        }
      }
    }
    // randomize colormaps and images
    if (!ctx->lock_colormap) {
      randomize_colormap(ctx, disable_auto_modes, scheme->auto_colormaps);
#ifdef DEBUG_AUTO_MODES
      xdebug("Scheme %d random colormap: mode= %s, result: %d\n", random, Scheme_AutoMode2str(scheme->auto_colormaps), ctx->auto_colormaps);
#endif
    }
    if (!ctx->lock_image) {
      randomize_image(ctx, disable_auto_modes, scheme->auto_images);
#ifdef DEBUG_AUTO_MODES
      xdebug("Scheme %d random image: mode= %s, result: %d\n", random, Scheme_AutoMode2str(scheme->auto_images), ctx->auto_images);
#endif
    }
#ifdef DEBUG_AUTO_MODES
    xdebug("%s: ok= %d, contains a BO_SCHEMES= %d\n", __func__, ok, contains_at_least_one_BO_SCHEMES(new));
#endif
  } while (!ok || !contains_at_least_one_BO_SCHEMES(new));

  if (NULL != new->params3d) {
    json_decref(new->params3d);
  }
  // set or reset 3D rotations
  if (b_rand_boolean()) {
    Params3d_randomize(&ctx->params3d);
  } else {
    zero_3d(&ctx->params3d);
  }
  new->params3d = Params3d_to_json(&ctx->params3d);

  Context_set(ctx);
#ifdef DEBUG_AUTO_MODES
  xdebug("\n");
#endif
}
