/* 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 "object.h"

void init_object_collision(void);

void vg4_object_collision_dump(FILE *);

static void object_collision_add(const char *, const char *, int (*)(void *, unsigned int, struct VG_Coll *));
static void object_collision_destroyall(void);
static int object_collision_call(void *, unsigned int, struct VG_Coll *, int);

struct collval {
  int (*collfn)(void *, unsigned int, struct VG_Coll *);
};


/* set functions */
void
init_object_collision(void)
{
  vg4->object->collision_add = object_collision_add;
  vg4->object->collision_destroyall = object_collision_destroyall;
  vg4->object->collision_call = object_collision_call;
} /* Ende init_object_collision */


/* vg4_object_collision_dump:
 * dump object-collision-functions
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
void
vg4_object_collision_dump(FILE *outfp)
{
  struct SML3_hash *hptr, *hsub;
  struct SML3_hashelem *he1, *he2;
  struct collval *cval;

  hptr = vg4data.lists.object.hcoll;

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

  fprintf(outfp, "\nDump of object-collision-functions\n"); 
  fprintf(outfp, "==================================\n\n");

  for (he1 = SML3_hashsublist(hptr, NULL); he1 != NULL; he1 = SML3_hashsublist(hptr, he1)) {
    hsub = SML3_hashsubhash(he1);
    for (he2 = SML3_hashlist(hsub, NULL, 0); he2 != NULL; he2 = SML3_hashlist(hsub, he2, 0)) {
      cval = (struct collval *)SML3_hashelem_valget(he2, NULL);
      fprintf(outfp, "- %s + %s = [%s]\n",
              (char *)SML3_hashelem_keyget(he1, NULL),
              (char *)SML3_hashelem_keyget(he2, NULL),
              (cval->collfn != NULL ? "set" : "unset")
             );
    }
  }
} /* Ende object_collision_dump */


/* object_collision_add:
 * add a collision-function for two object-IDs
 * @param objid1  object-ID for moving object
 * @param objid2  object-ID for hit object
 * @param collfn  collision-function:
 *                  int (*collfn)(void *vgame, unsigned int instanceid, struct VG_Coll *collb)
 *                    - vgame: private structure of the game, or NULL
 *                    - instanceid: instance-ID of the moving object-instance
 *                    - collb: collision-struct (from the array got from vg4->collision->setpos())
 *                    -> returns: one from VG_COLL_RETURNS
 *
 * The collision-function will also be added inverse,
 * that is for objid2 and objid1, if it has not been set already.
 */
static void
object_collision_add(const char *objid1, const char *objid2, int (*collfn)(void *, unsigned int, struct VG_Coll *))
{
  struct SML3_hash *hptr, *hsub;
  struct SML3_hashelem *he1;
  struct collval cval;

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

  hptr = vg4data.lists.object.hcoll;

  /* set function to objid1+objid2 */
  hsub = SML3_hashsubset(hptr, objid1, strlen(objid1) + 1);
  he1 = SML3_hashset(hsub, objid2, strlen(objid2) + 1);
  cval.collfn = collfn;
  SML3_hashelem_valset(he1, &cval, sizeof(cval));

  /* set function to objid2+objid1, if not already set */
  hsub = SML3_hashsubset(hptr, objid2, strlen(objid2) + 1);
  he1 = SML3_hashget(hsub, objid1, strlen(objid1) + 1);
  if (he1 == NULL) {
    he1 = SML3_hashset(hsub, objid1, strlen(objid1) + 1);
    SML3_hashelem_valset(he1, &cval, sizeof(cval));
  }
} /* Ende object_collision_add */


/* object_collision_destroyall:
 * destroy all settings with collision-functions
 */
static void
object_collision_destroyall(void)
{
  SML3_hashfree(&vg4data.lists.object.hcoll);
} /* Ende object_collision_destroyall */


/* object_collision_call:
 * call a collision-function
 * @param vgame        private structure of the game, or NULL
 * @param instanceid   instance-ID of the moving object-instance
 * @param collp        array of collisions (got from vg4->collision->setpos())
 * @param nr_coll      number of collisions in collp
 * @return  one from VG_COLL_RETURNS
 */
static int
object_collision_call(void *vgame, unsigned int instanceid, struct VG_Coll *collp, int nr_coll)
{
#undef SHOW_OUT
  struct SML3_hash *hptr, *hsub;
  struct SML3_hashelem *he1;
  struct VG_Object *objp;
  struct collval *cval;
  int cpos, retw, iret;
#ifdef SHOW_OUT
  char *mov_objid;
#endif

  if (instanceid == 0 || collp == NULL || nr_coll == 0) { return VG_COLL_RETURN_CONTINUE; }

  hptr = vg4data.lists.object.hcoll;

  objp = vg4->object->instance_getobj(instanceid);
  if (objp == NULL) { return VG_COLL_RETURN_CONTINUE; }
  hsub = SML3_hashsubget(hptr, objp->objid, strlen(objp->objid) + 1);
  if (hsub == NULL) { return VG_COLL_RETURN_CONTINUE; }
#ifdef SHOW_OUT
  mov_objid = objp->objid;
#endif

  retw = VG_COLL_RETURN_CONTINUE;

#ifdef SHOW_OUT
  fprintf(stderr, "%d collisions of moving %s:\n", nr_coll, mov_objid);
#endif

  for (cpos = 0; cpos < nr_coll; cpos++) {
    objp = vg4->object->instance_getobj(collp[cpos].instanceid);
    if (objp == NULL) { continue; }
    he1 = SML3_hashget(hsub, objp->objid, strlen(objp->objid) + 1);
    if (he1 == NULL) { continue; }
    cval = (struct collval *)SML3_hashelem_valget(he1, NULL);
    if (cval->collfn == NULL) { continue; }
    iret = cval->collfn(vgame, instanceid, &collp[cpos]);
#ifdef SHOW_OUT
    { const char *retstr;
      switch(iret) {
        case VG_COLL_RETURN_CONTINUE: retstr = "continue"; break;
        case VG_COLL_RETURN_STOP_X: retstr = "stop-x"; break;
        case VG_COLL_RETURN_STOP_Y: retstr = "stop-y"; break;
        case VG_COLL_RETURN_STOP: retstr = "stop"; break;
        case VG_COLL_RETURN_DEAD: retstr = "dead"; break;
        case VG_COLL_RETURN_BREAK: retstr = "break"; break;
        default: retstr = "?";
      }
      fprintf(stderr, " -> %s: %s\n", objp->objid, retstr);
    }
#endif
    if (iret == VG_COLL_RETURN_DEAD) {
      retw = iret;
    } else if (iret == VG_COLL_RETURN_BREAK) {
      if (retw != VG_COLL_RETURN_DEAD) { retw = iret; }
    } else if (iret == VG_COLL_RETURN_STOP) {
      if (retw != VG_COLL_RETURN_DEAD && retw != VG_COLL_RETURN_BREAK) { retw = iret; }
    } else if (iret == VG_COLL_RETURN_STOP_X) {
      if (retw == VG_COLL_RETURN_CONTINUE) {
        retw = iret;
      } else if (retw == VG_COLL_RETURN_STOP_Y) {
        retw = VG_COLL_RETURN_STOP;
      }
    } else if (iret == VG_COLL_RETURN_STOP_Y) {
      if (retw == VG_COLL_RETURN_CONTINUE) {
        retw = iret;
      } else if (retw == VG_COLL_RETURN_STOP_X) {
        retw = VG_COLL_RETURN_STOP;
      }
    }
  }

  return retw;
} /* Ende object_collision_call */
