/*
 *  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 "shuffler.h"
#include "shuffler_modes.h"
#include "brandom.h"


Shuffler_t *
Shuffler_new(const uint16_t size)
{
  Shuffler_t *s = xcalloc(1, sizeof(Shuffler_t));

  s->size = size;

  if (size) {
    s->used = xcalloc(size, sizeof(char));
    s->disabled = xcalloc(size, sizeof(char));
  }

  s->current = -1;
  s->mode = BS_SHUFFLE;

  return s;
}


void
Shuffler_delete(Shuffler_t *s)
{
  if ((NULL != s) && s->size) {
    xfree(s->used);
    xfree(s->disabled);
  }
  xfree(s);
}


void
Shuffler_verbose(Shuffler_t *s)
{
  s->verbose = 1;
}


static void
Shuffler_display(const Shuffler_t *s)
{
  uint16_t c;
  uint8_t one = 0;
  uint16_t n = 0;

  assert(NULL != s);
  if (!s->size) {
    return;
  }

  for (c = 0; c < s->size; c++)
    if (s->used[c]) {
      n++;
    }
  VERBOSE(printf("[S] Shuffler(%d): %d available: [", s->size, s->size - n));
  for (c = 0; c < s->size; c++) {
    if (!s->used[c] && !s->disabled[c]) {
      if (one) {
        VERBOSE(printf(", "));
      }
      VERBOSE(printf("%d", c));
      one = 1;
    }
  }
  VERBOSE(printf("]\n"));

  n = 0;
  for (c = 0; c < s->size; c++)
    if (s->disabled[c]) {
      n++;
    }
  one = 0;
  VERBOSE(printf("[S] Shuffler(%d): %d disabled: [", s->size, n));
  for (c = 0; c < s->size; c++) {
    if (s->disabled[c]) {
      if (one) {
        VERBOSE(printf(", "));
      }
      VERBOSE(printf("%d", c));
      one = 1;
    }
  }
  VERBOSE(printf("]\n"));
}


static uint16_t
Shuffler_next_available(Shuffler_t *s, uint16_t start, uint8_t want_unused)
{
  if (start == s->size) {
    start = 0;
  }

  while (s->disabled[start] || (want_unused && s->used[start])) {
    if (++start == s->size) {
      start = 0;
    }
  }

  s->current = start;
  s->used[start] = 1;

  return start;
}


static uint16_t
Shuffler_get_shuffle(Shuffler_t *s)
{
  uint32_t rnd = b_rand_uint32_range(0, s->size - 1);

  return Shuffler_next_available(s, rnd, 1);
}


static uint16_t
Shuffler_get_cycle(Shuffler_t *s)
{
  return Shuffler_next_available(s, s->current+1, 1);
}


static uint16_t
Shuffler_get_random(Shuffler_t *s)
{
  uint32_t rnd = b_rand_uint32_range(0, s->size);

  return Shuffler_next_available(s, rnd, 0);
}


static void
Shuffler_clean(Shuffler_t *s)
{
  uint16_t c;

  assert(NULL != s);
  assert(s->size);

  for (c = 0; (c < s->size) && (s->used[c] || s->disabled[c]); c++);

  if (c == s->size) {
    if (s->verbose) {
      VERBOSE(printf("[S] Shuffler_clean\n"));
    }
    memset((void *)s->used, 0, s->size*sizeof(char));
  }
}


uint16_t
Shuffler_get(Shuffler_t *s)
{
  uint16_t new = 0;

  if ((NULL == s) || (s->size == 1)) {
    return 0;
  }

  if (!s->size) {
    xerror("Attempt to get a value from an empty shuffler\n");
  }

  if (s->verbose) {
    Shuffler_display(s);
  }

  switch (s->mode) {
    case BS_SHUFFLE:
      new = Shuffler_get_shuffle(s);
      break;

    case BS_CYCLE:
      new = Shuffler_get_cycle(s);
      break;

    case BS_RANDOM:
      new = Shuffler_get_random(s);
      break;

    default:
      xerror("Bad shuffler mode %d\n", s->mode);
      break;
  }

  if (s->verbose) {
    VERBOSE(printf("[S] Shuffler (mode: %s) result= %d\n", Shuffler_mode2str(s->mode), new));
  }
  Shuffler_clean(s);

  return new;
}


void
Shuffler_set_mode(Shuffler_t *s, const enum ShufflerMode mode)
{
  assert(NULL != s);

  s->mode = mode;
}


void
Shuffler_next_mode(Shuffler_t *s)
{
  assert(NULL != s);

  s->mode = (enum ShufflerMode)((s->mode+1) & 2);
  /* TODO human printable shuffler mode (None, Shuffle, ...) */
  VERBOSE(printf("[S] Shuffler mode set to: %d\n", (int)s->mode));
}


