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

void init_object(void);
void dest_object(void);

static struct VG_Object * object_create(const char *, int, unsigned int, int, void *);
static void object_destroy(void *, unsigned int);
static void object_destroy_objid(void *, const char *, int);
static void object_destroy_except(void *, ...);
static void object_destroyall(void);
static int object_list(const char *, int, unsigned int **);
static VG_BOOL object_has_instances(const char *, int);
static void object_dump(FILE *);

static void free_list(void *);
static int cmp_list(const void *, const void *);


/* set functions */
void
init_object(void)
{
  vg4data.lists.object.i = SML3_calloc(1, sizeof(*vg4data.lists.object.i));
  vg4data.lists.object.i->id_start = 0;
  vg4data.lists.object.i->hiid = SML3_hashnew(NULL, 0);
  vg4data.lists.object.list = SML3_calloc(1, sizeof(*vg4data.lists.object.list));  /* first list-element is unused */
  vg4data.lists.object.list->prev = vg4data.lists.object.list->next = NULL;
  vg4data.lists.object.hmgmt = SML3_hashnew(NULL, 0);
  vg4data.lists.object.hcoll = SML3_hashnew(NULL, 0);
  vg4->object->create = object_create;
  vg4->object->destroy = object_destroy;
  vg4->object->destroy_objid = object_destroy_objid;
  vg4->object->destroy_except = object_destroy_except;
  vg4->object->destroyall = object_destroyall;
  vg4->object->list = object_list;
  vg4->object->has_instances = object_has_instances;
  vg4->object->dump = object_dump;
  init_object_instance();
  init_object_mgmt();
  init_object_call();
  init_object_collision();
} /* Ende init_object */

void
dest_object(void)
{
  vg4->object->collision_destroyall();
  vg4->object->mgmt_destroyall();
  vg4->object->destroyall();
  free(vg4data.lists.object.list);  /* first list-element is unused */
  vg4data.lists.object.list = NULL;
  SML3_hashfree(&vg4data.lists.object.i->hiid);
  free(vg4data.lists.object.i);
  vg4data.lists.object.i = NULL;
} /* Ende dest_object */


/* free-function for object_list() */
static void
free_list(void *vptr)
{
  if (vptr != NULL) { free(vptr); }
} /* Ende free_list */


/* sort-compare-function for object_list() */
static int
cmp_list(const void *v1, const void *v2)
{
  struct SML3_hashelem *he1 = *(struct SML3_hashelem **)v1;
  struct SML3_hashelem *he2 = *(struct SML3_hashelem **)v2;
  unsigned int drawlevel1 = (unsigned int)(size_t)SML3_hashelem_keyget(he1, NULL);
  unsigned int drawlevel2 = (unsigned int)(size_t)SML3_hashelem_keyget(he2, NULL);
  if (drawlevel1 < drawlevel2) { return -1; }
  if (drawlevel1 > drawlevel2) { return 1; }
  return 0;
} /* Ende cmp_list */


/* object_create:
 * create an object-instance
 * @param objid              object-ID (e.g. name of object)
 * @param subid              arbitrary sub-ID for object-ID, or 0 = no sub-ID
 * @param parent_instanceid  instance-ID of parent, or 0 = irrelevant
 * @param drawlevel          level-number for drawing order (begins with 1 = lowest level)
 * @param opriv              private struct for object-instance
 * @return  object
 */
static struct VG_Object *
object_create(const char *objid, int subid, unsigned int parent_instanceid, int drawlevel, void *opriv)
{
  struct vgi_objlist *olp, **olpp;

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

  if (drawlevel < 1) { drawlevel = 1; }
  if (objid == NULL || *objid == '\0') { objid = VGI_OBJID_NONE; }
  vg4->misc->strcpy(olp->obje.objid, sizeof(olp->obje.objid), objid);
  olp->obje.subid = subid;
  olp->obje.drawlevel = drawlevel;
  vg4->object->instance_create(&olp->obje);
  olp->obje.parent_instanceid = parent_instanceid;
  olp->obje.opriv = opriv;

  for (olpp = &vg4data.lists.object.list; *olpp != NULL; olpp = &(*olpp)->next) { olp->prev = *olpp; }
  *olpp = olp;

  return &olp->obje;
} /* Ende object_create */


/* object_destroy:
 * destroy an object-instance (will also be removed from all collision-tags)
 * @param vgame       private structure of the game, or NULL
 * @param instanceid  instance-ID
 */
