#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <box2d/box2d.h>
#include <aklabs/exclib.h>
#include <tidwall/hashmap.h>

#include "game.h"
#include "sprite.h"

struct hashmap *registry_sprite;
struct hashmap *registry_actor;
struct hashmap *registry_spritesheet;

int sprite_compare(const void *a, const void *b, void *udata)
{
  const sprite *ua = (sprite *)a;
  const sprite *ub = (sprite *)b;
  return strncmp(ua->name, ub->name, SPRITE_MAX_ACTOR_NAME_LENGTH);
}

int actor_compare(const void *a, const void *b, void *udata)
{
  const actor *ua = (actor *)a;
  const actor *ub = (actor *)b;
  return strncmp(ua->name, ub->name, SPRITE_MAX_NAME_LENGTH);
}

int spritesheet_compare(const void *a, const void *b, void *udata)
{
  const spritesheet *ua = (spritesheet *)a;
  const spritesheet *ub = (spritesheet *)b;
  return strncmp(ua->sourcefile, ub->sourcefile, SPRITE_SHEET_MAX_FILENAME_LENGTH);
}

uint64_t sprite_hash(const void *item, uint64_t seed0, uint64_t seed1)
{
  const sprite *spr = (sprite *)item;
  return hashmap_sip(spr->name, strlen(spr->name), seed0, seed1);
}

uint64_t actor_hash(const void *item, uint64_t seed0, uint64_t seed1)
{
  const actor *actortgt = (actor *)item;
  return hashmap_sip(actortgt->name, strlen(actortgt->name), seed0, seed1);
}

uint64_t spritesheet_hash(const void *item, uint64_t seed0, uint64_t seed1)
{
  const spritesheet *sheet = (spritesheet *)item;
  return hashmap_sip(sheet->sourcefile, strlen(sheet->sourcefile), seed0, seed1);
}

void registry_init(void)
{
  registry_sprite = hashmap_new(sizeof(sprite), 0, 0, 0, sprite_hash, sprite_compare, NULL, NULL);
  registry_actor = hashmap_new(sizeof(actor), 0, 0, 0, actor_hash, actor_compare, NULL, NULL);
  registry_spritesheet = hashmap_new(sizeof(spritesheet), 0, 0, 0, spritesheet_hash, spritesheet_compare, NULL, NULL);  
}

void actor_new(actor *obj, char *name)
{
  memset(obj, sizeof(actor), 0x00);
  strncpy(obj->name, name, SPRITE_MAX_ACTOR_NAME_LENGTH);
  obj->curSpriteReversing = false;
}

void actor_update(actor *obj)
{
  size_t iter = 0;
  SDL_Time curtime = 0;
  SDL_Time curtimems = 0;

  THROW_ZERO(obj, EXC_NULLPOINTER, "actor_update received NULL actor");
  if ( obj->curSprite == NULL ) {
    return;
  }
  SDL_GetCurrentTime(&curtime);
  curtimems = curtime / 1000000;
  // is it time to change frames?
  if ( (curtimems - obj->curSpriteFrameTimer) >= obj->curSprite->speed ) {
    // are we currently looping in reverse?
    if ( obj->curSprite->loop == true && obj->curSpriteReversing == true ) {
      // are we at the beginning of the loop?
      if ( obj->curSpriteFrameId == 0 ) {
	obj->curSpriteReversing = false;
	obj->curSpriteFrameId += 1;
      } else {
	obj->curSpriteFrameId -= 1;
      }
      // are we at the end of the animation?
    } else if ( obj->curSpriteFrameId == (obj->curSprite->frames-1) ) {
      // are we set to loop in reverse?
      if ( obj->curSprite->loop == true && obj->curSprite->loopReverse == true ) {
	obj->curSpriteReversing = true;
	obj->curSpriteFrameId -= 1;
	// are we set to loop forward?
      } else if ( obj->curSprite->loop == true ) {
	obj->curSpriteFrameId = 0;
      }
      // we are not looping in reverse and we are not at the end of the animation
    } else {
      obj->curSpriteFrameId += 1;
    }
    obj->curSpriteFrameTimer = curtimems;
  }
}

void actor_render(actor *obj, SDL_Renderer *renderer)
{
  THROW_ZERO(obj, EXC_NULLPOINTER, "NULL actor");
  THROW_ZERO(renderer, EXC_NULLPOINTER, "NULL renderer");
  THROW_ZERO(obj->curSprite, EXC_NULLPOINTER, "Actor has NULL sprite currently");
  
  SDL_FRect src;
  SDL_FRect dest;
  src.x = obj->curSprite->width * obj->curSprite->frameids[obj->curSpriteFrameId];
  if ( src.x >= obj->curSprite->sheet->texture->w ) {
    src.y = ((int)src.x / obj->curSprite->sheet->texture->w) * obj->curSprite->height;
    src.x = ((int)src.x % obj->curSprite->sheet->texture->w);
  } else {
    src.y = 0;
  }
  src.w = obj->curSprite->width;
  src.h = obj->curSprite->height;
  dest.x = obj->x;
  dest.y = obj->y;
  dest.w = obj->curSprite->width;
  dest.h = obj->curSprite->width;

  SDL_RenderTexture(renderer, obj->curSprite->sheet->texture, &src, &dest);
}

void sprite_new(sprite *spr, char *name, spritesheet *sheet)
{
  THROW_ZERO(spr, EXC_NULLPOINTER, "Null sprite reference");
  THROW_ZERO(name, EXC_NULLPOINTER, "Empty sprite name");
  THROW_ZERO(sheet, EXC_NULLPOINTER, "Null spritesheet reference");
  
  memset(spr, 0x00, sizeof(sprite));
  memcpy(spr->name, name, SPRITE_MAX_NAME_LENGTH);
  spr->sheet = sheet;
}

void spritesheet_new(spritesheet *sheet, short sprite_w, short sprite_h, char *filename)
{
  char *asset_path;
  THROW_ZERO(sheet, EXC_NULLPOINTER, "Null spritesheet pointer");
  THROW_ZERO(filename, EXC_NULLPOINTER, "Null filename pointer");

  SDL_asprintf(&asset_path, "%s%s", SDL_GetBasePath(), filename);
  sheet->texture = IMG_LoadTexture(renderer, asset_path);
  
  THROW_ZERO(sheet->texture, EXC_NULLPOINTER, "Failed loading asset");
}
