/**
 * @file
 * @brief Type-safe techniques for binding the first argument of an opaque callback.
 */

/*
 Copyright (C) 2001-2006, William Joseph.
 All Rights Reserved.

 This file is part of GtkRadiant.

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

 GtkRadiant 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 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with GtkRadiant; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#if !defined(INCLUDED_GENERIC_CLOSURE_H)
#define INCLUDED_GENERIC_CLOSURE_H

#include <cstddef>
#include "functional.h"
#include "callbackfwd.h"

template<typename Type>
inline void* convertToOpaque (Type* t)
{
	return t;
}
template<typename Type>
inline void* convertToOpaque (const Type* t)
{
	return const_cast<Type*> (t);
}
template<typename Type>
inline void* convertToOpaque (Type& t)
{
	return &t;
}
template<typename Type>
inline void* convertToOpaque (const Type& t)
{
	return const_cast<Type*> (&t);
}

template<typename Type>
class ConvertFromOpaque
{
};

template<typename Type>
class ConvertFromOpaque<Type&>
{
	public:
		static Type& apply (void* p)
		{
			return *static_cast<Type*> (p);
		}
};

template<typename Type>
class ConvertFromOpaque<const Type&>
{
	public:
		static const Type& apply (void* p)
		{
			return *static_cast<Type*> (p);
		}
};

template<typename Type>
class ConvertFromOpaque<Type*>
{
	public:
		static Type* apply (void* p)
		{
			return static_cast<Type*> (p);
		}
};

template<typename Type>
class ConvertFromOpaque<const Type*>
{
	public:
		static const Type* apply (void* p)
		{
			return static_cast<Type*> (p);
		}
};

template<typename Caller>
class BindFirstOpaque
{
		typedef typename Caller::first_argument_type FirstBound;
		FirstBound firstBound;
	public:
		typedef typename Caller::result_type result_type;
		explicit BindFirstOpaque (FirstBound firstBound) :
			firstBound(firstBound)
		{
		}
		result_type operator() () const
		{
			return Caller::call(firstBound);
		}
		FirstBound getBound () const
		{
			return firstBound;
		}
		static result_type thunk (void* environment)
		{
			return Caller::call(ConvertFromOpaque<FirstBound>::apply(environment));
		}
		void* getEnvironment () const
		{
			return convertToOpaque(firstBound);
		}
};

template<typename Caller>
class BindFirstOpaque1
{
		typedef typename Caller::first_argument_type FirstBound;
		FirstBound firstBound;
	public:
		typedef typename Caller::second_argument_type first_argument_type;
		typedef typename Caller::result_type result_type;
		explicit BindFirstOpaque1 (FirstBound firstBound) :
			firstBound(firstBound)
		{
		}
		result_type operator() (first_argument_type a1) const
		{
			return Caller::call(firstBound, a1);
		}
		FirstBound getBound () const
		{
			return firstBound;
		}
		static result_type thunk (void* environment, first_argument_type a1)
		{
			return Caller::call(ConvertFromOpaque<FirstBound>::apply(environment), a1);
		}
		void* getEnvironment () const
		{
			return convertToOpaque(firstBound);
		}
};

template<typename Caller>
class BindFirstOpaque2
{
		typedef typename Caller::first_argument_type FirstBound;
		FirstBound firstBound;
	public:
		typedef typename Caller::second_argument_type first_argument_type;
		typedef typename Caller::third_argument_type second_argument_type;
		typedef typename Caller::result_type result_type;
		explicit BindFirstOpaque2 (FirstBound firstBound) :
			firstBound(firstBound)
		{
		}
		result_type operator() (first_argument_type a1, second_argument_type a2) const
		{
			return Caller::call(firstBound, a1, a2);
		}
		FirstBound getBound () const
		{
			return firstBound;
		}
		static result_type thunk (void* environment, first_argument_type a1, second_argument_type a2)
		{
			return Caller::call(ConvertFromOpaque<FirstBound>::apply(environment), a1, a2);
		}
		void* getEnvironment () const
		{
			return convertToOpaque(firstBound);
		}
};

template<typename Caller>
class BindFirstOpaque3
{
		typedef typename Caller::first_argument_type FirstBound;
		FirstBound firstBound;
	public:
		typedef typename Caller::second_argument_type first_argument_type;
		typedef typename Caller::third_argument_type second_argument_type;
		typedef typename Caller::fourth_argument_type third_argument_type;
		typedef typename Caller::result_type result_type;
		explicit BindFirstOpaque3 (FirstBound firstBound) :
			firstBound(firstBound)
		{
		}
		result_type operator() (first_argument_type a1, second_argument_type a2, third_argument_type a3) const
		{
			return Caller::call(firstBound, a1, a2, a3);
		}
		FirstBound getBound () const
		{
			return firstBound;
		}
		static result_type thunk (void* environment, first_argument_type a1, second_argument_type a2,
				third_argument_type a3)
		{
			return Caller::call(ConvertFromOpaque<FirstBound>::apply(environment), a1, a2, a3);
		}
		void* getEnvironment () const
		{
			return convertToOpaque(firstBound);
		}
};

template<typename Thunk_>
class CallbackBase
{
		void* m_environment;
		Thunk_ m_thunk;
	public:
		typedef Thunk_ Thunk;
		CallbackBase (void* environment, Thunk function) :
			m_environment(environment), m_thunk(function)
		{
		}
		void* getEnvironment () const
		{
			return m_environment;
		}
		Thunk getThunk () const
		{
			return m_thunk;
		}
};

template<typename Thunk>
inline bool operator== (const CallbackBase<Thunk>& self, const CallbackBase<Thunk>& other)
{
	return self.getEnvironment() == other.getEnvironment() && self.getThunk() == other.getThunk();
}
template<typename Thunk>
inline bool operator!= (const CallbackBase<Thunk>& self, const CallbackBase<Thunk>& other)
{
	return !(self == other);
}
template<typename Thunk>
inline bool operator< (const CallbackBase<Thunk>& self, const CallbackBase<Thunk>& other)
{
	return self.getEnvironment() < other.getEnvironment() || (!(other.getEnvironment() < self.getEnvironment())
			&& self.getThunk() < other.getThunk());
}

/**
 * @brief Combines a void pointer with a pointer to a function which operates on a void pointer.
 * Use with the callback constructors MemberCaller, ConstMemberCaller, ReferenceCaller, ConstReferenceCaller, PointerCaller, ConstPointerCaller and FreeCaller.
 */
