#pragma once

#define WALLOC_SZ 10000

template <typename T>
struct WObj {
	int generation = 0;
	T* ptr = nullptr;
	WObj() = default;
	WObj(T* ptr, int generation): generation(generation), ptr(ptr) { }
	WObj(T* ptr): ptr(ptr) {
		this->generation = ptr->generation;
	}

	// // copy constructor
	WObj(T& other) {
		T* b = std::addressof(other);
		this->generation = b->generation;
		this->ptr = b;
		// return *this;
		// this->generation = ptr->generation;
	}

	bool operator==(const WObj& b) const{
		if(b.generation == this->generation && b.ptr == this->ptr) {
			return true;
		} else {
			return false;
		}
	}

	void operator=(const WObj& b) {
		this->generation = b.generation;
		this->ptr = b.ptr;
	}

	// void WObj::operator=(const T& _b) {
		// T* b = std::addressof(_b);
		// this->generation = b->generation;
		// this->ptr = b;
		// return *this;
	// };
	// void WObj::operator=(const T& b) {
	// 	this->generation = b.generation;
	// 	this->ptr = b.ptr;
	// };
	T* get();
	void clear();
};

// template struct WObj<int>;

template <typename T, int sz>
class WAlloc {
private:
	template <typename... Ts>
	T* push_int(int& slot,Ts&&... ts);
public:
	// static WAlloc<T,sz> alloc;
	static WAlloc<T,sz>& get_instance() {
		static WAlloc<T,sz> instance;
		return instance;
	}
	// std::vector<T> arr;
	T* arr;
	std::vector<int> free_slots;
	std::vector<int> generations;
	bool* slot_active;

	int get_index(T* _ptr){
		return _ptr - &this->arr[0];
	}

	int max_slot = -1;

	int alloc_cnt = 0;

	bool debug = false;

	WAlloc();

	void dbg_print();

	int get_next_slot();


	template <typename... Ts>
	WObj<T> push(Ts&&... ts);

	int get_alloc_slot(const T& val);

	void clear() {
		for(int i = 0; i <= max_slot; i++) {
			slot_active[i] = false;
			generations[i] = 0;
		}

		free_slots.resize(0);

		this->max_slot = -1;
		this->alloc_cnt = 0;

	}

	void pop(const T& val);
};


template <typename T, int sz>
WAlloc<T, sz>::WAlloc() {
	free_slots.reserve(sz);
	// generations.reserve(sz);
	generations.resize(sz);
	arr = (T*) malloc(sizeof(T) * sz);
	slot_active = (bool*) malloc(sizeof(bool) * sz);


	for(int i = 0; i < sz; i++) {
		slot_active[i] = false;
	}
}


template <typename T, int sz>
void WAlloc<T, sz>::dbg_print() {
	if(this->debug) {
		wlog_info("[max slot: {}][free slots: {}]", this->max_slot, this->free_slots.size());
		// std::cout << "----------- " << std::endl;
		// std::cout << "max_slot: " << self.max_slot << std::endl;
		// std::cout << "free slots: " << self.free_slots.size() << std::endl;
		// for(auto slot : self.free_slots) {
		// 	std::cout << "free slot: " << slot << std::endl;
		// }
	}
}

template <typename T, int sz>
int WAlloc<T, sz>::get_next_slot() {
	if(this->free_slots.size() > 0) {
		return this->free_slots.back();
	} else {
		return this->max_slot + 1;
	}
}

template <typename T, int sz>
template <typename ... Ts>
T* WAlloc<T, sz>::push_int(int& slot, Ts&&... ts) {
	bool is_overflow = false;

	if(this->free_slots.size() > 0) {
		slot = this->free_slots.back();
	} else {
		is_overflow = true;
		slot = this->max_slot + 1;
	}

	++this->alloc_cnt;

	T* ptr = new(&this->arr[slot]) T(std::forward<Ts>(ts)...);

	if(is_overflow) {
		++this->max_slot;
	} else {
		this->free_slots.pop_back();
	}

	ptr->alloc_slot = slot;
	this->slot_active[slot] = true;

	if(this->debug) {
		// std::cout << "----------- " << std::endl;
		// std::cout << "allocated slot: " << slot << std::endl;
		wlog_info("Allocated slot {} ", slot);
	}
	this->dbg_print();

	return ptr;
}

template <typename T, int sz>
template <typename ... Ts>
WObj<T> WAlloc<T, sz>::push(Ts&&... ts) {
	int slot;
	auto ptr = this->push_int(slot, std::forward<Ts>(ts)...);
	int slot_gen = ++this->generations[slot];
	ptr->generation = slot_gen;
	return WObj<T>(ptr,slot_gen);
}

template <typename T, int sz>
int WAlloc<T, sz>::get_alloc_slot(const T& val) {
	const T* val_ptr_cnst = &val;
	T* val_ptr = (T*)(val_ptr_cnst);
	return val_ptr - &this->arr[0];
}

template <typename T, int sz>
void WAlloc<T, sz>::pop(const T& val) {
	int alloc_slot = this->get_alloc_slot(val);
	if(alloc_slot == this->max_slot) {
		if(this->debug) {
			wlog_info("Deallocated max slot {} ", val.alloc_slot);
		}
		this->slot_active[alloc_slot] = false;
		--this->max_slot;
		// dealloc
	} else {
		if(this->debug) {
			// std::cout << "----------- " << std::endl;
			wlog_info("Deallocated free slot {} ", alloc_slot);
			// std::cout << "deallocated free slot: " << alloc_slot << std::endl;
		}
		this->slot_active[alloc_slot] = false;
		this->free_slots.push_back(alloc_slot);
		// dealloc
	}

	this->alloc_cnt--;
	this->dbg_print();
}

template <typename T>
T* WObj<T>::get() {
	WAlloc<T,WALLOC_SZ>* alloc_inst = &WAlloc<T,WALLOC_SZ>::get_instance();
	int arr_idx = ptr - alloc_inst->arr;
	if(
		alloc_inst->generations[arr_idx] == generation &&
		alloc_inst->slot_active[arr_idx]
	) {
		return ptr;
	} else {
		return nullptr;
	}
	// T*
	// WAlloc<T,WALLOC_SZ>::get_instance().arr[v - arr]
}

template <typename T>
void WObj<T>::clear() {
	this->ptr = nullptr;
	this->generation = 0;
}



