/* 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 <unistd.h>
#include <errno.h>
#include "hash.h"

void init_hash(void);
void dest_hash(void);

struct VG_Hash * vg4_hash_create_nolist(void);
struct VG_Hash * vg4_hash_clone_nolist(struct VG_Hash *);

static struct VG_Hash * hash_create(void);
static struct VG_Hash * hash_create_doit(VG_BOOL);
static struct VG_Hash * hash_clone(struct VG_Hash *);
static struct VG_Hash * hash_clone_doit(struct VG_Hash *, VG_BOOL);
static void hash_destroy(struct VG_Hash *);
static void hash_destroyall(void);
static const char * hash_list(struct VG_Hash *, void **);
static void hash_clear(struct VG_Hash *);
static VG_BOOL hash_is_empty(struct VG_Hash *);
static void hash_set(struct VG_Hash *, const char *, const void *, size_t);
static void hash_setstr(struct VG_Hash *, const char *, const char *);
static void hash_setint(struct VG_Hash *, const char *, int);
static VG_BOOL hash_isset(struct VG_Hash *, const char *);
static VG_BOOL hash_isnum(struct VG_Hash *, const char *);
static void * hash_get(struct VG_Hash *, const char *, size_t *);
static int hash_getint(struct VG_Hash *, const char *);
static void hash_insert(struct VG_Hash *, struct VG_Hash *, VG_BOOL);
static void * hash_remove(struct VG_Hash *, const char *);
static void hash_dump(FILE *);


/* set functions */
void
init_hash(void)
{
  vg4data.lists.hash = SML3_calloc(1, sizeof(*vg4data.lists.hash));  /* first list-element is unused */
  vg4data.lists.hash->prev = vg4data.lists.hash->next = NULL;
  vg4data.lists.hash->hs = NULL;
  vg4->hash->create = hash_create;
  vg4->hash->clone = hash_clone;
  vg4->hash->destroy = hash_destroy;
  vg4->hash->destroyall = hash_destroyall;
  vg4->hash->list = hash_list;
  vg4->hash->clear = hash_clear;
  vg4->hash->is_empty = hash_is_empty;
  vg4->hash->set = hash_set;
  vg4->hash->setstr = hash_setstr;
  vg4->hash->setint = hash_setint;
  vg4->hash->isset = hash_isset;
  vg4->hash->isnum = hash_isnum;
  vg4->hash->get = hash_get;
  vg4->hash->getint = hash_getint;
  vg4->hash->insert = hash_insert;
  vg4->hash->remove = hash_remove;
  vg4->hash->dump = hash_dump;
} /* Ende init_hash */

void
dest_hash(void)
{
  vg4->hash->destroyall();
  free(vg4data.lists.hash);  /* first list-element is unused */
  vg4data.lists.hash = NULL;
} /* Ende dest_hash */


/* vg4_hash_create_nolist: like hash_create(), but without inserting into list */
struct VG_Hash *
vg4_hash_create_nolist(void)
{
  return hash_create_doit(VG_FALSE);
} /* Ende vg4_hash_create_nolist */


/* vg4_hash_clone_nolist: like hash_clone(), but without inserting into list */
struct VG_Hash *
vg4_hash_clone_nolist(struct VG_Hash *hsh)
{
  return hash_clone_doit(hsh, VG_FALSE);
} /* Ende vg4_hash_clone_nolist */


/* hash_create:
 * create hash
 * @return  created hash
 */
static struct VG_Hash *
hash_create(void)
{
  return hash_create_doit(VG_TRUE);
} /* Ende hash_create */


/* hash_create_doit: do action */
static struct VG_Hash *
hash_create_doit(VG_BOOL intolist)
{
  struct VG_Hash *hsh, **hpp;

  hsh = SML3_calloc(1, sizeof(*hsh));
  hsh->prev = hsh->next = NULL;
  hsh->hs = SML3_hashnew(NULL, 0);

  if (intolist) {
    for (hpp = &vg4data.lists.hash; *hpp != NULL; hpp = &(*hpp)->next) { hsh->prev = *hpp; }
    *hpp = hsh;
  }

  return hsh;
} /* Ende hash_create_doit */