template<typename Result>
class Callback0: public CallbackBase<Result(*) (void*)>
{
		typedef CallbackBase<Result(*) (void*)> Base;
		static Result nullThunk (void*)
		{
		}

	public:
		typedef Result result_type;

		Callback0 () :
			Base(0, nullThunk)
		{
		}
		template<typename Caller>
		Callback0 (const BindFirstOpaque<Caller>& caller) :
			Base(caller.getEnvironment(), BindFirstOpaque<Caller>::thunk)
		{
		}
		Callback0 (void* environment, typename Base::Thunk function) :
			Base(environment, function)
		{
		}
		result_type operator() () const
		{
			return Base::getThunk()(Base::getEnvironment());
		}
};

template<typename Caller>
inline Callback0<typename Caller::result_type> makeCallback0 (const Caller& caller,
		typename Caller::first_argument_type callee)
{
	return Callback0<typename Caller::result_type> (BindFirstOpaque<Caller> (callee));
}
template<typename Caller>
inline Callback0<typename Caller::result_type> makeStatelessCallback0 (const Caller& caller)
{
	return makeCallback0(Caller0To1<Caller> (), 0);
}

typedef Callback0<void> Callback;

/**
 * @brief Combines a void pointer with a pointer to a function which operates on a void pointer and one other argument.
 * Use with the callback constructors MemberCaller1, ConstMemberCaller1, ReferenceCaller1, ConstReferenceCaller1, PointerCaller1, ConstPointerCaller1 and FreeCaller1.
 */
template<typename FirstArgument, typename Result>
class Callback1: public CallbackBase<Result(*) (void*, FirstArgument)>
{
		typedef CallbackBase<Result(*) (void*, FirstArgument)> Base;
		static Result nullThunk (void*, FirstArgument)
		{
		}