static void
object_destroy(void *vgame, unsigned int instanceid)
{
  struct vgi_objlist *olp;
  struct VG_Object *objp;

  if ((objp = vg4->object->instance_getobj(instanceid)) == NULL) { return; }

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

  for (olp = vg4data.lists.object.list->next; olp != NULL; olp = olp->next) {
    if (objp == &olp->obje) { break; }
  }
  if (olp == NULL) { return; }

  if (olp->prev != NULL) { olp->prev->next = olp->next; }
  if (olp->next != NULL) { olp->next->prev = olp->prev; }

  /* call parent's f_childexit()? */
  if (olp->obje.parent_instanceid != 0) {
    if ((objp = vg4->object->instance_getobj(olp->obje.parent_instanceid)) != NULL) {
      if (objp->f_childexit != NULL) {
        objp->f_childexit(vgame, objp, &olp->obje);
      }
    }
  }

  if (olp->obje.f_free != NULL) { olp->obje.f_free(vgame, olp->obje.opriv); }
  vg4_collision_removeid(olp->obje.instanceid);
  vg4->object->instance_destroy(olp->obje.instanceid);
  free(olp);
} /* Ende object_destroy */ 


/* object_destroy_objid:
 * destroy object-instances of an object-ID or all object-instances
 * (will also be removed from all collision-tags)
 * @param vgame  private structure of the game, or NULL
 * @param objid  object-ID, or NULL = all object-instances
 * @param subid  sub-ID for object-ID, or 0 = no sub-ID
 */
static void
object_destroy_objid(void *vgame, const char *objid, int subid)
{ 
  unsigned int *idlist;
  int ianz, ipos;
  struct VG_Object *objp;

  ianz = vg4->object->list(NULL, 0, &idlist);
  for (ipos = 0; ipos < ianz; ipos++) {
    objp = vg4->object->instance_getobj(idlist[ipos]);
    if (objp != NULL) {
      if (objid == NULL || (strcmp(objid, objp->objid) == 0 && (subid == 0 || subid == objp->subid))) {
        vg4->object->destroy(vgame, idlist[ipos]);
      }
    }
  }
  if (idlist != NULL) { free(idlist); }
} /* Ende object_destroy_objid */


/* object_destroy_except:
 * destroy object-instances of all object-IDs except the passed ones
 * (will also be removed from all collision-tags)
 * @param vgame  private structure of the game, or NULL
 * @param ...    further parameters are exceptions: object-IDs, which shall not be destroyed,
 *               last parameter must be NULL
 */
static void
object_destroy_except(void *vgame, ...)
{ 
  unsigned int *idlist;
  int ianz, ipos;
  struct VG_Object *objp;
  struct VG_Hash *hsexp;
  va_list ap;
  const char *pexp;

  hsexp = vg4->hash->create();

  /* get exceptions */
  va_start(ap, vgame);
  for (;;) {
    pexp = (const char *)va_arg(ap, const char *);
    if (pexp == NULL) { break; }
    vg4->hash->set(hsexp, pexp, NULL, 0);
  }
  va_end(ap);

  /* destroy instances except exceptions */
  ianz = vg4->object->list(NULL, 0, &idlist);
  for (ipos = 0; ipos < ianz; ipos++) {
    objp = vg4->object->instance_getobj(idlist[ipos]);
    if (objp != NULL) {
      if (!vg4->hash->isset(hsexp, objp->objid)) {
        vg4->object->destroy(vgame, idlist[ipos]);
      }
    }
  }
  if (idlist != NULL) { free(idlist); }

  vg4->hash->destroy(hsexp);
} /* Ende object_destroy_except */


/* object_destroyall:
 * destroy all object-instances (without calling any f_childexit())
 * (will also be removed from all collision-tags)
 */
static void
object_destroyall(void)
{
  struct vgi_objlist *olp0, *olp1;

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

  for (olp0 = vg4data.lists.object.list->next; olp0 != NULL; olp0 = olp1) {
    if (olp0->prev != NULL) { olp0->prev->next = olp0->next; }
    if (olp0->next != NULL) { olp0->next->prev = olp0->prev; }
    olp1 = olp0->next; 
    if (olp0->obje.f_free != NULL) { olp0->obje.f_free(NULL, olp0->obje.opriv); }
    vg4_collision_removeid(olp0->obje.instanceid);
    vg4->object->instance_destroy(olp0->obje.instanceid);
    free(olp0);
  }
  vg4data.lists.object.list->prev = vg4data.lists.object.list->next = NULL;
} /* Ende object_destroyall */ 


/* object_list:
 * return a list of instance-IDs for an object-ID (or all)
 * sorted by drawlevel (lower to upper)
 * @param objid      object-ID, or NULL = all object-instances
 * @param subid      sub-ID for object-ID, or 0 = no sub-ID
 * @param idlist     for returning list of instance-IDs, must be freed with free() (if not NULL),
 *                   (may be NULL for just returning number of instance-IDs)
 * @return  number of instance-IDs in the list
 */
