/*************************************************************************
*  Copyright (C) 2004 by Janek Kozicki                                   *
*  cosurgi@berlios.de                                                    *
*                                                                        *
*  This program is free software; it is licensed under the terms of the  *
*  GNU General Public License v2 or later. See file LICENSE for details. *
*************************************************************************/

#pragma once

#include <lib/factory/ClassFactory.hpp>
#include <lib/multimethods/Indexable.hpp>
#include <lib/serialization/Serializable.hpp>

#include <lib/compatibility/LokiCompatibility.hpp>
#include <boost/mpl/size.hpp>

#include <list>
#include <ostream>
#include <string>
#include <vector>

namespace yade { // Cannot have #include directive inside.


struct DynLibDispatcher_Item2D {
	int         ix1, ix2;
	std::string functorName;
	DynLibDispatcher_Item2D(int a, int b, std::string c)
	        : ix1(a)
	        , ix2(b)
	        , functorName(c) {};
};
struct DynLibDispatcher_Item1D {
	int         ix1;
	std::string functorName;
	DynLibDispatcher_Item1D(int a, std::string c)
	        : ix1(a)
	        , functorName(c) {};
};
///
/// base classes involved in multiple dispatch must be derived from Indexable
///

/// base template for all dispatchers								///

template <
        class BaseClassList, //	a typelist with base classess involved in the dispatch (or single class, for 1D )
        // 		FIXME: should use shared_ptr references, like this: DynLibDispatcher< TYPELIST_2( shared_ptr<PhysicalAction>& , shared_ptr<Body>& ) , ....
        class Executor,   //	class which gives multivirtual function
        class ResultType, //	type returned by multivirtual function
        class TList,
        //	typelist of arguments passed to multivirtual function
        //	WARNING: first arguments must be shared_ptr<BaseClass1> and shared_ptr<BaseClass2>, for details see FunctorWrapper

        bool autoSymmetry = true
        /*true - the function called is always the same,
						only order of arguments is rearranged
						to make correct function call, 
						only go() is called
				
					false - the function called is always different.
						arguments order is not rearranged
						go(), and goReverse() are called, respectively*/
        >
class DynLibDispatcher {
	// this template recursively defines a type for callBacks matrix, with required number of dimensions
private:
	template <typename T, size_t depth> struct RecursiveVector {
		using vec = std::vector<typename RecursiveVector<T, depth - 1>::vec>;
	};
	template <typename T> struct RecursiveVector<T, 0> {
		using vec = T;
	};

	typedef typename ::boost::mpl::at_c<BaseClassList, 0>::type BaseClass1; // 1D
	typedef typename ::boost::mpl::at_c<BaseClassList, 1>::type BaseClass2; // 2D

	using MatrixType    = typename RecursiveVector<shared_ptr<Executor>, boost::mpl::size<BaseClassList>::value>::vec;
	using MatrixIntType = typename RecursiveVector<int, boost::mpl::size<BaseClassList>::value>::vec;

	MatrixType    callBacks;     // multidimensional matrix that stores functors ( 1D, 2D, 3D, 4D, ....)
	MatrixIntType callBacksInfo; // multidimensional matrix for extra information about functors in the matrix
	                             // currently used to remember if it is reversed functor

public:
	DynLibDispatcher()
	{
		// FIXME - static_assert( typeid(BaseClass1) == typeid(Parm1) ); // 1D
		// FIXME - static_assert( typeid(BaseClass2) == typeid(Parm2) ); // 2D
		clearMatrix();
	};

	void clearMatrix()
	{
		callBacks.clear();
		callBacksInfo.clear();
	}

	shared_ptr<Executor> getExecutor(shared_ptr<BaseClass1>& arg1)
	{
		int ix1;
		if (arg1->getClassIndex() < 0)
			throw runtime_error(
			        "No functor for type " + arg1->getClassName() + " (index " + boost::lexical_cast<string>(arg1->getClassIndex())
			        + "), since the index is invalid (negative).");
		if (locateMultivirtualFunctor1D(ix1, arg1)) return callBacks[ix1];
		return shared_ptr<Executor>();
	}

	shared_ptr<Executor> getExecutor(shared_ptr<BaseClass1>& arg1, shared_ptr<BaseClass2>& arg2)
	{
		if (arg1->getClassIndex() < 0 || arg2->getClassIndex() < 0)
			throw runtime_error(
			        "No functor for types " + arg1->getClassName() + " (index " + boost::lexical_cast<string>(arg1->getClassIndex()) + ") + "
			        + arg2->getClassName() + " (index " + boost::lexical_cast<string>(arg2->getClassIndex())
			        + "), since some of the indices is invalid (negative).");
		int ix1, ix2;
		if (locateMultivirtualFunctor2D(ix1, ix2, arg1, arg2)) return callBacks[ix1][ix2];
		return shared_ptr<Executor>();
	}

