#include "./store.h"

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>

#include "util/container/array.h"
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/memory/memory.h"

#include "core/locommon/physics.h"
#include "core/locommon/position.h"

#include "./bullet.h"
#include "./character.h"
#include "./entity.h"
#include "./ground.h"

struct loentity_store_t {
  CONTAINER_ARRAY loentity_t** items;
};

static void loentity_store_iterator_assign_by_index_(
    loentity_store_t* store, loentity_store_iterator_t* itr) {
  assert(store != NULL);
  assert(itr   != NULL);
  assert(itr->index < container_array_get_length(store->items));

  itr->entity    = NULL;
  itr->ground    = NULL;
  itr->bullet    = NULL;
  itr->character = NULL;

  itr->entity = store->items[itr->index];
  switch (itr->entity->subclass) {
  case LOENTITY_SUBCLASS_GROUND:
    itr->ground = (loentity_ground_t*) itr->entity;
    break;
  case LOENTITY_SUBCLASS_BULLET:
    itr->bullet = (loentity_bullet_t*) itr->entity;
    break;
  case LOENTITY_SUBCLASS_CHARACTER:
    itr->character = (loentity_character_t*) itr->entity;
    break;
  default:
    assert(false);
  }
}

loentity_store_t* loentity_store_new(size_t reserve) {
  loentity_store_t* store = memory_new(sizeof(*store));
  *store = (typeof(*store)) {0};

  container_array_reserve(store->items, reserve);
  return store;
}

void loentity_store_delete(loentity_store_t* store) {
  if (store == NULL) return;

  container_array_delete(store->items);
  memory_delete(store);
}

void loentity_store_add(
    loentity_store_t* store, loentity_t* entity) {
  assert(store  != NULL);
  assert(entity != NULL);

  const size_t len = container_array_get_length(store->items);
  size_t index = 0;
  for (; index < len; ++index) {
    if (store->items[index] == NULL) break;
  }
  if (index == len) container_array_insert(store->items, index);
  store->items[index] = entity;
}

loentity_t* loentity_store_remove(
    loentity_store_t* store, const loentity_store_iterator_t* itr) {
  assert(store != NULL);
  assert(itr   != NULL);

  assert(itr->index < container_array_get_length(store->items));
  assert(itr->entity != NULL);
  assert(itr->entity == store->items[itr->index]);

  store->items[itr->index] = NULL;
  return itr->entity;
}

void loentity_store_clear(loentity_store_t* store) {
  assert(store != NULL);

  const size_t len = container_array_get_length(store->items);
  for (size_t i = 0; i < len; ++i) {
    loentity_t** e = &store->items[i];
    if (*e == NULL) continue;
    loentity_delete(*e);
    *e = NULL;
  }
}

bool loentity_store_iterate_next(
    loentity_store_t* store, loentity_store_iterator_t* itr) {
  assert(store != NULL);
  assert(itr   != NULL);

  ++itr->index;
  if (itr->entity == NULL) {
    itr->index = 0;
  }
  itr->entity    = NULL;
  itr->ground    = NULL;
  itr->bullet    = NULL;
  itr->character = NULL;

  const size_t len = container_array_get_length(store->items);
  for (; itr->index < len; ++itr->index) {
    if (store->items[itr->index] != NULL) break;
  }
  if (itr->index >= len) return false;

  loentity_store_iterator_assign_by_index_(store, itr);
  return true;
}

bool loentity_store_find_item_by_id(
    loentity_store_t* store, loentity_store_iterator_t* itr, loentity_id_t id) {
  assert(store != NULL);
  assert(itr   != NULL);

  const size_t len = container_array_get_length(store->items);
  for (itr->index = 0; itr->index < len; ++itr->index) {
    loentity_t* e = store->items[itr->index];
    if (e != NULL && e->id == id) {
      loentity_store_iterator_assign_by_index_(store, itr);
      return true;
    }
  }
  return false;
}

bool loentity_store_solve_collision_between_ground(
    loentity_store_t*          store,
    locommon_physics_entity_t* e,
    float                      dt) {
  assert(store != NULL);
  assert(e     != NULL);

  bool solved = false;

  loentity_store_iterator_t itr = {0};
  while (loentity_store_iterate_next(store, &itr)) {
    if (itr.ground == NULL) continue;

    const locommon_physics_entity_t g = {
      .pos      = itr.entity->pos,
      .velocity = vec2(0, 0),
      .size     = itr.ground->size,
    };
    if (locommon_physics_solve_collision_with_fixed_one(e, &g, dt)) {
      solved = true;
    }
  }
  return solved;
}

bool loentity_store_affect_bullets_shot_by_others(
    loentity_store_t*     store,
    loentity_character_t* chara,
    const vec2_t*         velocity,
    float                 dt) {
  assert(store != NULL);
  assert(chara != NULL);
  assert(vec2_valid(velocity));
  assert(MATH_FLOAT_VALID(dt));

  loentity_store_iterator_t itr = {0};
  while (loentity_store_iterate_next(store, &itr)) {
    if (itr.bullet == NULL || itr.bullet->owner == chara->super.id) continue;

    if (loentity_bullet_hittest(itr.bullet, &chara->super.pos, velocity, dt)) {
      if (loentity_bullet_affect(itr.bullet, chara)) return true;
    }
  }
  return false;
}

bool loentity_store_affect_bullets_shot_by_one(
    loentity_store_t*     store,
    loentity_character_t* chara,
    loentity_id_t         shooter,
    const vec2_t*         velocity,
    float                 dt) {
  assert(store != NULL);
  assert(chara != NULL);
  assert(vec2_valid(velocity));
  assert(MATH_FLOAT_VALID(dt));

  loentity_store_iterator_t itr = {0};
  while (loentity_store_iterate_next(store, &itr)) {
    if (itr.bullet == NULL || itr.bullet->owner != shooter) continue;

    if (loentity_bullet_hittest(itr.bullet, &chara->super.pos, velocity, dt)) {
      if (loentity_bullet_affect(itr.bullet, chara)) return true;
    }
  }
  return false;
}