static int
object_list(const char *objid, int subid, unsigned int **idlist)
{
  struct vgi_objlist *olp;
  struct SML3_hash *hid;
  struct SML3_hashelem *he1;
  unsigned int *idf;
  int ipos, ianz;

  if (idlist != NULL) { *idlist = NULL; }

  if (vg4data.lists.object.list == NULL) { return 0; }

  hid = SML3_hashnew(free_list, 0);

  /* set objects according to their drawlevel into hid (key = drawlevel) */
  for (olp = vg4data.lists.object.list->next; olp != NULL; olp = olp->next) {
    if (objid != NULL && (strcmp(objid, olp->obje.objid) != 0 || (subid != 0 && subid != olp->obje.subid))) { continue; }
    he1 = SML3_hashset(hid, (const void *)(size_t)olp->obje.drawlevel, 0);
    idf = (unsigned int *)SML3_hashelem_valget(he1, NULL);
    if (idf == NULL) {
      ipos = 1;
      idf = SML3_malloc(sizeof(*idf) * (ipos + 1));
      idf[0] = ipos;
    } else {
      ipos = (int)idf[0] + 1;
      idf = SML3_realloc(idf, sizeof(*idf) * (ipos + 1));
      idf[0] = ipos;
    }
    idf[ipos] = olp->obje.instanceid;
    SML3_hashelem_valset(he1, idf, 0);
  }

  /* sort hid, drawlevel ascending */
  ianz = 0;
  SML3_hashsort(hid, cmp_list, 0);
  for (he1 = SML3_hashsort_first(hid); he1 != NULL; he1 = SML3_hashsort_next(he1)) {
    idf = (unsigned int *)SML3_hashelem_valget(he1, NULL);
    ipos = (int)idf[0];
    if (idlist != NULL) {
      if (*idlist == NULL) {
        *idlist = SML3_malloc(sizeof(**idlist) * (ianz + ipos));
      } else {
        *idlist = SML3_realloc(*idlist, sizeof(**idlist) * (ianz + ipos));
      }
      memcpy(&(*idlist)[ianz], &idf[1], sizeof(*idf) * ipos);
    }
    ianz += ipos;
  }

  SML3_hashfree(&hid);

  return ianz;
} /* Ende object_list */


/* object_has_instances:
 * return whether one or more instances of an object-ID are active
 * @param objid      object-ID
 * @param subid      sub-ID for object-ID, or 0 = no sub-ID
 * @return  VG_TRUE = yes, VG_FALSE = no
 */
static VG_BOOL
object_has_instances(const char *objid, int subid)
{
  struct vgi_objlist *olp;

  if (objid == NULL || *objid == '\0') { objid = VGI_OBJID_NONE; }

  if (vg4data.lists.object.list == NULL) { return VG_FALSE; }

  for (olp = vg4data.lists.object.list->next; olp != NULL; olp = olp->next) {
    if (strcmp(objid, olp->obje.objid) == 0 && (subid == 0 || subid == olp->obje.subid)) { break; }
  }
  if (olp == NULL) { return VG_FALSE; }

  return VG_TRUE;
} /* Ende object_has_instances */


/* object_dump:
 * dump object entries
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
static void
object_dump(FILE *outfp)
{
  struct vgi_objlist *olp;
  struct VG_Object *objp;

  vg4_object_mgmt_dump(outfp);
  fprintf(outfp, "\n");
  vg4_object_collision_dump(outfp);

  if (outfp == NULL) { outfp = stdout; }
        
  fprintf(outfp, "\nDump of objects\n"); 
  fprintf(outfp, "===============\n\n");

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

  for (olp = vg4data.lists.object.list->next; olp != NULL; olp = olp->next) {
    objp = &olp->obje;
    fprintf(outfp, "- instanceid=%u\n", objp->instanceid);
    fprintf(outfp, "  objid=%s, subid=%d\n", objp->objid, objp->subid);
    fprintf(outfp, "  parent-instanceid=%u\n", objp->parent_instanceid);
    fprintf(outfp, "  drawlevel=%d\n", objp->drawlevel);
    fprintf(outfp, "  f_free=[%s]\n", (objp->f_free != NULL ? "set" : "unset"));
    fprintf(outfp, "  f_run=[%s]\n", (objp->f_run != NULL ? "set" : "unset"));
    fprintf(outfp, "  f_draw=[%s]\n", (objp->f_draw != NULL ? "set" : "unset"));
    fprintf(outfp, "  f_data=[%s]\n", (objp->f_data != NULL ? "set" : "unset"));
    fprintf(outfp, "  f_childexit=[%s]\n", (objp->f_childexit != NULL ? "set" : "unset"));
    fprintf(outfp, "  f_signal=[%s]\n", (objp->f_signal != NULL ? "set" : "unset"));
    fprintf(outfp, "\n");
  }
} /* Ende object_dump */