	shared_ptr<Executor> getFunctor1D(shared_ptr<BaseClass1>& base1) { return getExecutor(base1); }
	/* Return pointer to the functor for two base classes given. Swap is true if the dispatch objects should be swapped before calling Executor::go. */
	shared_ptr<Executor> getFunctor2D(shared_ptr<BaseClass1>& base1, shared_ptr<BaseClass2>& base2, bool& swap)
	{
		int ix1, ix2;
		if (!locateMultivirtualFunctor2D(ix1, ix2, base1, base2)) return shared_ptr<Executor>();
		swap = (bool)(callBacksInfo[ix1][ix2]);
		return callBacks[ix1][ix2];
	}

	/*! Return representation of the dispatch matrix as vector of int,string (i.e. index,functor name) */
	vector<DynLibDispatcher_Item1D> dataDispatchMatrix1D()
	{
		vector<DynLibDispatcher_Item1D> ret;
		for (size_t i = 0; i < callBacks.size(); i++) {
			if (callBacks[i]) ret.push_back(DynLibDispatcher_Item1D(i, callBacks[i]->getClassName()));
		}
		return ret;
	}
	/*! Return representation of the dispatch matrix as vector of int,int,string (i.e. index1,index2,functor name) */
	vector<DynLibDispatcher_Item2D> dataDispatchMatrix2D()
	{
		vector<DynLibDispatcher_Item2D> ret;
		for (size_t i = 0; i < callBacks.size(); i++) {
			for (size_t j = 0; j < callBacks[i].size(); j++) {
				if (callBacks[i][j]) { ret.push_back(DynLibDispatcher_Item2D(i, j, callBacks[i][j]->getClassName())); }
			}
		}
		return ret;
	}

	/*! Dump 1d dispatch matrix to given stream. */
	std::ostream& dumpDispatchMatrix1D(std::ostream& out, const string& prefix = "")
	{
		for (size_t i = 0; i < callBacks.size(); i++) {
			if (callBacks[i]) out << prefix << i << " -> " << callBacks[i]->getClassName() << std::endl;
		}
		return out;
	}
	/*! Dump 2d dispatch matrix to given stream. */
	std::ostream& dumpDispatchMatrix2D(std::ostream& out, const string& prefix = "")
	{
		for (size_t i = 0; i < callBacks.size(); i++) {
			for (size_t j = 0; j < callBacks.size(); j++) {
				if (callBacks[i][j]) out << prefix << i << "+" << j << " -> " << callBacks[i][j]->getClassName() << std::endl;
			}
		}
		return out;
	}

public:
	void add1DEntry(string baseClassName, shared_ptr<Executor> executor)
	{
		// create base class, to access its index. (we can't access static variable, because
		// the class might not exist in memory at all, and we have to load dynamic library,
		// so that a static variable is created and accessible)
		shared_ptr<BaseClass1> baseClass = YADE_PTR_CAST<BaseClass1>(ClassFactory::instance().createShared(baseClassName));
		// this is a strange tweak without which it won't work.
		shared_ptr<Indexable> base = YADE_PTR_CAST<Indexable>(baseClass);

		assert(base);
		const int& index = base->getClassIndex();
		if (index == -1) std::cerr << "--------> Did you forget to call createIndex(); in constructor?\n";
		assert(index != -1);

		int maxCurrentIndex = base->getMaxCurrentlyUsedClassIndexOfKin();
		callBacks.resize(maxCurrentIndex + 1); // make sure that there is a place for new Functor

		callBacks[index] = executor;
	};


public:
	void add2DEntry(string baseClassName1, string baseClassName2, shared_ptr<Executor> executor)
	{
		shared_ptr<BaseClass1> baseClass1 = YADE_PTR_CAST<BaseClass1>(ClassFactory::instance().createShared(baseClassName1));
		shared_ptr<BaseClass2> baseClass2 = YADE_PTR_CAST<BaseClass2>(ClassFactory::instance().createShared(baseClassName2));
		shared_ptr<Indexable>  base1      = YADE_PTR_CAST<Indexable>(baseClass1);
		shared_ptr<Indexable>  base2      = YADE_PTR_CAST<Indexable>(baseClass2);

		assert(base1);
		assert(base2);

		const int& index1 = base1->getClassIndex();
		if (index1 == -1) std::cerr << "--------> Did you forget to call createIndex(); in constructor?\n";
		assert(index1 != -1);

		const int& index2 = base2->getClassIndex();
		if (index2 == -1) std::cerr << "--------> Did you forget to call createIndex(); in constructor?\n";
		assert(index2 != -1);

		if (typeid(BaseClass1) == typeid(BaseClass2))
			assert(base1->getMaxCurrentlyUsedClassIndexOfKin() == base2->getMaxCurrentlyUsedClassIndexOfKin());

		int maxCurrentIndex1 = base1->getMaxCurrentlyUsedClassIndexOfKin();
		int maxCurrentIndex2 = base2->getMaxCurrentlyUsedClassIndexOfKin();

		callBacks.resize(maxCurrentIndex1 + 1); // resizing callBacks table
		callBacksInfo.resize(maxCurrentIndex1 + 1);
		for (auto ci = callBacks.begin(); ci != callBacks.end(); ++ci)
			ci->resize(maxCurrentIndex2 + 1);
		for (auto cii = callBacksInfo.begin(); cii != callBacksInfo.end(); ++cii)
			cii->resize(maxCurrentIndex2 + 1);

		if (typeid(BaseClass1) == typeid(BaseClass2)) // both base classes are the same
		{
			callBacks[index2][index1] = executor;
			callBacks[index1][index2] = executor;

			string order        = baseClassName1 + " " + baseClassName2;
			string reverseOrder = baseClassName2 + " " + baseClassName1;

			if (autoSymmetry || executor->checkOrder() == order) // if you want autoSymmetry, you don't have to DEFINE_FUNCTOR_ORDER_2D
			{
				callBacksInfo[index2][index1] = 1; // this is reversed call
				callBacksInfo[index1][index2] = 0;
			} else if (executor->checkOrder() == reverseOrder) {
				callBacksInfo[index2][index1] = 0;
				callBacksInfo[index1][index2] = 1; // this is reversed call
			} else {
				throw std::runtime_error(("Multimethods: checkOrder: undefined dispatch order for " + executor->getClassName()).c_str());
			}
		} else // classes are different, no symmetry possible
		{
			callBacks[index1][index2]     = executor;
			callBacksInfo[index1][index2] = 0;
		}
	}


