/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames 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.
 * VgaGames 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 VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include "random.h"

void init_random(void);
void dest_random(void);

static unsigned int random_get(const char *, unsigned int, unsigned int);
static void random_remove(const char *);


/* set functions */
void
init_random(void)
{
  vg4data.lists.random.s = SML3_calloc(1, sizeof(*vg4data.lists.random.s));
  vg4data.lists.random.s->hs = SML3_hashnew(NULL, 0);
  vg4->random->get = random_get;
  vg4->random->remove = random_remove;
} /* Ende init_random */

void
dest_random(void)
{
  SML3_hashfree(&vg4data.lists.random.s->hs);
  free(vg4data.lists.random.s);
  vg4data.lists.random.s = NULL;
} /* Ende dest_random */


/* random_get:
 * return (network-suitable) random number between nstart and nend (inclusive)
 * @param scope   scope of random numbers
 * @param nstart  starting number
 * @param nend    ending number
 * @return  random number
 */
static unsigned int
random_get(const char *scope, unsigned int nstart, unsigned int nend)
{
  struct SML3_hashelem *heptr;
  struct vgi_random_entry *pval;
  struct vgi_random *sptr;
  size_t scoplen;

  if (nstart > nend) { return 0; }
  if (nstart == nend) { return nstart; }

  sptr = vg4data.lists.random.s;

  if (scope != NULL) { scoplen = strlen(scope); } else { scoplen = 0; }
  heptr = SML3_hashget(sptr->hs, scope, scoplen);

  if (heptr == NULL) {  /* create entry */
    struct vgi_random_entry bval;
    if (vg4data.lists.nw.nw_seed == 0) {  /* no network (yet) */
      struct timeval tv;
      gettimeofday(&tv, NULL);
      bval.seed = (unsigned int)tv.tv_usec;
      bval.nw_seed = 0;
    } else {  /* network seed */
      bval.nw_seed = bval.seed = vg4data.lists.nw.nw_seed;
    }
    bval.modifier = 0;
    if (scoplen > 0) {
      void *cksum;
      char cres[128 + 1];
      cksum = SML3_cksum_init(SML3_CKSUM_DIGEST_HORNER9);
      SML3_cksum_add(cksum, scope, scoplen);
      SML3_cksum_result(cksum, cres, sizeof(cres));
      bval.modifier = (unsigned int)atoi(cres);
    }
    bval.seed += bval.modifier;
    heptr = SML3_hashset(sptr->hs, scope, scoplen);
    SML3_hashelem_valset(heptr, &bval, sizeof(bval));
  }

  pval = SML3_hashelem_valget(heptr, NULL);

  if (vg4data.lists.nw.nw_seed > 0 && pval->nw_seed != vg4data.lists.nw.nw_seed) {
    /* network set and scope still uses no-network-seed or has old network-seed */
    pval->nw_seed = pval->seed = vg4data.lists.nw.nw_seed;
    pval->seed += pval->modifier;
  }

  /* Xorshift */
  pval->seed ^= (pval->seed << 13);
  pval->seed ^= (pval->seed >> 17);
  pval->seed ^= (pval->seed << 5);

  return (nstart + (pval->seed % (nend - nstart + 1)));
} /* Ende random_get */


/* random_remove:
 * remove entry for scope to save memory,
 * (it is no harm if scope is NULL or was already removed)
 * @param scope   scope of random numbers
 */
static void
random_remove(const char *scope)
{
  struct SML3_hashelem *heptr;
  struct vgi_random *sptr;
  size_t scoplen;

  sptr = vg4data.lists.random.s;

  if (scope != NULL) { scoplen = strlen(scope); } else { scoplen = 0; }
  heptr = SML3_hashget(sptr->hs, scope, scoplen);
  if (heptr != NULL) { SML3_hashdel(heptr); }
} /* Ende random_remove */
