// license:BSD-3-Clause
// copyright-holders:Couriersud

#ifndef PSTATE_H_
#define PSTATE_H_

///
/// \file pstate.h
///

#include "pstring.h"
#include "ptypes.h"

#include <array>
#include <memory>
#include <vector>

// ----------------------------------------------------------------------------------------
// state saving ...
// ----------------------------------------------------------------------------------------

namespace plib {
class state_manager_t
{
public:

	struct datatype_t
	{
		datatype_t(std::size_t bsize, bool bintegral, bool bfloat)
		: m_size(bsize), m_is_integral(bintegral), m_is_float(bfloat), m_is_custom(false)
		{}
		explicit datatype_t(bool bcustom)
		: m_size(0), m_is_integral(false), m_is_float(false), m_is_custom(bcustom)
		{}

		std::size_t size() const noexcept { return m_size; }
		bool is_integral() const noexcept { return m_is_integral; }
		bool is_float()    const noexcept { return m_is_float; }
		bool is_custom()   const noexcept { return m_is_custom; }

	private:
		std::size_t m_size;
		bool m_is_integral;
		bool m_is_float;
		bool m_is_custom;
	};

	template<typename T>
	static datatype_t dtype()
	{
		return datatype_t(sizeof(T),
				plib::is_integral<T>::value || std::is_enum<T>::value,
				plib::is_floating_point<T>::value);
	}

	struct callback_t
	{
	public:
		using list_t = std::vector<callback_t *>;

		virtual void register_state(state_manager_t &manager, const pstring &module) = 0;
		virtual void on_pre_save(state_manager_t &manager) = 0;
		virtual void on_post_load(state_manager_t &manager) = 0;
	protected:
		callback_t() = default;
		virtual ~callback_t() = default;
		PCOPYASSIGNMOVE(callback_t, default)
	};

	struct entry_t
	{
		using list_t = std::vector<entry_t>;

		entry_t(const pstring &stname, const datatype_t &dt, const void *owner,
				const std::size_t count, void *ptr)
		: m_name(stname), m_dt(dt), m_owner(owner), m_callback(nullptr), m_count(count), m_ptr(ptr) { }

		entry_t(const pstring &stname, const void *owner, callback_t *callback)
		: m_name(stname), m_dt(datatype_t(true)), m_owner(owner), m_callback(callback), m_count(0), m_ptr(nullptr) { }

		pstring name() const noexcept { return m_name; }
		datatype_t dt() const noexcept { return m_dt; }
		const void * owner() const noexcept { return m_owner; }
		callback_t * callback() const noexcept { return m_callback; }
		std::size_t count() const noexcept { return m_count; }
		void * ptr() const noexcept { return m_ptr; }

	private:
		pstring             m_name;
		datatype_t          m_dt;
		const void *        m_owner;
		callback_t *        m_callback;
		std::size_t         m_count;
		void *              m_ptr;
	};

	state_manager_t() = default;

	struct saver_t
	{
		saver_t(state_manager_t &sm, const void *owner, const pstring &membername)
		: m_sm(sm)
		, m_owner(owner)
		, m_membername(membername)
		{ }

		template <typename XS>
		void save_item(XS &xstate, const pstring &itemname)
		{
			m_sm.save_item(m_owner, xstate, m_membername + "." + itemname);
		}

		state_manager_t &m_sm;
		const void * m_owner;
		const pstring m_membername;
	};

	template<typename C>
	void save_item(const void *owner, C &state, const pstring &stname)
	{
		save_item_dispatch(owner, state, stname);
	}

	template<typename C, std::size_t N>
	void save_item(const void *owner, C (&state)[N], const pstring &stname) // NOLINT(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
	{
		save_state_ptr(owner, stname, dtype<C>(), N, &(state[0]));
	}

	template<typename C>
	void save_item(const void *owner, C *state, const pstring &stname, const std::size_t count)
	{
		save_state_ptr(owner, stname, dtype<C>(), count, state);
	}

	template<typename C, typename A>
	void save_item(const void *owner, std::vector<C, A> &v, const pstring &stname)
	{
		save_state_ptr(owner, stname, dtype<C>(), v.size(), v.data());
	}

	template<typename C, std::size_t N>
	void save_item(const void *owner, std::array<C, N> &a, const pstring &stname)
	{
		save_state_ptr(owner, stname, dtype<C>(), N, a.data());
	}

	void save_state_ptr(const void *owner, const pstring &stname, const datatype_t &dt, const std::size_t count, void *ptr)
	{
		m_save.emplace_back(stname, dt, owner, count, ptr);
	}

	void pre_save()
	{
		for (auto & s : m_custom)
			s.callback()->on_pre_save(*this);
	}

	void post_load()
	{
		for (auto & s : m_custom)
			s.callback()->on_post_load(*this);
	}

	void remove_save_items(const void *owner)
	{
		auto i = m_save.end();
		while (i != m_save.begin())
		{
			i--;
			if (i->owner() == owner)
				i = m_save.erase(i);
		}
		i = m_custom.end();
		while (i > m_custom.begin())
		{
			i--;
			if (i->owner() == owner)
				i = m_custom.erase(i);
		}
	}

	std::vector<const entry_t *> save_list() const
	{
		std::vector<const entry_t *> ret;
		for (const auto &i : m_save)
			ret.push_back(&i);
		return ret;
	}

protected:

private:

	template<typename C>
	std::enable_if_t<plib::is_integral<C>::value || std::is_enum<C>::value
			|| plib::is_floating_point<C>::value>
	save_item_dispatch(const void *owner, C &state, const pstring &stname)
	{
		save_state_ptr( owner, stname, dtype<C>(), 1, &state);
	}

	template<typename C>
	std::enable_if_t<!(plib::is_integral<C>::value || std::is_enum<C>::value
			|| plib::is_floating_point<C>::value)>
	save_item_dispatch(const void *owner, C &state, const pstring &stname)
	{
		saver_t sav(*this, owner, stname);
		state.save_state(sav);
	}

	entry_t::list_t m_save;
	entry_t::list_t m_custom;

};

template<>
inline void state_manager_t::save_item(const void *owner, callback_t &state, const pstring &stname)
{
	m_custom.emplace_back(stname, owner, &state);
	state.register_state(*this, stname);
}


} // namespace plib

#endif // PSTATE_H_