	bool locateMultivirtualFunctor1D(int& index, shared_ptr<BaseClass1>& base)
	{
		if (callBacks.empty()) return false;
		index = base->getClassIndex();
		assert(index >= 0 && (unsigned int)(index) < callBacks.size());
		if (callBacks[index]) return true;

		int depth     = 1;
		int index_tmp = base->getBaseClassIndex(depth);
		while (1)
			if (index_tmp == -1) return false;
			else if (callBacks[index_tmp]) {
				// BEGIN FIXME - this should be a separate function to resize stuff
				//cerr << index << " " << index_tmp << endl;
				if (callBacksInfo.size() <= (unsigned int)index) callBacksInfo.resize(index + 1);
				if (callBacks.size() <= (unsigned int)index) callBacks.resize(index + 1);
				// END
				callBacksInfo[index] = callBacksInfo[index_tmp];
				callBacks[index]     = callBacks[index_tmp];
				return true;
			} else
				index_tmp = base->getBaseClassIndex(++depth);

		return false; // FIXME - this line should be not needed
	}

	bool locateMultivirtualFunctor2D(int& index1, int& index2, shared_ptr<BaseClass1>& base1, shared_ptr<BaseClass2>& base2)
	{
//#define _DISP_TRACE(msg) cerr<<"@DT@"<<__LINE__<<" "<<msg<<endl;
#define _DISP_TRACE(msg)
		if (callBacks.empty()) return false;
		index1 = base1->getClassIndex();
		index2 = base2->getClassIndex();
		assert(index1 >= 0);
		assert(index2 >= 0);
		assert((unsigned int)(index1) < callBacks.size());
		assert((unsigned int)(index2) < callBacks[index1].size());
		_DISP_TRACE("arg1: " << base1->getClassName() << "=" << index1 << "; arg2: " << base2->getClassName() << "=" << index2)
		/* This is python code for the algorithm:

				def ff(x,sum): print x,sum-x,sum
				for dist in range(0,5):
					for ix1 in range(0,dist+1): ff(ix1,dist)

				Increase depth sum from 0 up and look for possible matches, of which sum of distances beween the argument and the declared functor arg type equals depth.
				
				Two matches are considered euqally good (ambiguous) if they have the same depth. That raises exception.

				If both indices are negative (reached the top of hierarchy for that indexable type) and nothing has been found for given depth, raise exception (undefined dispatch).

				FIXME: by the original design, callBacks don't distinguish between dispatch that was already looked for,
				but is undefined and dispatch that was never looked for before. This means that there can be lot of useless lookups;
				e.g. if MetaInteractingGeometry2AABB is not in BoundingVoumeMetaEngine, it is looked up at every step.

			*/
		if (callBacks[index1][index2]) {
			_DISP_TRACE("Direct hit at [" << index1 << "][" << index2 << "] → " << callBacks[index1][index2]->getClassName());
			return true;
		}
		int foundIx1, foundIx2;
		int maxDp1 = -1, maxDp2 = -1;

		for (int dist = 1;; dist++) {
			bool distTooBig = true;
			foundIx1 = foundIx2 = -1; // found no dispatch at this depth yet
			for (int dp1 = 0; dp1 <= dist; dp1++) {
				int dp2 = dist - dp1;
				if ((maxDp1 >= 0 && dp1 > maxDp1) || (maxDp2 >= 0 && dp2 > maxDp2)) continue;
				_DISP_TRACE(" Trying indices with depths " << dp1 << " and " << dp2 << ", dist=" << dist);
				int ix1 = dp1 > 0 ? base1->getBaseClassIndex(dp1) : index1, ix2 = dp2 > 0 ? base2->getBaseClassIndex(dp2) : index2;
				if (ix1 < 0) maxDp1 = dp1;
				if (ix2 < 0) maxDp2 = dp2;
				if (ix1 < 0 || ix2 < 0) continue; // hierarchy height exceeded in either dimension
				distTooBig = false;
				if (callBacks[ix1][ix2]) {
					if (foundIx1 != -1
					    && callBacks[foundIx1][foundIx2]
					            != callBacks
					                    [ix1]
					                    [ix2]) { // we found a callback, but there already was one at this distance and it was different from the current one
						cerr << __FILE__ << ":" << __LINE__ << ": ambiguous 2d dispatch ("
						     << "arg1=" << base1->getClassName() << ", arg2=" << base2->getClassName() << ", distance=" << dist
						     << "), dispatch matrix:" << endl;
						dumpDispatchMatrix2D(cerr, "AMBIGUOUS: ");
						throw runtime_error("Ambiguous dispatch.");
					}
					foundIx1                      = ix1;
					foundIx2                      = ix2;
					callBacks[index1][index2]     = callBacks[ix1][ix2];
					callBacksInfo[index1][index2] = callBacksInfo[ix1][ix2];
					_DISP_TRACE("Found callback [" << ix1 << "][" << ix2 << "] → " << callBacks[ix1][ix2]->getClassName());
				}
			}
			if (foundIx1 != -1) return true;
			if (distTooBig) {
				_DISP_TRACE("Undefined dispatch, dist=" << dist);
				return false; /* undefined dispatch */
			}
		}
	};

