#pragma once

#include "glContainer.h"
#include "pack.h"
#include <cassert>
#include <special_members.h>
#include <utility>

template<typename T> class InstanceVertices : protected glContainer<T> {
	using base = glContainer<T>;

public:
	class [[nodiscard]] InstanceProxy {
	public:
		InstanceProxy(InstanceVertices * iv, std::size_t idx) : instances {iv}, index {idx} { }

		InstanceProxy(InstanceProxy && other) : instances {std::exchange(other.instances, nullptr)}, index {other.index}
		{
		}

		NO_COPY(InstanceProxy);

		~InstanceProxy()
		{
			if (instances) {
				instances->release(index);
			}
		}

		InstanceProxy &
		operator=(InstanceProxy && other)
		{
			if (instances) {
				instances->release(index);
			}
			instances = std::exchange(other.instances, nullptr);
			index = other.index;
			return *this;
		}

		template<typename U>
		T &
		operator=(U && v)
		{
			return instances->lookup(index) = std::forward<U>(v);
		}

		[[nodiscard]] operator T &()
		{
			return instances->lookup(index);
		}

		[[nodiscard]] operator const T &() const
		{
			return instances->lookup(index);
		}

		[[nodiscard]] T *
		get()
		{
			return &instances->lookup(index);
		}

		[[nodiscard]] const T *
		get() const
		{
			return &instances->lookup(index);
		}

		[[nodiscard]] T *
		operator->()
		{
			return get();
		}

		[[nodiscard]] const T *
		operator->() const
		{
			return get();
		}

		[[nodiscard]] T &
		operator*()
		{
			return instances->lookup(index);
		}

		[[nodiscard]] const T &
		operator*() const
		{
			return instances->lookup(index);
		}

	private:
		InstanceVertices<T> * instances;
		std::size_t index;
	};

	template<typename... Params>
	[[nodiscard]] InstanceProxy
	acquire(Params &&... params)
	{
		if (!unused.empty()) {
			auto idx = unused.back();
			unused.pop_back();
			index[idx] = base::size();
			base::emplace_back(std::forward<Params>(params)...);
			return InstanceProxy {this, idx};
		}
		index.emplace_back(base::size());
		base::emplace_back(std::forward<Params>(params)...);
		return InstanceProxy {this, index.size() - 1};
	}

	using base::bufferName;

	[[nodiscard]] auto
	size() const
	{
		base::unmap();
		return base::size();
	}

protected:
	friend InstanceProxy;

	void
	release(const size_t pidx)
	{
		if (base::size() - 1 != index[pidx]) {
			lookup(pidx) = std::move(base::back());
			*std::find_if(index.begin(), index.end(), [this, old = base::size() - 1](const auto & i) {
				return i == old && !std::binary_search(unused.begin(), unused.end(), &i - index.data());
			}) = index[pidx];
		}
		base::pop_back();
		if (pidx == index.size() - 1) {
			index.pop_back();
		}
		else {
			// Remember p.index is free index now, keeping it sorted
			unused.insert(std::upper_bound(unused.begin(), unused.end(), pidx), pidx);
		}
	}

	[[nodiscard]] T &
	lookup(size_t pindex)
	{
		return base::data()[index[pindex]];
	}

	//  Index into buffer given to nth proxy
	std::vector<size_t> index;
	//  List of free spaces in index
	std::vector<size_t> unused;
};