	public:
		typedef FirstArgument first_argument_type;
		typedef Result result_type;

		Callback1 () :
			Base(0, nullThunk)
		{
		}
		template<typename Caller>
		Callback1 (const BindFirstOpaque1<Caller>& caller) :
			Base(caller.getEnvironment(), BindFirstOpaque1<Caller>::thunk)
		{
		}
		Callback1 (void* environment, typename Base::Thunk function) :
			Base(environment, function)
		{
		}
		result_type operator() (FirstArgument firstArgument) const
		{
			return Base::getThunk()(Base::getEnvironment(), firstArgument);
		}
};

template<typename Caller>
inline Callback1<typename Caller::second_argument_type, typename Caller::result_type> makeCallback1 (
		const Caller& caller, typename Caller::first_argument_type callee)
{
	return Callback1<typename Caller::second_argument_type, typename Caller::result_type> (BindFirstOpaque1<Caller> (
			callee));
}
template<typename Caller>
inline Callback1<typename Caller::second_argument_type, typename Caller::result_type> makeStatelessCallback1 (
		const Caller& caller)
{
	return makeCallback1(Caller1To2<Caller> (), 0);
}

/**
 * @brief Combines a void pointer with a pointer to a function which operates on a void pointer and two other arguments.
 */
template<typename FirstArgument, typename SecondArgument, typename Result>
class Callback2: public CallbackBase<Result(*) (void*, FirstArgument, SecondArgument)>
{
		typedef CallbackBase<Result(*) (void*, FirstArgument, SecondArgument)> Base;
		static Result nullThunk (void*, FirstArgument, SecondArgument)
		{
		}

	public:
		typedef FirstArgument first_argument_type;
		typedef SecondArgument second_argument_type;
		typedef Result result_type;

		Callback2 () :
			Base(0, nullThunk)
		{
		}
		template<typename Caller>
		Callback2 (const BindFirstOpaque2<Caller>& caller) :
			Base(caller.getEnvironment(), BindFirstOpaque2<Caller>::thunk)
		{
		}
		Callback2 (void* environment, typename Base::Thunk function) :
			Base(environment, function)
		{
		}
		result_type operator() (FirstArgument firstArgument, SecondArgument secondArgument) const
		{
			return Base::getThunk()(Base::getEnvironment(), firstArgument, secondArgument);
		}
};

template<typename Caller>
inline Callback2<typename Caller::second_argument_type, typename Caller::third_argument_type,
		typename Caller::result_type> makeCallback2 (const Caller& caller, typename Caller::first_argument_type callee)
{
	return Callback2<typename Caller::second_argument_type, typename Caller::third_argument_type,
			typename Caller::result_type> (BindFirstOpaque2<Caller> (callee));
}
template<typename Caller>
inline Callback2<typename Caller::first_argument_type, typename Caller::second_argument_type,
		typename Caller::result_type> makeStatelessCallback2 (const Caller& caller)
{
	return makeCallback2(Caller2To3<Caller> (), 0);
}

/**
 * @brief Combines a void pointer with a pointer to a function which operates on a void pointer and three other arguments.
 */
template<typename FirstArgument, typename SecondArgument, typename ThirdArgument, typename Result>
class Callback3: public CallbackBase<Result(*) (void*, FirstArgument, SecondArgument, ThirdArgument)>
{
		typedef CallbackBase<Result(*) (void*, FirstArgument, SecondArgument, ThirdArgument)> Base;
		static Result nullThunk (void*, FirstArgument, SecondArgument, ThirdArgument)
		{
		}

	public:
		typedef FirstArgument first_argument_type;
		typedef SecondArgument second_argument_type;
		typedef ThirdArgument third_argument_type;
		typedef Result result_type;

		Callback3 () :
			Base(0, nullThunk)
		{
		}
		template<typename Caller>
		Callback3 (const BindFirstOpaque3<Caller>& caller) :
			Base(caller.getEnvironment(), BindFirstOpaque3<Caller>::thunk)
		{
		}
		Callback3 (void* environment, typename Base::Thunk function) :
			Base(environment, function)
		{
		}
		result_type operator() (FirstArgument firstArgument, SecondArgument secondArgument, ThirdArgument thirdArgument) const
		{
			return Base::getThunk()(Base::getEnvironment(), firstArgument, secondArgument, thirdArgument);
		}
};

