// 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"

static int _compare_components(
    uint32_t         a_num_components
  , const uint32_t * a_component_indexes
  , uint32_t         b_num_components
  , const uint32_t * b_component_indexes
) {
  if(a_num_components != b_num_components) {
    return (int)a_num_components - (int)b_num_components;
  }
  if(a_num_components > 0) {
    return memcmp(a_component_indexes, b_component_indexes, sizeof(*a_component_indexes) * a_num_components);
  } else {
    return 0;
  }
}

static int _sort(void * ud, const void * ap, const void * bp) {
  alias_ecs_Instance * instance = (alias_ecs_Instance *)ud;
  uint32_t a_index = *(uint32_t *)ap;
  uint32_t b_index = *(uint32_t *)bp;
  alias_ecs_Archetype * a = &instance->archetype.data[a_index];
  alias_ecs_Archetype * b = &instance->archetype.data[b_index];
  return _compare_components(a->components.count, a->components.index, b->components.count, b->components.index);
}

static int _search(void * ud, const void * ap, const void * bp) {
  alias_ecs_Instance * instance = (alias_ecs_Instance *)ud;
  const alias_ecs_ComponentSet * components = (const alias_ecs_ComponentSet *)ap;
  uint32_t archetype_index = *(uint32_t *)bp;
  const alias_ecs_Archetype * archetype = &instance->archetype.data[archetype_index];
  return _compare_components(components->count, components->index, archetype->components.count, archetype->components.index);
}

alias_ecs_Result alias_ecs_resolve_archetype(
    alias_ecs_Instance        * instance
  , alias_ecs_ComponentSet      components
  , alias_ecs_ArchetypeHandle * out_ptr
) {
  return_if_ERROR(alias_ecs_ComponentSet_expand_required(instance, &components));
  
  uint32_t * index_ptr = alias_ecs_bsearch(
      &components
    , instance->archetype.components_index
    , instance->archetype.length
    , sizeof(*instance->archetype.components_index)
    , (alias_ecs_CompareCB) { _search, instance }
  );

  if(index_ptr != NULL) {
    *out_ptr = *index_ptr;
    alias_ecs_ComponentSet_free(instance, &components);
    return ALIAS_ECS_SUCCESS;
  }

  uint32_t archetype_index = instance->archetype.length++;

  if(instance->archetype.length > instance->archetype.capacity) {
    uint32_t old_capacity = instance->archetype.capacity;
    uint32_t new_capacity = instance->archetype.length + (instance->archetype.length >> 1);
    RELOC(instance, old_capacity, new_capacity, instance->archetype.components_index);
    RELOC(instance, old_capacity, new_capacity, instance->archetype.data);
    instance->archetype.capacity = new_capacity;
  }

  alias_ecs_Archetype * archetype = &instance->archetype.data[archetype_index];

  archetype->components = components;

  size_t * sizes = alias_malloc(&instance->memory_cb, sizeof(size_t) * (1 + components.count), alignof(*sizes));

  sizes[0] = sizeof(uint32_t);
  for(uint32_t i = 0; i < components.count; i++) {
    sizes[i + 1] = instance->component.data[components.index[i]].size;

    archetype->any_init |= !alias_Closure_is_empty(&instance->component.data[components.index[i]].init);
    archetype->any_cleanup |= !alias_Closure_is_empty(&instance->component.data[components.index[i]].cleanup);
  }

  alias_PagedSOA_initialize(&archetype->paged_soa, &instance->memory_cb, sizeof(uint32_t), 1 + components.count, sizes);

  alias_free(&instance->memory_cb, sizes, sizeof(size_t) * (1 + components.count), alignof(*sizes));

  instance->archetype.components_index[archetype_index] = archetype_index;
  alias_ecs_quicksort(
      instance->archetype.components_index
    , instance->archetype.length
    , sizeof(*instance->archetype.components_index)
    , (alias_ecs_CompareCB) { _sort, instance });

  *out_ptr = archetype_index;

  return ALIAS_ECS_SUCCESS;
}

