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

void init_collision(void);
void dest_collision(void);

void vg4_collision_removeid(unsigned int);

static unsigned int collision_create(const struct VG_Rect *, int, int);
static void collision_destroy(unsigned int);
static void collision_destroyall(void);
static void collision_clear(unsigned int);
static void collision_insert(unsigned int, unsigned int, const struct VG_Rect *, int);
static void collision_remove(unsigned int, unsigned int);
static int collision_setpos(unsigned int, unsigned int, const struct VG_Rect *, struct VG_Coll **);
static void collision_mark(unsigned int, struct VG_Image *, int, const char *);
static void collision_dump(FILE *);

static struct vgi_collision * find_tag(unsigned int);


/* set functions */
void
init_collision(void)
{
  vg4data.lists.collision.list = SML3_calloc(1, sizeof(*vg4data.lists.collision.list));  /* first list-element is unused */
  vg4data.lists.collision.list->prev = vg4data.lists.collision.list->next = NULL;
  vg4->collision->create = collision_create;
  vg4->collision->destroy = collision_destroy;
  vg4->collision->destroyall = collision_destroyall;
  vg4->collision->clear = collision_clear;
  vg4->collision->insert = collision_insert;
  vg4->collision->remove = collision_remove;
  vg4->collision->setpos = collision_setpos;
  vg4->collision->mark = collision_mark;
  vg4->collision->dump = collision_dump;
} /* Ende init_collision */

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


/* vg4_collision_removeid:
 * remove object-instance from all collision-tags
 * @param instanceid  instance-ID
 */
void
vg4_collision_removeid(unsigned int instanceid)
{
  struct vgi_collision *clp;

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

  for (clp = vg4data.lists.collision.list->next; clp != NULL; clp = clp->next) {
    quadtree_remove(&clp->qdtr, instanceid);
  }
} /* Ende vg4_collision_removeid */


/* return collision-tag or NULL = not found */
static struct vgi_collision *
find_tag(unsigned int tag)
{
  struct vgi_collision *clp;
  
  if (vg4data.lists.collision.list == NULL) { return NULL; }

  for (clp = vg4data.lists.collision.list->next; clp != NULL; clp = clp->next) {
    if (clp->qdtr.tag == tag) { break; }
  }

  return clp;
} /* Ende find_tag */


/* collision_create:
 * create a collision-tag
 * @param rect       rectangle, which shall be covered by the collision-tag, or NULL = whole window
 * @param max_rect   maximal number of rectangles before sub-levels are created, or 0 = default
 * @param max_level  maximal number of sub-levels, or 0 = default
 * @return  collision-tag
 */
static unsigned int
collision_create(const struct VG_Rect *rect, int max_rect, int max_level)
{
  struct vgi_collision *clp, **clpp;
  unsigned int ctag;

  for (ctag = 1;; ctag++) {
    if (ctag == 0) { fprintf(stderr, "Too many collision-tags\n"); abort(); }
    for (clp = vg4data.lists.collision.list->next; clp != NULL; clp = clp->next) {
      if (clp->qdtr.tag == ctag) { break; }
    }
    if (clp == NULL) { break; }
  }

  clp = SML3_calloc(1, sizeof(*clp));
  clp->prev = clp->next = NULL;
  quadtree_init(&clp->qdtr, ctag, rect, max_rect, max_level);

  for (clpp = &vg4data.lists.collision.list; *clpp != NULL; clpp = &(*clpp)->next) { clp->prev = *clpp; }
  *clpp = clp;

  return ctag;
} /* Ende collision_create */


/* collision_destroy:
 * destroy collision-tag
 * @param ctag  collision-tag
 */
static void
collision_destroy(unsigned int ctag)
{
  struct vgi_collision *clp;

  if ((clp = find_tag(ctag)) == NULL) { return; }

  quadtree_dest(&clp->qdtr);
  if (clp->prev != NULL) { clp->prev->next = clp->next; }
  if (clp->next != NULL) { clp->next->prev = clp->prev; }

  free(clp);
} /* Ende collision_destroy */