void
Shuffler_enable(Shuffler_t *s, const uint16_t i)
{
  assert(NULL != s);
  assert(s->size);
  assert(i < s->size);

  s->disabled[i] = 0;
}


void
Shuffler_disable(Shuffler_t *s, const uint16_t i)
{
  assert(NULL != s);
  assert(s->size);
  assert(i < s->size);

  s->disabled[i] = 1;
}


void
Shuffler_used(Shuffler_t *s, const uint16_t i)
{
  assert(NULL != s);
  assert(s->size);
  assert(i < s->size);

  s->used[i] = 1;
  Shuffler_clean(s);
}


void
Shuffler_restart(Shuffler_t *s)
{
  assert(NULL != s);
  assert(s->size);

  memset((void *)s->used, 0, s->size*sizeof(char));
  s->current = -1;
}


void
Shuffler_reinit(Shuffler_t *s)
{
  Shuffler_restart(s);
  memset((void *)s->disabled, 0, s->size*sizeof(char));
}


void
Shuffler_grow_one_left(Shuffler_t *s)
{
  /*
   * do a realloc but on the left of the array
   * this one is needed since we preprend sequences
   * to the list
   */

  char *nused = NULL;
  char *ndisabled = NULL;
  uint16_t new_size;

  assert(NULL != s);
  new_size = s->size + 1;

  nused = xcalloc(new_size, sizeof(char));
  ndisabled = xcalloc(new_size, sizeof(char));

  memcpy((void *)(nused+sizeof(char)), s->used, s->size*sizeof(char));
  memcpy((void *)(ndisabled+sizeof(char)), s->disabled, s->size*sizeof(char));

  s->size = new_size;

  xfree(s->used);
  xfree(s->disabled);

  s->used = nused;
  s->disabled = ndisabled;
}


uint8_t
Shuffler_ok(const Shuffler_t *s)
{
  uint16_t c = 0;

  assert(NULL != s);
  assert(s->size);

  while ((c < s->size) && s->disabled[c]) {
    c++;
  }

  return (c != s->size);
}


enum ShufflerMode
Shuffler_parse_mode(const char *opt) {
  if (NULL != opt)
  {
    if (!strcasecmp(opt, shuffler_modes[BS_SHUFFLE])) {
      return BS_SHUFFLE;
    } else if (!strcasecmp(opt, shuffler_modes[BS_CYCLE])) {
      return BS_CYCLE;
    } else if (!strcasecmp(opt, shuffler_modes[BS_RANDOM])) {
      return BS_RANDOM;
    }
  }
  fprintf(stderr, "[!] Failed to parse shuffler mode '%s'\n", opt);

  return BS_SHUFFLE;
}


const char *
Shuffler_mode2str(const enum ShufflerMode mode)
{
  switch (mode) {
    case BS_SHUFFLE:
      return "shuffle";

    case BS_CYCLE:
      return "cycle";

    case BS_RANDOM:
      return "random";

    default:
      return NULL;
  }
}


uint8_t
Shuffler_is_done(const Shuffler_t *s)
{
  for (uint16_t i = 0; i < s->size; i++) {
    if (!s->used[i]) {
      return 0;
    }
  }

  return 1;
}
