// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "ecs_local.h"

#include <alias/log.h>

alias_ecs_Result alias_ecs_validate_entity_handle(
    const alias_ecs_Instance * instance
  , alias_ecs_EntityHandle     entity
  , uint32_t                 * index_ptr
) {
  uint32_t index = (uint32_t)(entity & 0xFFFFFFFF);
  uint32_t generation = (uint32_t)(entity >> 32);
  if(index >= instance->entity.length) {
    return ALIAS_ECS_ERROR_INVALID_ENTITY;
  }
  if(generation != instance->entity.generation[index]) {
    return ALIAS_ECS_ERROR_INVALID_ENTITY;
  }
  *index_ptr = index;
  return ALIAS_ECS_SUCCESS;
}

alias_ecs_Result alias_ecs_create_entity(
    alias_ecs_Instance     * instance
  , alias_ecs_EntityHandle * entity_ptr
) {
  uint32_t index;

  if(instance->entity.free_indexes.length > 0) {
    index = *alias_Vector_pop(&instance->entity.free_indexes);
  } else {
    index = instance->entity.length++;
  }

  if(instance->entity.length > instance->entity.capacity) {
    size_t old_capacity = instance->entity.capacity;
    size_t new_capacity = instance->entity.length + 1;
    new_capacity += new_capacity >> 1;
    RELOC(instance, old_capacity, new_capacity, instance->entity.generation);
    RELOC(instance, old_capacity, new_capacity, instance->entity.layer_index);
    RELOC(instance, old_capacity, new_capacity, instance->entity.archetype_index);
    RELOC(instance, old_capacity, new_capacity, instance->entity.archetype_code);
    instance->entity.capacity = new_capacity;
  }

  uint32_t generation = instance->entity.generation[index];

  *entity_ptr = ((uint64_t)generation << 32) | (uint64_t)index;

  return ALIAS_ECS_SUCCESS;
}

