#pragma once

#include "utility.h"
#include <cstddef>
#include <iterator>

namespace container
{
using CapacityType = EntityIndex;
using BoolArrayType = u8;

template<typename T, CapacityType capacity>
class Array
{
public:
	using data_type = T;
	using pointer = data_type*;
	using reference = data_type&;
	using const_pointer = const data_type*;
	using const_reference = const data_type&;
	using difference_type = std::ptrdiff_t;
	using iterator_category = std::forward_iterator_tag;

public:
	class Iterator
	{
	public:
		Iterator(data_type* const input)
			: m_ptr(input)
			{ }

		Iterator& operator++()
		{ m_ptr++; return *this; }

		Iterator  operator++(int)
		{ auto it = *this; m_ptr++; return it; }

		Iterator& operator--()
		{ m_ptr--; return *this; }

		Iterator  operator--(int) 
		{ auto it = *this; m_ptr++; return it; }


		reference operator[](const CapacityType index) const { return m_ptr[index]; }
		pointer operator->() const { return m_ptr; }
		reference operator*() const { return *m_ptr; }

		bool operator==(const Iterator& other) const { return m_ptr == other.m_ptr; }
		bool operator!=(const Iterator& other) const { return m_ptr != other.m_ptr; }
	private:
		pointer m_ptr;
	};

	class ConstIterator
	{
	public:
		ConstIterator(data_type* const input)
			: m_ptr(input)
			{ }

		ConstIterator& operator++()
		{ m_ptr++; return *this; }

		ConstIterator  operator++(int)
		{ auto it = *this; m_ptr++; return it; }

		ConstIterator& operator--()
		{ m_ptr--; return *this; }

		ConstIterator  operator--(int) 
		{ auto it = *this; m_ptr++; return it; }


		const_reference operator[](const CapacityType index) const { return m_ptr[index]; }
		const_pointer operator->() const { return m_ptr; }
		const_reference operator*() const { return *m_ptr; }

		bool operator==(const ConstIterator& other) const { return m_ptr == other.m_ptr; }
		bool operator!=(const ConstIterator& other) const { return m_ptr != other.m_ptr; }

	private:
		pointer m_ptr;
	};

public:
	Array() { m_data = new T[capacity]; }

	~Array() { delete[] m_data; }

	Array<T, capacity>(const Array<T, capacity>&) = delete;

	Array(Array&& other)
		: m_data(other.m_data)
	{ 
		m_data = other.m_data;
		other.m_data = nullptr; 
	}


	Array& operator=(Array&& other)
	{
		if (this != &other)
		{
			delete[] m_data;
			m_data = other.m_data;
			other.m_data = nullptr;
		}

		return *this;
	}

	pointer data() { return m_data; }

	const_pointer cosntData() const { return m_data; }

	reference operator[](const CapacityType index) { return m_data[index]; }

	const_reference operator[](const CapacityType index) const { return m_data[index]; }

	pointer operator->() { return m_data; }

	const_pointer operator->() const { return m_data; }

	reference operator*() { return *m_data; }

	const_reference operator*() const { return *m_data; }

	inline constexpr CapacityType size() const { return capacity; }

	Iterator begin() { return Iterator(m_data); }
	Iterator end()   { return Iterator(m_data + capacity); }

	ConstIterator begin() const { return ConstIterator(m_data); }
	ConstIterator end()   const { return ConstIterator(m_data + capacity); }

private:
	pointer m_data;

};


template <CapacityType capacity>
class Array<bool, capacity>
{
public:
	class reference;
	class const_reference;
	using data_type = BoolArrayType;
	using pointer = reference;
	using const_pointer = const_reference;
	using difference_type = std::ptrdiff_t;
	using iterator_category = std::forward_iterator_tag;

public:
	class reference
	{
		friend class Array;
		reference(data_type* const ptr_in, const data_type mask_in)
		: m_ptr(ptr_in)
		, m_mask(mask_in)
		{

		}

	public:
	~reference()
	{ }

	reference(const reference&) = default;

	operator bool () const 
	{
		return !!(*m_ptr & m_mask);
	}

	reference& operator=(const bool input)
	{
		switch ((int)input) 
		{
		case true:
			*m_ptr |= m_mask;
			break;
		case false:
			*m_ptr &= m_mask;
			break;
		}
		return *this;
	}

	reference& operator=(const reference& other)
	{
		return *this = bool(other);
	}

	void flip()
	{
		*m_ptr ^= m_mask;
	}

	private:
		data_type* const m_ptr;
		const data_type m_mask;
	};

	class const_reference
	{ 
		friend class Array;
		const_reference(const data_type* ptr_in, const data_type mask_in)
		: m_ptr(ptr_in)
		, m_mask(mask_in)
		{ }

	public:
		~const_reference()
		{ }

		const_reference(const const_reference&) = delete;

		operator bool () const { return !!(*m_ptr & m_mask); }

		const_reference& operator=(bool) = delete;