/* hash_clone:
 * clone hash
 * @param hsh  hash
 * @return  cloned hash
 */
static struct VG_Hash *
hash_clone(struct VG_Hash *hsh)
{
  return hash_clone_doit(hsh, VG_TRUE);
} /* Ende hash_clone */


/* hash_clone_doit: do action */
static struct VG_Hash *
hash_clone_doit(struct VG_Hash *hsh, VG_BOOL intolist)
{
  struct VG_Hash *hsc, **hpp;

  hsc = SML3_calloc(1, sizeof(*hsc));
  hsc->prev = hsc->next = NULL;

  if (hsh != NULL && hsh->hs != NULL) {
    hsc->hs = SML3_hashcopy(hsh->hs, NULL);
  } else {
    hsc->hs = SML3_hashnew(NULL, 0);
  }

  if (intolist) {
    for (hpp = &vg4data.lists.hash; *hpp != NULL; hpp = &(*hpp)->next) { hsc->prev = *hpp; }
    *hpp = hsc;
  }

  return hsc;
} /* Ende hash_clone_doit */


/* hash_destroy:
 * destroy hash
 * @param hsh  hash
 */
static void
hash_destroy(struct VG_Hash *hsh)
{
  if (hsh == NULL) { return; }

  if (hsh->hs != NULL) { SML3_hashfree(&hsh->hs); }
  if (hsh->prev != NULL) { hsh->prev->next = hsh->next; }
  if (hsh->next != NULL) { hsh->next->prev = hsh->prev; }

  free(hsh);
} /* Ende hash_destroy */


/* hash_destroyall:
 * destroy all hashes
 */
static void
hash_destroyall(void)
{
  struct VG_Hash *hp0, *hp1;

  if (vg4data.lists.hash == NULL) { return; }

  for (hp0 = vg4data.lists.hash->next; hp0 != NULL; hp0 = hp1) {
    hp1 = hp0->next;
    if (hp0->hs != NULL) { SML3_hashfree(&hp0->hs); }
    free(hp0);
  }
  vg4data.lists.hash->prev = vg4data.lists.hash->next = NULL;
} /* Ende hash_destroyall */


/* hash_list:
 * return key of next entry from hash
 * @param hsh    hash
 * @param vpos   for returning actual position
 * @return       next key
 *
 * The position vpos must be NULL at the first call,
 * end of list is, when vpos is NULL again after calling hash_list()
 *
 * E.g.:
 *   struct VG_Hash *hsh = vg4->hash->create();
 *   void *vpos;
 *   const char *key;
 *   [...]
 *   vpos = NULL;
 *   for (key = vg4->hash->list(hsh, &vpos); vpos != NULL; key = vg4->hash->list(hsh, &vpos)) {
 *     printf("- %s\n", key);
 *     vpos = vg4->hash->remove(hsh, key);  // remove entry
 *   }
 */
static const char *
hash_list(struct VG_Hash *hsh, void **vpos)
{
  struct SML3_hashelem *he1;

  if (vpos == NULL) { return NULL; }
  if (hsh == NULL || hsh->hs == NULL) { *vpos = NULL; return NULL; }

  he1 = (struct SML3_hashelem *)*vpos;

  he1 = SML3_hashlist(hsh->hs, he1, 0);
  *vpos = he1;
  if (he1 == NULL) { return NULL; }

  return (const char *)SML3_hashelem_keyget(he1, NULL);
} /* Ende hash_list */


/* hash_clear:
 * empty hash
 * @param hsh  hash
 */
static void
hash_clear(struct VG_Hash *hsh)
{
  if (hsh == NULL || hsh->hs == NULL) { return; }
  SML3_hashclear(hsh->hs);
} /* Ende hash_clear */