template<typename Caller>
inline Callback3<typename Caller::second_argument_type, typename Caller::third_argument_type,
		typename Caller::fourth_argument_type, typename Caller::result_type> makeCallback3 (const Caller& caller,
		typename Caller::first_argument_type callee)
{
	return Callback3<typename Caller::second_argument_type, typename Caller::third_argument_type,
			typename Caller::fourth_argument_type, typename Caller::result_type> (BindFirstOpaque3<Caller> (callee));
}
template<typename Caller>
inline Callback3<typename Caller::first_argument_type, typename Caller::second_argument_type,
		typename Caller::third_argument_type, typename Caller::result_type> makeStatelessCallback3 (
		const Caller& caller)
{
	return makeCallback3(Caller3To4<Caller> (), 0);
}

/**
 * @brief Forms a Callback from a non-const Environment reference and a non-const Environment member-function.
 * @skipline MemberCaller example
 * @until end example
 */
template<typename Environment, void(Environment::*member) ()>
class MemberCaller: public BindFirstOpaque<Member<Environment, void, member> >
{
	public:
		MemberCaller (Environment& environment) :
			BindFirstOpaque<Member<Environment, void, member> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a const Environment reference and a const Environment member-function.
 * @skipline MemberCaller example
 * @until end example
 */
template<typename Environment, void(Environment::*member) () const>
class ConstMemberCaller: public BindFirstOpaque<ConstMember<Environment, void, member> >
{
	public:
		ConstMemberCaller (const Environment& environment) :
			BindFirstOpaque<ConstMember<Environment, void, member> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a non-const Environment reference and a const Environment member-function which takes one argument.
 */
template<typename Environment, typename FirstArgument, void(Environment::*member) (FirstArgument)>
class MemberCaller1: public BindFirstOpaque1<Member1<Environment, FirstArgument, void, member> >
{
	public:
		MemberCaller1 (Environment& environment) :
			BindFirstOpaque1<Member1<Environment, FirstArgument, void, member> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a const Environment reference and a const Environment member-function which takes one argument.
 */
template<typename Environment, typename FirstArgument, void(Environment::*member) (FirstArgument) const>
class ConstMemberCaller1: public BindFirstOpaque1<ConstMember1<Environment, FirstArgument, void, member> >
{
	public:
		ConstMemberCaller1 (const Environment& environment) :
			BindFirstOpaque1<ConstMember1<Environment, FirstArgument, void, member> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a non-const Environment reference and a free function which operates on a non-const Environment reference.
 * @skipline ReferenceCaller example
 * @until end example
 */
template<typename Environment, void(*func) (Environment&)>
class ReferenceCaller: public BindFirstOpaque<Function1<Environment&, void, func> >
{
	public:
		ReferenceCaller (Environment& environment) :
			BindFirstOpaque<Function1<Environment&, void, func> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a const Environment reference and a free function which operates on a const Environment reference.
 * @skipline ReferenceCaller example
 * @until end example
 */
template<typename Environment, void(*func) (const Environment&)>
class ConstReferenceCaller: public BindFirstOpaque<Function1<const Environment&, void, func> >
{
	public:
		ConstReferenceCaller (const Environment& environment) :
			BindFirstOpaque<Function1<const Environment&, void, func> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a non-const Environment reference and a free function which operates on a non-const Environment reference and one other argument.
 */
template<typename Environment, typename FirstArgument, void(*func) (Environment&, FirstArgument)>
class ReferenceCaller1: public BindFirstOpaque1<Function2<Environment&, FirstArgument, void, func> >
{
	public:
		ReferenceCaller1 (Environment& environment) :
			BindFirstOpaque1<Function2<Environment&, FirstArgument, void, func> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a const Environment reference and a free function which operates on a const Environment reference and one other argument.
 */
template<typename Environment, typename FirstArgument, void(*func) (const Environment&, FirstArgument)>
class ConstReferenceCaller1: public BindFirstOpaque1<Function2<const Environment&, FirstArgument, void, func> >
{
	public:
		ConstReferenceCaller1 (const Environment& environment) :
			BindFirstOpaque1<Function2<const Environment&, FirstArgument, void, func> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a const Environment pointer and a free function which operates on a const Environment pointer.
 */
template<typename Environment, void(*func) (const Environment*)>
class ConstPointerCaller: public BindFirstOpaque<Function1<const Environment*, void, func> >
{
	public:
		ConstPointerCaller (const Environment* environment) :
			BindFirstOpaque<Function1<const Environment*, void, func> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a const Environment pointer and a free function which operates on a const Environment pointer and one other argument.
 */
template<typename Environment, typename FirstArgument, void(*func) (const Environment*, FirstArgument)>
class ConstPointerCaller1: public BindFirstOpaque1<Function2<const Environment*, FirstArgument, void, func> >
{
	public:
		ConstPointerCaller1 (const Environment* environment) :
			BindFirstOpaque1<Function2<const Environment*, FirstArgument, void, func> > (environment)
		{
		}
};

/**
 * @brief Forms a Callback from a free function which takes no arguments.
 */
template<void(*func) ()>
class FreeCaller: public BindFirstOpaque<Caller0To1<Function0<void, func> > >
{
	public:
		FreeCaller () :
			BindFirstOpaque<Caller0To1<Function0<void, func> > > (0)
		{
		}
};

/**
 * @brief Forms a Callback from a free function which takes a single argument.
 */
template<typename FirstArgument, void(*func) (FirstArgument)>
class FreeCaller1: public BindFirstOpaque1<Caller1To2<Function1<FirstArgument, void, func> > >
{
	public:
		FreeCaller1 () :
			BindFirstOpaque1<Caller1To2<Function1<FirstArgument, void, func> > > (0)
		{
		}
};

/**
 * @brief Constructs a Callback from a non-const \p functor with zero arguments.
 * @param functor Must define \c operator()().
 */
template<typename Functor>
inline Callback makeCallback (Functor& functor)
{
	return Callback(MemberCaller<Functor, &Functor::operator()> (functor));
}

/**
 * @brief Constructs a Callback from a const \p functor with zero arguments.
 * @param functor Must define const \c operator()().
 */
template<typename Functor>
inline Callback makeCallback (const Functor& functor)
{
	return Callback(ConstMemberCaller<Functor, &Functor::operator()> (functor));
}

/**
 * @brief Constructs a Callback1 from a non-const \p functor with one argument.
 * @param functor Must define \c first_argument_type and \c operator()(first_argument_type).
 */
template<typename Functor>
inline Callback1<typename Functor::first_argument_type> makeCallback1 (Functor& functor)
{
	typedef typename Functor::first_argument_type FirstArgument;
	return Callback1<FirstArgument> (MemberCaller1<Functor, FirstArgument, &Functor::operator()> (functor));
}

/**
 * @brief  Constructs a Callback1 from a const \p functor with one argument.
 * @param functor Must define \c first_argument_type and const \c operator()(first_argument_type).
 */
template<typename Functor>
inline Callback1<typename Functor::first_argument_type> makeCallback1 (const Functor& functor)
{
	typedef typename Functor::first_argument_type FirstArgument;
	return Callback1<FirstArgument> (ConstMemberCaller1<Functor, FirstArgument, &Functor::operator()> (functor));
}

typedef Callback1<bool> BoolImportCallback;
typedef Callback1<const BoolImportCallback&> BoolExportCallback;

typedef Callback1<int> IntImportCallback;
typedef Callback1<const IntImportCallback&> IntExportCallback;

typedef Callback1<float> FloatImportCallback;
typedef Callback1<const FloatImportCallback&> FloatExportCallback;

typedef Callback1<const char*> StringImportCallback;
typedef Callback1<const StringImportCallback&> StringExportCallback;

typedef Callback1<std::size_t> SizeImportCallback;
typedef Callback1<const SizeImportCallback&> SizeExportCallback;

template<bool(*BoolFunction) ()>
class BoolFunctionExport
{
	public:
		static void apply (const BoolImportCallback& importCallback)
		{
			importCallback(BoolFunction());
		}
};

#endif