	// calling multivirtual function, 1D
	template <typename... Args> ResultType operator()(shared_ptr<BaseClass1>& base, Args... args)
	{
		int index;
		if (locateMultivirtualFunctor1D(index, base)) return (callBacks[index])->go(base, args...);
		else
			return ResultType();
	}


	// calling multivirtual function, 2D,
	// symmetry handling in private struct
private:
	template <bool useSymmetry, class BaseClassTrait1, class BaseClassTrait2, typename... Args> struct InvocationTraits {
		static ResultType doDispatch(shared_ptr<Executor>& ex, shared_ptr<BaseClassTrait1> base1, shared_ptr<BaseClassTrait2> base2, Args... args)
		{
			return ex->goReverse(base1, base2, args...);
		}
	};
	template <class BaseClassTrait, typename... Args> struct InvocationTraits<true, BaseClassTrait, BaseClassTrait, Args...> {
		static ResultType doDispatch(shared_ptr<Executor>& ex, shared_ptr<BaseClassTrait> base1, shared_ptr<BaseClassTrait> base2, Args... args)
		{
			return ex->go(base2, base1, args...);
		}
	};

	// calling multivirtual function, 2D, public interface
public:
	template <typename... Args> ResultType operator()(shared_ptr<BaseClass1>& base1, shared_ptr<BaseClass2>& base2, Args... args)
	{
		int index1, index2;
		if (locateMultivirtualFunctor2D(index1, index2, base1, base2)) {
			if (callBacksInfo[index1][index2]) // reversed
			{
				using CallTraits = InvocationTraits<autoSymmetry, BaseClass1, BaseClass2, Args...>;
				return CallTraits::doDispatch(callBacks[index1][index2], base1, base2, args...);
			} else
				return (callBacks[index1][index2])->go(base1, base2, args...);
		} else
			return ResultType();
	}
};

}; // namespace yade