/* hash_is_empty:
 * return whether hash is empty
 * @param hsh  hash
 * @return  VG_TRUE = is empty, VG_FALSE = is not empty
 */
static VG_BOOL
hash_is_empty(struct VG_Hash *hsh)
{
  if (hsh == NULL || hsh->hs == NULL) { return VG_TRUE; }
  if (SML3_hashlist(hsh->hs, NULL, 0) == NULL) { return VG_TRUE; }
  return VG_FALSE;
} /* Ende hash_is_empty */


/* hash_set:
 * insert entry into hash
 * @param hsh   hash
 * @param key   key
 * @param vval  value (may be NULL)
 * @param sval  sizeof value
 */
static void
hash_set(struct VG_Hash *hsh, const char *key, const void *vval, size_t sval)
{
  struct SML3_hashelem *he1;

  if (hsh == NULL || hsh->hs == NULL) { return; }
  if (key == NULL || *key == '\0') { return; }
  if (vval == NULL) {
    sval = 0;
  } else if (sval == 0) {
    sval = strlen(vval) + 1;
  }

  he1 = SML3_hashset(hsh->hs, key, strlen(key) + 1);
  SML3_hashelem_valset(he1, vval, sval);
} /* Ende hash_set */


/* hash_setstr:
 * insert entry with string-value into hash
 * @param hsh   hash
 * @param key   key
 * @param val   string-value
 */
static void
hash_setstr(struct VG_Hash *hsh, const char *key, const char *val)
{
  hash_set(hsh, key, val, 0);
} /* Ende hash_setstr */


/* hash_setint:
 * insert entry with integer-value into hash, (may also used with boolean-value)
 * @param hsh  hash
 * @param key  key
 * @param val  integer value
 */
static void
hash_setint(struct VG_Hash *hsh, const char *key, int val)
{
  struct SML3_hashelem *he1;

  if (hsh == NULL || hsh->hs == NULL) { return; }
  if (key == NULL || *key == '\0') { return; }

  he1 = SML3_hashset(hsh->hs, key, strlen(key) + 1);
  SML3_hashelem_valset(he1, (const void *)(size_t)val, 0);
} /* Ende hash_setint */


/* hash_isset:
 * return whether hash contains the key
 * @param hsh  hash
 * @param key  key
 * @return     VG_TRUE = yes or VG_FALSE = no
 */
static VG_BOOL
hash_isset(struct VG_Hash *hsh, const char *key)
{
  struct SML3_hashelem *he1;

  if (hsh == NULL || hsh->hs == NULL) { return VG_FALSE; }
  if (key == NULL || *key == '\0') { return VG_FALSE; }

  he1 = SML3_hashget(hsh->hs, key, strlen(key) + 1);
  if (he1 == NULL) { return VG_FALSE; }
  return VG_TRUE;
} /* Ende hash_isset */


/* hash_isnum:
 * return whether key-entry of hash contains an integer-value
 * @param hsh  hash
 * @param key  key
 * @return     VG_TRUE = yes or VG_FALSE = no
 */
static VG_BOOL
hash_isnum(struct VG_Hash *hsh, const char *key)
{
  struct SML3_hashelem *he1;
  size_t sval;

  if (hsh == NULL || hsh->hs == NULL) { return VG_FALSE; }
  if (key == NULL || *key == '\0') { return VG_FALSE; }

  he1 = SML3_hashget(hsh->hs, key, strlen(key) + 1);
  if (he1 == NULL) { return VG_FALSE; }

  SML3_hashelem_valget(he1, &sval);
  if (sval == 0) { return VG_TRUE; }

  return VG_FALSE;
} /* Ende hash_isnum */


/* hash_get:
 * return value of key-entry from hash
 * @param hsh   hash
 * @param key   key
 * @param sval  for returning sizeof value, if not NULL
 * @return      pointer to value, (may be NULL)
 */