/* collision_destroyall:
 * destroy all collision-tags
 */
static void
collision_destroyall(void)
{ 
  struct vgi_collision *clp0, *clp1;
  
  if (vg4data.lists.collision.list == NULL) { return; }

  for (clp0 = vg4data.lists.collision.list->next; clp0 != NULL; clp0 = clp1) {
    if (clp0->prev != NULL) { clp0->prev->next = clp0->next; }
    if (clp0->next != NULL) { clp0->next->prev = clp0->prev; }
    clp1 = clp0->next;
    quadtree_dest(&clp0->qdtr);
    free(clp0);
  }
  vg4data.lists.collision.list->prev = vg4data.lists.collision.list->next = NULL;
} /* Ende collision_destroyall */


/* collision_clear:
 * clear a collision-tag
 * @param ctag  collision-tag
 */
static void
collision_clear(unsigned int ctag)
{
  struct vgi_collision *clp;

  if ((clp = find_tag(ctag)) == NULL) { return; }

  quadtree_clear(&clp->qdtr);
} /* Ende collision_clear */


/* collision_insert:
 * insert object-instance into a collision-tag
 * @param ctag        collision-tag
 * @param instanceid  instance-ID
 * @param rect        position of object-instance
 * @param percent     percent of using rect from the center,
 *                    e.g.: rect={5+10,5+10} and 80%  =>  rect={6+8,6+8}
 */
static void
collision_insert(unsigned int ctag, unsigned int instanceid, const struct VG_Rect *rect, int percent)
{
  struct vgi_collision *clp;

  if ((clp = find_tag(ctag)) == NULL) { return; }

  quadtree_insert(&clp->qdtr, instanceid, rect, percent);
} /* Ende collision_insert */


/* collision_remove:
 * remove object-instance from a collision-tag
 * @param ctag        collision-tag
 * @param instanceid  instance-ID
 */ 
static void 
collision_remove(unsigned int ctag, unsigned int instanceid)
{
  struct vgi_collision *clp;

  if ((clp = find_tag(ctag)) == NULL) { return; }

  quadtree_remove(&clp->qdtr, instanceid);
} /* Ende collision_remove */


/* collision_setpos:
 * set position of an object-instance and get collisions
 * @param ctag        collision-tag
 * @param instanceid  instance-ID
 * @param nrect       position of object-instance
 * @param collp       for returning allocated array with collisions (must be freed with free()), may be NULL
 * @return  number of collisions in collp, even if collp is NULL
 */
static int 
collision_setpos(unsigned int ctag, unsigned int instanceid, const struct VG_Rect *nrect, struct VG_Coll **collp)
{
  struct vgi_collision *clp;

  if (collp != NULL) { *collp = NULL; }
  if ((clp = find_tag(ctag)) == NULL) { return 0; }

  return quadtree_setpos(&clp->qdtr, instanceid, nrect, collp);
} /* Ende collision_setpos */


/* collision_mark:
 * mark object-instance-IDs in the collision-tag by drawing a rectangle around them
 * @param ctag   collision-tag
 * @param imgp   image or NULL = window 
 * @param color  rectangle color (VG_COLOR_*)
 * @param objid  restriction to a specific object-ID, or NULL = all
 */
static void
collision_mark(unsigned int ctag, struct VG_Image *imgp, int color, const char *objid)
{
  struct vgi_collision *clp;

  if ((clp = find_tag(ctag)) == NULL) { return; }

  quadtree_mark(&clp->qdtr, imgp, color, objid);
} /* Ende collision_mark */


/* collision_dump:
 * dump collision entries
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
static void
collision_dump(FILE *outfp)
{
  struct vgi_collision *clp;

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

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

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

  for (clp = vg4data.lists.collision.list->next; clp != NULL; clp = clp->next) {
    quadtree_dump(outfp, &clp->qdtr);
  }
} /* Ende collision_dump */
