/*! \file piparsehelper.h
 * \brief Helper class to automate structs receive
*/
/*
	PIP - Platform Independent Primitives
	Helper class to automate structs receive
	Ivan Pelipenko peri4ko@yandex.ru

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIPARSEHELPER_H
#define PIPARSEHELPER_H

#include "pip_io_utils_export.h"
#include "piobject.h"


/** \class PIParseHelper
 * \brief Helper class to automate structs receive
 *
 *
 * \section PIParseHelper_synopsis Synopsis
 * This class helps to deserialize and invoke neccessarily methods.
 *
 * Data packets with header and various data types can be automated by this class.
 * Every key value mapped to event handler or lambda-function.
 *
 * This class can remove \b switch-case with deserialization code and
 * replace it with several \a assign() calls, binded to ready-to-use event handlers.
 * Moreover data type automatic takes from event handler or lambda argument. One should
 * only make \"PIByteArray & operator <<()\" with used types, deserialization will be
 * performed by %PIParseHelper.
 *
 *
 * \section PIParseHelper_usage Usage
 * There are two variants: subclassing and aggregating.
 *
 * ### Subclass
 *
 * Inherit your class from %PIParseHelper and construct it with \b this.
 *
 * In \a assign() methods you can use event handlers with 0 or 1 arguments,
 * using HANDLER() macro.
 *
 * ### Aggregate
 *
 * Create instance of %PIParseHelper and construct it with target object.
 *
 * In \a assign() methods you can use event handlers with 0 or 1 arguments,
 * using HANDLER() macro in target object namespace (e.g. \"o.HANDLER(slot)\").
 *
 *
 * \section PIParseHelper_lambda Lambda-functions
 * Assign methods that receive lambda-functions can`t accept direct lambda (\"[](){}\")
 * with template type deducation because of C++ specific. E.g.
 * \code assign(1, [this](SomeStruct s){}) \endcode
 * doesn`t compile, but these variants allowed:
 * \code assign(1, std::function<void(SomeStruct)>([this](SomeStruct s){})) \endcode
 * \code assign<SomeStruct>(1, [this](SomeStruct s){}) \endcode
 *
 *
 * \section PIParseHelper_examples Examples
 * First example describes subclass variant. As one can see, it`s a single place to change
 * type of received data - event handler argument.
 * \snippet piparsehelper.cpp 0
 *
 * Second example show separate variant:
 * \snippet piparsehelper.cpp 1
 *
 **/

template <typename Key>
class PIParseHelper {
public:

	//! \brief Construct %PIParseHelper with target object \"p\"
	PIParseHelper(PIObject * p): parent(p) {}


	//! \brief Assign key \"key\" to event handler \"handler\" with 1 argument
	template <typename T, typename Ret>
	void assign(Key key, Ret(*handler)(void*,T)) {
		if (!parent) return;
		auto mf = PIObject::__meta_data().value(parent->classNameID());
		auto it = mf.eh_func.makeIterator();
		while (it.hasNext()) {
			it.next();
			if (it.value().addr == handler) {
				void * addr = it.value().addr;
				auto func = [addr](PIObject * o, PIByteArray data){
					T v; data >> v;
					((void(*)(void*, T))addr)(o, v);
				};
				functions[key] << func;
				break;
			}
		}
	}


	//! \brief Assign key \"key\" to event handler \"handler\" without arguments
	template <typename Ret>
	void assign(Key key, Ret(*handler)(void*)) {
		if (!parent) return;
		auto mf = PIObject::__meta_data().value(parent->classNameID());
		auto it = mf.eh_func.makeIterator();
		while (it.hasNext()) {
			it.next();
			if (it.value().addr == handler) {
				void * addr = it.value().addr;
				auto func = [addr](PIObject * o, PIByteArray ){
					((void(*)(void*))addr)(o);
				};
				functions[key] << func;
				break;
			}
		}
	}


	//! \brief Assign key \"key\" to lambda-function \"func\" with 1 argument
	//! \note Important! Direct lambda functions are not allowed, see \ref PIParseHelper_lambda
	template <typename T>
	void assign(Key key, std::function<void(T)> func) {
		if (!parent) return;
		auto lf = [func](PIObject * , PIByteArray data){
			if (!data.isEmpty()) {
				T v; data >> v;
				func(v);
			}
		};
		functions[key] << lf;
	}


	//! \brief Assign key \"key\" to lambda-function \"func\" without arguments
	//! \note Important! Direct lambda functions are not allowed, see \ref PIParseHelper_lambda
	void assign(Key key, std::function<void()> func) {
		if (!parent) return;
		auto lf = [func](PIObject * , PIByteArray ){
			func();
		};
		functions[key] << lf;
	}


	//! \brief Deserialize data and invoke assigned to \"key\" methods
	void parse(Key key, PIByteArray ba) {
		if (!parent) return;
		auto fl = functions.value(key);
		for (auto f: fl)
			f(parent, ba);
	}

private:
	PIMap<Key, PIVector<std::function<void(PIObject *, PIByteArray)>>> functions;
	PIObject * parent;

};


#endif // PIPARSEHELPER_H