static alias_ecs_Result _allocate_code(
    alias_ecs_Instance * instance
  , uint32_t             archetype_index
  , uint32_t           * archetype_code
) {
  alias_ecs_Archetype * archetype = &instance->archetype.data[archetype_index];

  uint32_t code;

  if(archetype->free_codes.length > 0) {
    code = *alias_Vector_pop(&archetype->free_codes);
  } else {
    alias_PagedSOA_space_for(&archetype->paged_soa, &instance->memory_cb, 1);
    code = alias_PagedSOA_push(&archetype->paged_soa);
  }

  *(uint32_t *)alias_PagedSOA_page(&archetype->paged_soa, code) += 1;

  *archetype_code = code;

  return ALIAS_ECS_SUCCESS;
}

static alias_ecs_Result _free_code(
    alias_ecs_Instance * instance
  , uint32_t             archetype_index
  , uint32_t             code
) {
  alias_ecs_Archetype * archetype = &instance->archetype.data[archetype_index];

  if(!alias_Vector_space_for(&archetype->free_codes, &instance->memory_cb, 1)) {
    return ALIAS_ECS_ERROR_OUT_OF_MEMORY;
  }
  
  *alias_Vector_push(&archetype->free_codes) = code;

  *(uint32_t *)alias_PagedSOA_page(&archetype->paged_soa, code) -= 1;

  return ALIAS_ECS_SUCCESS;
}

alias_ecs_Result alias_ecs_unset_entity_archetype(
    alias_ecs_Instance * instance
  , uint32_t             entity_index
) {
  uint32_t archetype_index = ENTITY_ARCHETYPE_INDEX(instance, entity_index);
  uint32_t archetype_code = ENTITY_ARCHETYPE_CODE(instance, entity_index);
  
  ENTITY_DATA_ENTITY_INDEX(instance, entity_index) = 0;
  ENTITY_ARCHETYPE_INDEX(instance, entity_index) = 0;
  ENTITY_ARCHETYPE_CODE(instance, entity_index) = 0;

  return _free_code(instance, archetype_index, archetype_code);
}

alias_ecs_Result alias_ecs_set_entity_archetype(
    alias_ecs_Instance * instance
  , uint32_t             entity_index
  , uint32_t             archetype_index
) {
  alias_ecs_Archetype * archetype = &instance->archetype.data[archetype_index];

  if(ENTITY_ARCHETYPE_INDEX(instance, entity_index) == 0) {
    uint32_t code;
    return_if_ERROR(_allocate_code(instance, archetype_index, &code));
    ENTITY_ARCHETYPE_INDEX(instance, entity_index) = archetype_index;
    ENTITY_ARCHETYPE_CODE(instance, entity_index) = code;
    ENTITY_DATA_ENTITY_INDEX(instance, entity_index) = entity_index;
    return ALIAS_ECS_SUCCESS;
  }

  ENTITY_DATA_ENTITY_INDEX(instance, entity_index) = 0;

  uint32_t old_archetype_index = ENTITY_ARCHETYPE_INDEX(instance, entity_index);
  uint32_t old_code = ENTITY_ARCHETYPE_CODE(instance, entity_index);
  alias_ecs_Archetype * old_archetype = &instance->archetype.data[old_archetype_index];
  uint32_t old_block_index = old_code >> 16;
  uint32_t old_block_offset = old_code & 0xFFFF;

  return_if_ERROR(_free_code(instance, old_archetype_index, old_code));

  uint32_t code;
  return_if_ERROR(_allocate_code(instance, archetype_index, &code));
  uint32_t block_index = code >> 16;
  uint32_t block_offset = code & 0xFFFF;
  
  ENTITY_ARCHETYPE_INDEX(instance, entity_index) = archetype_index;
  ENTITY_ARCHETYPE_CODE(instance, entity_index) = code;
  ENTITY_DATA_ENTITY_INDEX(instance, entity_index) = entity_index;

  uint32_t r = 0;
  uint32_t w = 0;

  while(w < archetype->components.count && r < old_archetype->components.count) {
    alias_ecs_ComponentHandle c = archetype->components.index[w];

    while(r < old_archetype->components.count && old_archetype->components.index[r] < c) {
      r++;
    }

    if(r < old_archetype->components.count && old_archetype->components.index[r] == c) {
      void * old_data = alias_ecs_raw_access(instance, old_archetype_index, r, old_block_index, old_block_offset);
      void *     data = alias_ecs_raw_access(instance,     archetype_index, w,     block_index,     block_offset);
      memcpy(data, old_data, archetype->paged_soa.size_offset[w + 1] >> 16);
      
      r++;
    }

    w++;
  }
  
  return ALIAS_ECS_SUCCESS;
}