alias_ecs_Result alias_ecs_free_entity(
    alias_ecs_Instance * instance
  , uint32_t             entity_id
) {
  ++instance->entity.generation[entity_id];
  if(!alias_Vector_space_for(&instance->entity.free_indexes, &instance->memory_cb, 1)) {
    return ALIAS_ECS_ERROR_OUT_OF_MEMORY;
  }
  *alias_Vector_push(&instance->entity.free_indexes) = entity_id;
  return ALIAS_ECS_SUCCESS;
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------

static int _compar_component_index(const void * ap, const void * bp, void *ud) {
  uint32_t a = *(uint32_t *)ap;
  uint32_t b = *(uint32_t *)bp;
  return a - b;
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------
alias_ecs_Result alias_ecs_spawn(
    alias_ecs_Instance              * instance
  , const alias_ecs_EntitySpawnInfo * spawn_info
  , alias_ecs_EntityHandle          * entities_ptr
) {
  return_ERROR_INVALID_ARGUMENT_if(instance == NULL);
  return_ERROR_INVALID_ARGUMENT_if(spawn_info == NULL);

  return_ERROR_INVALID_ARGUMENT_if(spawn_info->count == 0);

  uint32_t layer_index = UINT32_MAX;
  if(spawn_info->layer != ALIAS_ECS_INVALID_LAYER) {
    return_if_ERROR(alias_ecs_validate_layer_handle(instance, spawn_info->layer, &layer_index));
  }

  uint32_t archetype_index;
  {
    alias_ecs_ComponentSet components;
    components.count = spawn_info->num_components;
    ALLOC(instance, components.count, components.index);
    for(uint32_t i = 0; i < components.count; ++i) {
      components.index[i] = spawn_info->components[i].component;
    }
    qsort(components.index, components.count, sizeof(*components.index), _compar_component_index, NULL);
    return_if_ERROR(alias_ecs_resolve_archetype(instance, components, &archetype_index));
  }
  alias_ecs_Archetype * archetype = &instance->archetype.data[archetype_index];

  int free_out_entities = entities_ptr == NULL;
  if(free_out_entities) {
    ALLOC(instance, spawn_info->count, entities_ptr);
  }

  for(uint32_t i = 0; i < spawn_info->count; i++) {
    alias_ecs_EntityHandle entity;
    uint32_t entity_index;

    return_if_ERROR(alias_ecs_create_entity(instance, &entity));

    return_if_ERROR(alias_ecs_validate_entity_handle(instance, entity, &entity_index));

    if(layer_index != UINT32_MAX) {
      return_if_ERROR(alias_ecs_set_entity_layer(instance, entity_index, layer_index));
    }
    return_if_ERROR(alias_ecs_set_entity_archetype(instance, entity_index, archetype_index));

    entities_ptr[i] = entity;
  }

  for(uint32_t i = 0; i < spawn_info->num_components; i++) {
    alias_ecs_EntitySpawnComponent spawn_component = spawn_info->components[i];

    uint32_t component_index = alias_ecs_ComponentSet_order_of(&archetype->components, spawn_component.component);

    ASSERT(component_index != UINT32_MAX);

    uint32_t component_size, component_offset;
    alias_PagedSOA_decode_column(&archetype->paged_soa, component_index + 1, &component_size, &component_offset);

    const uint8_t * read = spawn_component.data;
    uint32_t stride = spawn_component.stride ? spawn_component.stride : component_size;

    for(uint32_t j = 0; j < spawn_info->count; j++) {
      uint32_t entity_index = (uint32_t)(entities_ptr[j] & 0xFFFFFFFF);
      uint32_t code = ENTITY_ARCHETYPE_CODE(instance, entity_index);
      uint32_t page, index;
      alias_PagedSOA_decode_code(&archetype->paged_soa, code, &page, &index);
      void * write = alias_PagedSOA_raw_write(&archetype->paged_soa, page, index, component_size, component_offset);
      memcpy(write, read, component_size);
      read += stride;
    }
  }

  for(uint32_t i = 0; i < archetype->components.count; i++) {
    uint32_t j;
    for(j = 0; j < spawn_info->num_components && spawn_info->components[j].component != archetype->components.index[i]; j++) ;
    if(j < spawn_info->num_components) {
      continue;
    }
    uint32_t component_size, component_offset;
    alias_PagedSOA_decode_column(&archetype->paged_soa, i + 1, &component_size, &component_offset);
    for(j = 0; j < spawn_info->count; j++) {
      uint32_t entity_index = (uint32_t)(entities_ptr[j] & 0xFFFFFFFF);
      uint32_t code = ENTITY_ARCHETYPE_CODE(instance, entity_index);
      uint32_t page, index;
      alias_PagedSOA_decode_code(&archetype->paged_soa, code, &page, &index);
      void * write = alias_PagedSOA_raw_write(&archetype->paged_soa, page, index, component_size, component_offset);
      memset(write, 0, component_size);
    }
  }

  for(uint32_t j = 0; j < spawn_info->count; j++) {
    uint32_t entity_index = (uint32_t)(entities_ptr[j] & 0xFFFFFFFF);
    alias_ecs_init_components(instance, entity_index, archetype_index);
  }

  if(free_out_entities) {
    FREE(instance, spawn_info->count, entities_ptr);
  }
  
  return ALIAS_ECS_SUCCESS;
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------

alias_ecs_Result alias_ecs_add_component_to_entity(
    alias_ecs_Instance        * instance
  , alias_ecs_EntityHandle      entity
  , alias_ecs_ComponentHandle   component_handle
  , const void                * data
) {
  uint32_t entity_index;
  
  return_ERROR_INVALID_ARGUMENT_if(instance == NULL);
  return_if_ERROR(alias_ecs_validate_entity_handle(instance, entity, &entity_index));
  return_ERROR_INVALID_ARGUMENT_if(component_handle >= instance->component.length);

  const alias_ecs_Component * component = &instance->component.data[component_handle];
  return_ERROR_INVALID_ARGUMENT_if(component->non_null && data == NULL);

  alias_ecs_Archetype * archetype = ENTITY_ARCHETYPE_DATA(instance, entity_index);

  uint32_t component_index = alias_ecs_ComponentSet_order_of(&archetype->components, component_handle);

  if(component_index != UINT32_MAX) {
    return ALIAS_ECS_ERROR_COMPONENT_EXISTS;
  }

  alias_ecs_ArchetypeHandle new_archetype;
  {
    alias_ecs_ComponentSet new_components;
    return_if_ERROR(alias_ecs_ComponentSet_add(instance, &new_components, &archetype->components, component_handle));

    return_if_ERROR(alias_ecs_resolve_archetype(instance, new_components, &new_archetype));

    // alias_ecs_resolve_archetype 'consumes' new components
  }

  return_if_ERROR(alias_ecs_set_entity_archetype(instance, entity, new_archetype));

  component_index = alias_ecs_ComponentSet_order_of(&instance->archetype.data[new_archetype].components, component_handle);
  
  if(data != NULL) {
    memcpy(alias_ecs_write(instance, entity_index, component_index), data, component->size);
  } else {
    memset(alias_ecs_write(instance, entity_index, component_index), 0, component->size);
  }

  alias_ecs_init_component(instance, entity_index, new_archetype, component_index);

  return ALIAS_ECS_SUCCESS;
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------

alias_ecs_Result alias_ecs_remove_component_from_entity(
    alias_ecs_Instance        * instance
  , alias_ecs_EntityHandle      entity
  , alias_ecs_ComponentHandle   component_handle
) {
  uint32_t entity_index;
  
  return_ERROR_INVALID_ARGUMENT_if(instance == NULL);
  return_if_ERROR(alias_ecs_validate_entity_handle(instance, entity, &entity_index));
  return_ERROR_INVALID_ARGUMENT_if(component_handle >= instance->component.length);

  uint32_t archetype_index = ENTITY_ARCHETYPE_INDEX(instance, entity_index);
  alias_ecs_Archetype * archetype = &instance->archetype.data[archetype_index];

  uint32_t component_index = alias_ecs_ComponentSet_order_of(&archetype->components, component_handle);

  if(component_index == UINT32_MAX) {
    return ALIAS_ECS_ERROR_COMPONENT_DOES_NOT_EXIST;
  }
  alias_ecs_cleanup_component(instance, entity_index, archetype_index, component_index);

  alias_ecs_ArchetypeHandle new_archetype;
  {
    alias_ecs_ComponentSet new_components;
    return_if_ERROR(alias_ecs_ComponentSet_remove(instance, &new_components, &archetype->components, component_handle));

    return_if_ERROR(alias_ecs_resolve_archetype(instance, new_components, &new_archetype));

    // alias_ecs_resolve_archetype 'consumes' new components
  }

  alias_ecs_set_entity_archetype(instance, entity, new_archetype);

  return ALIAS_ECS_SUCCESS;
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------

alias_ecs_Result alias_ecs_read_entity_component(
    alias_ecs_Instance        * instance
  , alias_ecs_EntityHandle      entity
  , alias_ecs_ComponentHandle   component_handle
  , const void ** ptr
) {
  uint32_t entity_index;

  return_ERROR_INVALID_ARGUMENT_if(instance == NULL);
  return_if_ERROR(alias_ecs_validate_entity_handle(instance, entity, &entity_index));
  return_ERROR_INVALID_ARGUMENT_if(component_handle >= instance->component.length);

  alias_ecs_Archetype * archetype = ENTITY_ARCHETYPE_DATA(instance, entity_index);

  uint32_t component_index = alias_ecs_ComponentSet_order_of(&archetype->components, component_handle);
  if(component_index == UINT32_MAX) {
    *ptr = NULL;
    return ALIAS_ECS_ERROR_COMPONENT_DOES_NOT_EXIST;
  }

  *ptr = alias_ecs_write(instance, entity_index, component_index);

  return ALIAS_ECS_SUCCESS;
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------

alias_ecs_Result alias_ecs_write_entity_component(
    alias_ecs_Instance        * instance
  , alias_ecs_EntityHandle      entity
  , alias_ecs_ComponentHandle   component_handle
  , void ** ptr
) {
  uint32_t entity_index;

  return_ERROR_INVALID_ARGUMENT_if(instance == NULL);
  return_if_ERROR(alias_ecs_validate_entity_handle(instance, entity, &entity_index));
  return_ERROR_INVALID_ARGUMENT_if(component_handle >= instance->component.length);

  alias_ecs_Archetype * archetype = ENTITY_ARCHETYPE_DATA(instance, entity_index);

  uint32_t component_index = alias_ecs_ComponentSet_order_of(&archetype->components, component_handle);
  if(component_index == UINT32_MAX) {
    *ptr = NULL;
    return ALIAS_ECS_ERROR_COMPONENT_DOES_NOT_EXIST;
  }

  *ptr = alias_ecs_write(instance, entity_index, component_index);

  return ALIAS_ECS_SUCCESS;
}

// -------------------------------------------------------------------------------------------------------------------------------------------------------------

alias_ecs_Result alias_ecs_despawn(
    alias_ecs_Instance           * instance
  , uint32_t                       count
  , const alias_ecs_EntityHandle * entity
) {
  uint32_t entity_index;
  
  return_ERROR_INVALID_ARGUMENT_if(instance == NULL);
  return_ERROR_INVALID_ARGUMENT_if(count > instance->entity.length);
  return_ERROR_INVALID_ARGUMENT_if(entity == NULL);

  for(uint32_t i = 0; i < count; i++) {
    return_if_ERROR(alias_ecs_validate_entity_handle(instance, entity[i], &entity_index));

    alias_ecs_cleanup_components(instance, entity_index, ENTITY_ARCHETYPE_INDEX(instance, entity_index));
    alias_ecs_unset_entity_layer(instance, entity_index);
    alias_ecs_unset_entity_archetype(instance, entity_index);
    alias_ecs_free_entity(instance, entity_index);
  }

  return ALIAS_ECS_SUCCESS;
}

alias_ecs_Result alias_ecs_despawn_indexes(
    alias_ecs_Instance * instance
  , uint32_t             count
  , const uint32_t     * entity_indexes
) {
  uint32_t entity_index;
  
  return_ERROR_INVALID_ARGUMENT_if(instance == NULL);
  return_ERROR_INVALID_ARGUMENT_if(count > instance->entity.length);
  return_ERROR_INVALID_ARGUMENT_if(entity_indexes == NULL);

  for(uint32_t i = 0; i < count; i++) {
    entity_index = entity_indexes[i];

    alias_ecs_cleanup_components(instance, entity_index, ENTITY_ARCHETYPE_INDEX(instance, entity_index));
    alias_ecs_unset_entity_layer(instance, entity_index);
    alias_ecs_unset_entity_archetype(instance, entity_index);
    alias_ecs_free_entity(instance, entity_index);
  }

  return ALIAS_ECS_SUCCESS;
}