static void *
hash_get(struct VG_Hash *hsh, const char *key, size_t *sval)
{
  struct SML3_hashelem *he1;

  if (sval != NULL) { *sval = 0; }
  if (hsh == NULL || hsh->hs == NULL) { return NULL; }
  if (key == NULL || *key == '\0') { return NULL; }

  he1 = SML3_hashget(hsh->hs, key, strlen(key) + 1);
  if (he1 == NULL) { return NULL; }

  return SML3_hashelem_valget(he1, sval);
} /* Ende hash_get */


/* hash_getint:
 * return integer-value of key-entry from hash, (may also used with boolean-value)
 * @param hsh   hash
 * @param key   key
 * @return      integer-value
 */
static int
hash_getint(struct VG_Hash *hsh, const char *key)
{
  struct SML3_hashelem *he1;

  if (hsh == NULL || hsh->hs == NULL) { return 0; }
  if (key == NULL || *key == '\0') { return 0; }

  he1 = SML3_hashget(hsh->hs, key, strlen(key) + 1);
  if (he1 == NULL) { return 0; }

  return (int)(size_t)SML3_hashelem_valget(he1, NULL);
} /* Ende hash_getint */


/* hash_insert:
 * insert a hash into another hash
 * @param hsh_dest    destination-hash
 * @param hsh_source  source-hash
 * @param overwrite   whether overwrite existing entries in destination-hash
 */
static void
hash_insert(struct VG_Hash *hsh_dest, struct VG_Hash *hsh_source, VG_BOOL overwrite)
{
  struct SML3_hashelem *he_dest, *he_source;
  size_t slen;
  void *vptr;

  if (hsh_dest == NULL || hsh_dest->hs == NULL) { return; }
  if (hsh_source == NULL || hsh_source->hs == NULL) { return; }

  for (he_source = SML3_hashlist(hsh_source->hs, NULL, 0); he_source != NULL; he_source = SML3_hashlist(hsh_source->hs, he_source, 0)) {
    vptr = (void *)SML3_hashelem_keyget(he_source, &slen);
    he_dest = SML3_hashget(hsh_dest->hs, vptr, slen);
    if (he_dest == NULL || overwrite) {
      he_dest = SML3_hashset(hsh_dest->hs, vptr, slen);
      vptr = SML3_hashelem_valget(he_source, &slen);
      SML3_hashelem_valset(he_dest, vptr, slen);
    }
  }
} /* Ende hash_insert */


/* hash_remove:
 * remove entry from hash
 * @param hsh   hash
 * @param key   key
 * @return      previous position (needed by hash_list)
 */
static void *
hash_remove(struct VG_Hash *hsh, const char *key)
{
  struct SML3_hashelem *he1;

  if (hsh == NULL || hsh->hs == NULL) { return NULL; }
  if (key == NULL || *key == '\0') { return NULL; }

  he1 = SML3_hashget(hsh->hs, key, strlen(key) + 1);
  if (he1 == NULL) { return NULL; }

  he1 = SML3_hashdel(he1);
  return he1;
} /* Ende hash_remove */


/* hash_dump:
 * dump hash entries
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
static void
hash_dump(FILE *outfp)
{
  struct VG_Hash *hsh;
  void *vpos, *vval;
  const char *key;
  struct SML3_hashelem *he1;
  size_t sval;

  if (outfp == NULL) { outfp = stdout; }

  fprintf(outfp, "\nDump of hash entries\n");
  fprintf(outfp, "====================\n\n");

  if (vg4data.lists.hash == NULL) { return; }

  for (hsh = vg4data.lists.hash->next; hsh != NULL; hsh = hsh->next) {
    vpos = NULL;
    for (key = vg4->hash->list(hsh, &vpos); vpos != NULL; key = vg4->hash->list(hsh, &vpos)) {
      he1 = SML3_hashget(hsh->hs, key, strlen(key) + 1);
      vval = SML3_hashelem_valget(he1, &sval);
      fprintf(outfp, "- %s: pointer=%p, size=%u\n", key, vval, (unsigned int)sval);
    }
  }
} /* Ende hash_dump */
