module bubel.ecs.vector;

import core.bitop;
//import core.stdc.stdlib : free, malloc;
import bubel.ecs.std;
//import core.stdc.string : memcpy, memset;
//import std.algorithm : swap;
import std.conv : emplace;
import std.traits : hasMember, isCopyable, TemplateOf, Unqual;

export @nogc @safe nothrow pure size_t nextPow2(size_t num) {
	return 1 << bsr(num) + 1;
}

export __gshared size_t gVectorsCreated = 0;
export __gshared size_t gVectorsDestroyed = 0;

struct Vector(T) {
	T[] array;
	size_t used;
public:

	export this()(T t) {
		add(t);
	}

	export this(X)(X[] t) if (is(Unqual!X == Unqual!T)) {
		add(t);

	}

	/*static if (isCopyable!T) {
		export this(this) {
			T[] tmp = array[0 .. used];
			array = null;
			used = 0;
			add(tmp);
		}
	} else {
		@disable this(this);
	}*/

	@disable this(this);

	export ~this() {
		clear();
	}

	export void clear() {
		removeAll();
	}

	export void removeAll() {
		if (array !is null) {
			/*foreach (ref el; array[0 .. used]) {
				destroy(el);
			}*/
			freeData(cast(void[]) array);
			gVectorsDestroyed++;
		}
		array = null;
		used = 0;
	}

	export bool empty() const {
		return (used == 0);
	}

	export size_t length() const {
		return used;
	}

	export void length(size_t newLength) {
		if (newLength > used) {
			reserve(newLength);
			foreach (ref el; array[used .. newLength]) {
				emplace(&el);
			}
		} else {
			foreach (ref el; array[newLength .. used]) {
				destroy(el);
			}
		}
		used = newLength;
	}

	export void reset() {
		used = 0;
	}

	export void reserve(size_t numElements) {
		if (numElements > array.length) {
			extend(numElements);
		}
	}

	export size_t capacity() {
		return array.length - used;
	}

	export void extend(size_t newNumOfElements) {
		auto oldArray = manualExtend(array, newNumOfElements);
		if (oldArray !is null) {
			freeData(oldArray);
		}
	}

	export @nogc void freeData(void[] data) {
		// 0x0F probably invalid value for pointers and other types
		memset(data.ptr, 0x0F, data.length); // Makes bugs show up xD 
		free(data.ptr);
	}

	export static void[] manualExtend(ref T[] array, size_t newNumOfElements = 0) {
		if (newNumOfElements == 0)
			newNumOfElements = 2;
		if (array.length == 0)
			gVectorsCreated++;
		T[] oldArray = array;
		size_t oldSize = oldArray.length * T.sizeof;
		size_t newSize = newNumOfElements * T.sizeof;
		T* memory = cast(T*) malloc(newSize);
		memcpy(cast(void*) memory, cast(void*) oldArray.ptr, oldSize);
		array = memory[0 .. newNumOfElements];
		return cast(void[]) oldArray;
	}

	export Vector!T copy()() {
		Vector!T duplicate;
		duplicate.reserve(used);
		duplicate ~= array[0 .. used];
		return duplicate;
	}

	export bool canAddWithoutRealloc(uint elemNum = 1) {
		return used + elemNum <= array.length;
	}

	export void add()(T t) {
		if (used >= array.length) {
			extend(nextPow2(used + 1));
		}
		emplace(&array[used], t);
		used++;
	}

	/// Add element at given position moving others
	export void add()(T t, size_t pos) {
		assert(pos <= used);
		if (used >= array.length) {
			extend(array.length * 2);
		}
		foreach_reverse (size_t i; pos .. used) {
			//swap(array[i + 1], array[i]);
			array[i+1] = array[i];
		}
		emplace(&array[pos], t);
		used++;
	}

	export void add(X)(X[] t) if (is(Unqual!X == Unqual!T)) {
		if (used + t.length > array.length) {
			extend(nextPow2(used + t.length));
		}
		foreach (i; 0 .. t.length) {
			emplace(&array[used + i], t[i]);
		}
		used += t.length;
	}

	export void remove(size_t elemNum) {
		destroy(array[elemNum]);
		//swap(array[elemNum], array[used - 1]);
		array[elemNum] = array[used - 1];
		used--;
	}

	export void removeStable()(size_t elemNum) {
		used--;
		foreach (i; 0 .. used) {
			array[i] = array[i + 1];
		}
	}

	export bool tryRemoveElement()(T elem) {
		foreach (i, ref el; array[0 .. used]) {
			if (el == elem) {
				remove(i);
				return true;
			}
		}
		return false;
	}

	export void removeElement()(T elem) {
		bool ok = tryRemoveElement(elem);
		assert(ok, "There is no such an element in vector");
	}

	export ref T opIndex(size_t elemNum) const {
		//debug assert(elemNum < used, "Range violation [index]");
		return *cast(T*)&array.ptr[elemNum];
	}

	export auto opSlice() {
		return array.ptr[0 .. used];
	}

	export T[] opSlice(size_t x, size_t y) {
		assert(y <= used);
		return array.ptr[x .. y];
	}

	export size_t opDollar() {
		return used;
	}

	export void opAssign(X)(X[] slice) {
		reset();
		this ~= slice;
	}

	export void opOpAssign(string op)(T obj) {
		//static assert(op == "~");
		add(obj);
	}

	export void opOpAssign(string op, X)(X[] obj) {
		//static assert(op == "~");
		add(obj);
	}

	export void opIndexAssign()(T obj, size_t elemNum) {
		assert(elemNum < used, "Range viloation");
		array[elemNum] = obj;
	}

	export void opSliceAssign()(T[] obj, size_t a, size_t b) {
		assert(b <= used && a <= b, "Range viloation");
		array.ptr[a .. b] = obj;
	}

	export bool opEquals()(auto ref const Vector!(T) r) const {
		return used == r.used && array.ptr[0 .. used] == r.array.ptr[0 .. r.used];
	}
}