		const_reference& operator=(const const_reference& other) = delete;
		const_reference& operator=(const reference& other) = delete;

	private:
		const data_type* const m_ptr;
		const data_type m_mask;
	};


	class Iterator
	{
	public:
		Iterator(data_type* const input, const data_type index)
			: m_ptr(input)
			, m_bitIndex(index)
		{ }

		Iterator& operator++()
		{
			switch (m_bitIndex++) 
			{
			case 7:
				m_bitIndex = 0;
				m_ptr++;
				return *this;
				break;

			default:
				return *this;
			}
		}

		Iterator  operator++(int)
		{
			auto it = *this;
			switch (m_bitIndex++) 
			{
			case 7:
				m_bitIndex = 0;
				m_ptr++;
				return it;
				break;

			default:
				return it;
			}
		}

		Iterator& operator--()
		{
			switch (m_bitIndex--) 
			{
			case 0:
				m_bitIndex = 7;
				m_ptr--;
				return *this;
				break;

			default:
				return *this;
			}
		}

		Iterator  operator--(int)
		{
			auto it = *this;
			switch (m_bitIndex--) 
			{
			case 0:
				m_bitIndex = 7;
				m_ptr--;
				return it;
				break;

			default:
				return it;
			}
		}

		bool operator[](const int) = delete;
		reference operator*() const
		{
			return reference(m_ptr, 1 << m_bitIndex);
		}

		bool operator==(const Iterator& other) const
		{
			return (m_ptr == other.m_ptr) && (m_bitIndex == other.m_bitIndex);
		}

		bool operator!=(const Iterator& other) const
		{
			return (m_ptr != other.m_ptr) || (m_bitIndex != other.m_bitIndex);
		}

	private:
		data_type* m_ptr;
		data_type m_bitIndex{};
	};

	class ConstIterator
	{
	public:
		ConstIterator(const data_type* input, const data_type index)
			: m_ptr(input)
			, m_bitIndex(index)
		{ }

		ConstIterator& operator++()
		{
			switch (m_bitIndex++) 
			{
			case 7:
				m_bitIndex = 0;
				m_ptr++;
				return *this;
				break;

			default:
				return *this;
			}
		}

		ConstIterator  operator++(int)
		{
			auto it = *this;
			switch (m_bitIndex++) 
			{
			case 7:
				m_bitIndex = 0;
				m_ptr++;
				return it;
				break;

			default:
				return it;
			}
		}

		ConstIterator& operator--()
		{
			switch (m_bitIndex--) 
			{
			case 0:
				m_bitIndex = 7;
				m_ptr--;
				return *this;
				break;

			default:
				return *this;
			}
		}

		ConstIterator  operator--(int)
		{
			auto it = *this;
			switch (m_bitIndex--) 
			{
			case 0:
				m_bitIndex = 7;
				m_ptr--;
				return it;
				break;

			default:
				return it;
			}
		}

		bool operator[](const int) = delete;
		const_reference operator*() const 
		{ return const_reference(m_ptr, 1 << m_bitIndex); }

		bool operator==(const ConstIterator& other) const 
		{ return (m_ptr == other.m_ptr) && (m_bitIndex == other.m_bitIndex); }


		bool operator!=(const ConstIterator& other) const 
		{ return (m_ptr != other.m_ptr) || (m_bitIndex != other.m_bitIndex); }

	private:
		const data_type* m_ptr;
		data_type m_bitIndex{};
	};

public:
	Array() { m_data = new data_type[(capacity / 8) + 1]; }

	~Array() { delete[] m_data; }

	Array<bool, capacity>(const Array<bool, capacity>&) = delete;

	Array(Array&& other)
		: m_data(other.m_data)
	{
		m_data = other.m_data;
		other.m_data = nullptr;
	}

	Array& operator=(Array&& other) 
	{
		if (this != &other)
		{
			delete[] m_data;
			m_data = other.m_data;
			other.m_data = nullptr;
		}
		
		return *this;
	}


	//use readAt() instead
	reference operator[](const CapacityType index)
	{ return reference(m_data + (index / 8), 1 << (index % 8)); }


	const_reference operator[](const CapacityType index) const 
	{ return reference(m_data + (index / 8), 1 << (index % 8)); }

	inline constexpr CapacityType size() { return capacity; }

	bool readAt(const CapacityType index) const { return m_data[index / 8] & (1 << (index % 8)); } 

	void changeAt(const CapacityType index, const bool input)
	{
		switch ((int)input) 
		{
		case true:
			m_data[index / 8] |= (1 << (index % 8));
			break;
		case false:
			m_data[index / 8] &= ~(1 << (index % 8));
			break;
		}
	} 

	Iterator begin() { return Iterator(m_data, 0); }
	Iterator end()   { return Iterator(m_data + (capacity / 8), capacity % 8); }

	ConstIterator begin() const { return ConstIterator(m_data, 0); }
	ConstIterator end()   const { return ConstIterator(m_data + (capacity / 8), capacity % 8); }

private:
	data_type* m_data;

};

}//namespace