#ifndef world_hpp
#define world_hpp

struct Arena;

#include <stdint.h>

#include <new>
#include <utility>

using Component_Mask = uint64_t;
using Entity_Id = uint32_t;

#define max_entities 1024
#define slinky_size 128
#define max_components 64
#define max_slinkies (max_entities / slinky_size)

static_assert((!(slinky_size & (slinky_size - 1))));
/* slinky_size needs to be a power of 2 */
static consteval int get_slinky_size_bit() {
	int i;
	int m = slinky_size;
	for (i = 0; m; m >>= 1, i++);
	if (i == 0)
		throw "wtf";
	return i - 1;
}
static constexpr int slinky_size_bit = get_slinky_size_bit();

struct Slinky {
	Entity_Id* entities;
	void* data;
	int count;

	void init(Arena* a, int size);
	void* add(Entity_Id eid, int size, int& mapping);
	void* get(Entity_Id eid, int mapping, int size);
	void remove(Entity_Id eid, int mapping, int size);
};

struct Pool {
	struct Mapping {
		int slinky;
		int mapping;
	};

	Component_Mask mask;
	Slinky slinkies[max_slinkies];
	Mapping mapping[max_entities];
	int offsets[max_components];
	int size, count, slinky_count;

	void init(Arena* a, Component_Mask m);
	Slinky& get_slinky(Arena* a);
	void* add(Arena* a, Entity_Id eid);
	void* get(Entity_Id eid, int cid);
	void* get(Entity_Id eid);
	void remove(Entity_Id eid);
};

int entity_version(Entity_Id e);
int entity_index(Entity_Id e);
Entity_Id entity_id(int index, int version);
int get_new_component_id(int size);

struct World {
	static constexpr int max_pools = 32;
	Pool pools[max_pools];
	Component_Mask masks[max_entities];
	uint8_t versions[max_entities];
	Entity_Id entities[max_entities];
	Entity_Id freelist[max_entities];
	Arena* arena;
	int count, free_count;

	void init(Arena* a);
	uint64_t hash_mask(Component_Mask m);
	Pool& get_pool(Component_Mask m);

	template <typename T>
	int get_component_id() {
  	static int id = get_new_component_id(sizeof(T));
  	return id;
	}

	Entity_Id create_entity();

	template <typename... T>
	Component_Mask get_mask() {
		return (... | ((Component_Mask)1 << get_component_id<T>()));
	}

	template <typename... T>
	std::tuple<T&...> add(Entity_Id e) {
		Component_Mask m = get_mask<T...>();
		add(e, m);
		Pool& p = get_pool(masks[entity_index(e)]);
		return { *(T*)p.get(e, get_component_id<T>())... };
	}

	template <typename T>
	T& get(Entity_Id e) {
		int id = get_component_id<T>();
		return *(T*)get(e, id);
	}

	template <typename T>
	void remove(Entity_Id e) {
		int id = get_component_id<T>();
		remove(e, id);
	}

	template <typename T>
	bool has(Entity_Id e) {
		int id = get_component_id<T>();
		Component_Mask m = masks[entity_index(e)];
		return (m & ((Component_Mask)1 << id)) != 0;
	}

	void* add(Entity_Id eid, Component_Mask m);
	void* get(Entity_Id eid, int cid);
	void remove(Entity_Id eid, int cid);
	void destroy(Entity_Id e);

	struct View {
		World* w;
		Pool* pools[max_pools];
		int pool_count;

		struct Iter {
			View* v;
			int ptr, ind;

			bool equals(const Iter& other) {
				return ptr == other.ptr && ind == other.ind;
			}
			bool operator==(const Iter& other) {
				return equals(other);
			}
			bool operator!=(const Iter& other) {
				return !equals(other);
			}
			Iter operator++() {
				Pool* p = v->pools[ind];
				ptr++;
				if (ptr == p->count) {
					ptr = 0;
					ind++;
				}
				return *this;
			}

			template <typename C>
			C& get() {
				Pool* p = v->pools[ind];
				Slinky& s = p->slinkies[ptr >> slinky_size_bit];
				int si = &s - p->slinkies;
				int off = ptr - (si << slinky_size_bit);
				return *(C*)(
					(char*)s.data + off * p->size +
					p->offsets[v->w->get_component_id<C>()]
				);
			}

			Iter& operator*() {
				return *this;
			}

			Entity_Id entity() {
				Pool* p = v->pools[ind];
				Slinky& s = p->slinkies[ptr >> slinky_size_bit];
				int si = &s - p->slinkies;
				int off = ptr - (si << slinky_size_bit);
				return s.entities[off];
			}
		};

		Iter begin() {
			Iter i;
			i.v = this;
			i.ind = 0;
			i.ptr = 0;
			return i;
		}

		Iter end() {
			Iter i;
			i.v = this;
			i.ind = pool_count;
			i.ptr = 0;
			return i;
		}
	};

	template <typename... T>
	View view() {
		int i;
		auto m = get_mask<T...>();
		View v;
		v.w = this;
		v.pool_count = 0;
		for (i = 0; i < max_pools; i++) {
			Pool& p = pools[i];
			if (p.count && m == (p.mask & m))
				v.pools[v.pool_count++] = &p;
		}
		return v;
	}
};

#endif