// Copyright (c) 2003 Daniel Wallin and Arvid Norberg

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.


#ifndef LUABIND_POLICY_HPP_INCLUDED
#define LUABIND_POLICY_HPP_INCLUDED

#include <luabind/config.hpp>

#include <typeinfo>
#include <string>
#include <memory>

#include <boost/type_traits/is_enum.hpp>
#include <boost/type_traits/is_array.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/or.hpp>
#include <boost/type_traits/add_reference.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include <boost/type_traits/is_pointer.hpp>
#include <boost/type_traits/is_base_and_derived.hpp>
#include <boost/bind/arg.hpp>
#include <boost/limits.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/version.hpp>

#include <luabind/detail/class_registry.hpp>
#include <luabind/detail/primitives.hpp>
#include <luabind/detail/object_rep.hpp>
#include <luabind/detail/typetraits.hpp>
#include <luabind/detail/class_cache.hpp>
#include <luabind/detail/debug.hpp>
#include <luabind/detail/class_rep.hpp>
#include <luabind/detail/has_get_pointer.hpp>
#include <luabind/detail/make_instance.hpp>

#include <boost/type_traits/add_reference.hpp>

#include <luabind/detail/decorate_type.hpp>
#include <luabind/weak_ref.hpp>
#include <luabind/back_reference_fwd.hpp>

#include <luabind/value_wrapper.hpp>
#include <luabind/from_stack.hpp>
#include <luabind/typeid.hpp>
#include <luabind/lua502.hpp>

namespace luabind
{
	namespace detail
	{
		struct conversion_policy_base {};
	}

	template<int N, bool HasArg = true>
	struct conversion_policy : detail::conversion_policy_base
	{
		BOOST_STATIC_CONSTANT(int, index = N);
		BOOST_STATIC_CONSTANT(bool, has_arg = HasArg);
	};

	class index_map
	{
	public:
		index_map(const int* m): m_map(m) {}

		int operator[](int index) const
		{
			return m_map[index];
		}

	private:
		const int* m_map;
	};

//	 template<class T> class functor;
	 class weak_ref;
}

namespace luabind { namespace detail
{
	template<class H, class T>
	struct policy_cons
	{
		typedef H head;
		typedef T tail;

		template<class U>
		policy_cons<U, policy_cons<H,T> > operator,(policy_cons<U,detail::null_type>)
		{
			return policy_cons<U, policy_cons<H,T> >();
		}

		template<class U>
		policy_cons<U, policy_cons<H,T> > operator+(policy_cons<U,detail::null_type>)
		{
			return policy_cons<U, policy_cons<H,T> >();
		}

		template<class U>
		policy_cons<U, policy_cons<H,T> > operator|(policy_cons<U,detail::null_type>)
		{
			return policy_cons<U, policy_cons<H,T> >();
		}
	};

	struct indirection_layer
	{
		template<class T>
		indirection_layer(const T&);
	};

	yes_t is_policy_cons_test(const null_type&);
	template<class H, class T>
	yes_t is_policy_cons_test(const policy_cons<H,T>&);
	no_t is_policy_cons_test(...);

	template<class T>
	struct is_policy_cons
	{
		static const T& t;

		BOOST_STATIC_CONSTANT(bool, value =
			sizeof(is_policy_cons_test(t)) == sizeof(yes_t));

		typedef boost::mpl::bool_<value> type;
	};

	template<bool>
	struct is_string_literal
	{
		static no_t helper(indirection_layer);
		static yes_t helper(const char*);
	};

	template<>
	struct is_string_literal<false>
	{
		static no_t helper(indirection_layer);
	};


    namespace mpl = boost::mpl;

    template <class T, class Clone>
    void make_pointee_instance(lua_State* L, T& x, mpl::true_, Clone)
    {
        if (get_pointer(x))
        {
            make_instance(L, x);
        }
        else
        {
            lua_pushnil(L);
        }
    }

    template <class T>
    void make_pointee_instance(lua_State* L, T& x, mpl::false_, mpl::true_)
    {
        std::auto_ptr<T> ptr(new T(x));
        make_instance(L, ptr);
    }

    template <class T>
    void make_pointee_instance(lua_State* L, T& x, mpl::false_, mpl::false_)
    {
        make_instance(L, &x);
    }

    template <class T, class Clone>
    void make_pointee_instance(lua_State* L, T& x, Clone)
    {
        make_pointee_instance(L, x, has_get_pointer<T>(), Clone());
    }

// ********** pointer converter ***********

	struct pointer_converter
	{
		typedef pointer_converter type;
        typedef mpl::false_ is_native;

        pointer_converter()
          : result(0)
        {}

        void* result;

        int const consumed_args(...)
        {
            return 1;
        }

		template<class T>
		void apply(lua_State* L, T* ptr)
		{
			if (ptr == 0)
			{
				lua_pushnil(L);
				return;
			}

			if (luabind::get_back_reference(L, ptr))
				return;

            make_instance(L, ptr);
		}

		template<class T>
		T* apply(lua_State*, by_pointer<T>, int)
		{
            return static_cast<T*>(result);
		}

		template<class T>
		int match(lua_State* L, by_pointer<T>, int index)
		{
			if (lua_isnil(L, index)) return 0;
			object_rep* obj = get_instance(L, index);
			if (obj == 0) return -1;

            if (obj->is_const())
                return -1;

            std::pair<void*, int> s = obj->get_instance(registered_class<T>::id);
            result = s.first;
            return s.second;
		}

		template<class T>
		void converter_postcall(lua_State*, by_pointer<T>, int)
		{}
	};

// ******* value converter *******

	struct value_converter
	{
		typedef value_converter type;
        typedef mpl::false_ is_native;

        int const consumed_args(...)
        {
            return 1;
        }

        value_converter()
          : result(0)
        {}

        void* result;

		template<class T>
		void apply(lua_State* L, T x)
		{
			if (luabind::get_back_reference(L, x))
				return;

            make_pointee_instance(L, x, mpl::true_());
		}

		template<class T>
		T apply(lua_State*, by_value<T>, int)
		{
            return *static_cast<T*>(result);
		}

		template<class T>
		int match(lua_State* L, by_value<T>, int index)
		{
			// special case if we get nil in, try to match the holder type
			if (lua_isnil(L, index))
				return -1;

			object_rep* obj = get_instance(L, index);
			if (obj == 0) return -1;

            std::pair<void*, int> s = obj->get_instance(registered_class<T>::id);
            result = s.first;
            return s.second;
		}

		template<class T>
		void converter_postcall(lua_State*, T, int) {}
	};

// ******* const pointer converter *******

    struct const_pointer_converter
	{
		typedef const_pointer_converter type;
        typedef mpl::false_ is_native;

        int const consumed_args(...)
        {
            return 1;
        }

        const_pointer_converter()
          : result(0)
        {}

        void* result;

		template<class T>
		void apply(lua_State* L, const T* ptr)
		{
			if (ptr == 0)
			{
				lua_pushnil(L);
				return;
			}

			if (luabind::get_back_reference(L, ptr))
				return;

            make_instance(L, ptr);
		}

		template<class T>
		T const* apply(lua_State*, by_const_pointer<T>, int)
		{
			return static_cast<T const*>(result);
		}

		template<class T>
		int match(lua_State* L, by_const_pointer<T>, int index)
		{
			if (lua_isnil(L, index)) return 0;
			object_rep* obj = get_instance(L, index);
			if (obj == 0) return -1; // if the type is not one of our own registered types, classify it as a non-match
            std::pair<void*, int> s = obj->get_instance(registered_class<T>::id);
            if (s.second >= 0 && !obj->is_const())
                s.second += 10;
            result = s.first;
            return s.second;
		}

        template<class T>
        void converter_postcall(lua_State*, T, int) {}
	};

// ******* reference converter *******

    struct ref_converter : pointer_converter
	{
		typedef ref_converter type;
        typedef mpl::false_ is_native;

        int const consumed_args(...)
        {
            return 1;
        }

		template<class T>
		void apply(lua_State* L, T& ref)
		{
			if (luabind::get_back_reference(L, ref))
				return;

            make_pointee_instance(L, ref, mpl::false_());
		}

		template<class T>
		T& apply(lua_State* L, by_reference<T>, int index)
		{
			assert(!lua_isnil(L, index));
            return *pointer_converter::apply(L, by_pointer<T>(), index);
		}

		template<class T>
		int match(lua_State* L, by_reference<T>, int index)
		{
            object_rep* obj = get_instance(L, index);
            if (obj == 0) return -1;

            if (obj->is_const())
                return -1;

            std::pair<void*, int> s = obj->get_instance(registered_class<T>::id);
            result = s.first;
            return s.second;
		}

		template<class T>
		void converter_postcall(lua_State*, T, int) {}
	};

// ******** const reference converter *********

	struct const_ref_converter
	{
		typedef const_ref_converter type;
        typedef mpl::false_ is_native;

        int const consumed_args(...)
        {
            return 1;
        }

        const_ref_converter()
          : result(0)
        {}

        void* result;

		template<class T>
		void apply(lua_State* L, T const& ref)
		{
			if (luabind::get_back_reference(L, ref))
				return;

            make_pointee_instance(L, ref, mpl::false_());
		}

		template<class T>
		T const& apply(lua_State*, by_const_reference<T>, int)
		{
			return *static_cast<T*>(result);
		}

		template<class T>
		int match(lua_State* L, by_const_reference<T>, int index)
		{
			object_rep* obj = get_instance(L, index);
			if (obj == 0) return -1; // if the type is not one of our own registered types, classify it as a non-match

            std::pair<void*, int> s = obj->get_instance(registered_class<T>::id);
            if (s.second >= 0 && !obj->is_const())
                s.second += 10;
            result = s.first;
            return s.second;
		}

		template<class T>
		void converter_postcall(lua_State*, by_const_reference<T>, int)
		{
		}
	};

	// ****** enum converter ********

	struct enum_converter
	{
		typedef enum_converter type;
        typedef mpl::true_ is_native;

        int const consumed_args(...)
        {
            return 1;
        }

		void apply(lua_State* L, int val)
		{
			lua_pushnumber(L, val);
		}

		template<class T>
		T apply(lua_State* L, by_value<T>, int index)
		{
			return static_cast<T>(static_cast<int>(lua_tonumber(L, index)));
		}

		template<class T>
		static int match(lua_State* L, by_value<T>, int index)
		{
			if (lua_isnumber(L, index)) return 0; else return -1;
		}

		template<class T>
		T apply(lua_State* L, by_const_reference<T>, int index)
		{
			return static_cast<T>(static_cast<int>(lua_tonumber(L, index)));
		}

		template<class T>
		static int match(lua_State* L, by_const_reference<T>, int index)
		{
			if (lua_isnumber(L, index)) return 0; else return -1;
		}

		template<class T>
		void converter_postcall(lua_State*, T, int) {}
	};

	template <class U>
	struct value_wrapper_converter
	{
		typedef value_wrapper_converter<U> type;
		typedef mpl::true_ is_native;

        int const consumed_args(...)
        {
            return 1;
        }

		template<class T>
		T apply(lua_State* L, by_const_reference<T>, int index)
		{
			return T(from_stack(L, index));
		}

		template<class T>
		T apply(lua_State* L, by_value<T>, int index)
		{
			return apply(L, by_const_reference<T>(), index);
		}

		template<class T>
		static int match(lua_State* L, by_const_reference<T>, int index)
		{
			return value_wrapper_traits<T>::check(L, index)
                ? (std::numeric_limits<int>::max)() / LUABIND_MAX_ARITY
                : -1;
		}

		template<class T>
		static int match(lua_State* L, by_value<T>, int index)
		{
			return match(L, by_const_reference<T>(), index);
		}

		void converter_postcall(...) {}

		template<class T>
		void apply(lua_State* interpreter, T const& value_wrapper)
		{
			value_wrapper_traits<T>::unwrap(interpreter, value_wrapper);
		}
    };

    template <class T>
    struct default_converter_generator
      : mpl::eval_if<
            is_value_wrapper_arg<T>
          , value_wrapper_converter<T>
          , mpl::eval_if<
                boost::is_enum<typename boost::remove_reference<T>::type>
              , enum_converter
              , mpl::eval_if<
                    is_nonconst_pointer<T>
                  , pointer_converter
                  , mpl::eval_if<
                        is_const_pointer<T>
                      , const_pointer_converter
                      , mpl::eval_if<
                            is_nonconst_reference<T>
                          , ref_converter
                          , mpl::eval_if<
                                is_const_reference<T>
                              , const_ref_converter
                              , value_converter
                            >
                        >
                    >
                >
            >
        >
    {};

} // namespace detail

// *********** default_policy *****************

template <class T>
struct default_converter
  : detail::default_converter_generator<T>::type
{};

template <class T, class Derived = default_converter<T> >
struct native_converter_base
{
    typedef boost::mpl::true_ is_native;

    int const consumed_args(...)
    {
        return 1;
    }

    template <class U>
    void converter_postcall(lua_State*, U const&, int)
    {}

    int match(lua_State* L, detail::by_value<T>, int index)
    {
        return derived().compute_score(L, index);
    }

    int match(lua_State* L, detail::by_value<T const>, int index)
    {
        return derived().compute_score(L, index);
    }

    int match(lua_State* L, detail::by_const_reference<T>, int index)
    {
        return derived().compute_score(L, index);
    }

    T apply(lua_State* L, detail::by_value<T>, int index)
    {
        return derived().from(L, index);
    }

    T apply(lua_State* L, detail::by_value<T const>, int index)
    {
        return derived().from(L, index);
    }

    T apply(lua_State* L, detail::by_const_reference<T>, int index)
    {
        return derived().from(L, index);
    }

    void apply(lua_State* L, T const& value)
    {
        derived().to(L, value);
    }

    Derived& derived()
    {
        return static_cast<Derived&>(*this);
    }
};

template <class T>
lua_Integer as_lua_integer(T v)
{
    return static_cast<lua_Integer>(v);
}

template <class T>
lua_Number as_lua_number(T v)
{
    return static_cast<lua_Number>(v);
}

# define LUABIND_NUMBER_CONVERTER(type, kind) \
    template <> \
struct default_converter<type> \
  : native_converter_base<type> \
{ \
    int compute_score(lua_State* L, int index) \
    { \
        return lua_type(L, index) == LUA_TNUMBER ? 0 : -1; \
    }; \
    \
    type from(lua_State* L, int index) \
    { \
        return static_cast<type>(BOOST_PP_CAT(lua_to, kind)(L, index)); \
    } \
    \
    void to(lua_State* L, type const& value) \
    { \
        BOOST_PP_CAT(lua_push, kind)(L, BOOST_PP_CAT(as_lua_, kind)(value)); \
    } \
}; \
\
template <> \
struct default_converter<type const> \
  : default_converter<type> \
{}; \
\
template <> \
struct default_converter<type const&> \
  : default_converter<type> \
{};

LUABIND_NUMBER_CONVERTER(char, integer)
LUABIND_NUMBER_CONVERTER(signed char, integer)
LUABIND_NUMBER_CONVERTER(unsigned char, integer)
LUABIND_NUMBER_CONVERTER(signed short, integer)
LUABIND_NUMBER_CONVERTER(unsigned short, integer)
LUABIND_NUMBER_CONVERTER(signed int, integer)
LUABIND_NUMBER_CONVERTER(signed long long, integer)

LUABIND_NUMBER_CONVERTER(unsigned int, number)
LUABIND_NUMBER_CONVERTER(unsigned long, number)
LUABIND_NUMBER_CONVERTER(unsigned long long, number)

LUABIND_NUMBER_CONVERTER(signed long, integer)
LUABIND_NUMBER_CONVERTER(float, number)
LUABIND_NUMBER_CONVERTER(double, number)
LUABIND_NUMBER_CONVERTER(long double, number)

# undef LUABIND_NUMBER_CONVERTER

template <>
struct default_converter<bool>
  : native_converter_base<bool>
{
    static int compute_score(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TBOOLEAN ? 0 : -1;
    }

    bool from(lua_State* L, int index)
    {
        return lua_toboolean(L, index) == 1;
    }

    void to(lua_State* L, bool value)
    {
        lua_pushboolean(L, value);
    }
};

template <>
struct default_converter<bool const>
  : default_converter<bool>
{};

template <>
struct default_converter<bool const&>
  : default_converter<bool>
{};

template <>
struct default_converter<std::string>
  : native_converter_base<std::string>
{
    static int compute_score(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TSTRING ? 0 : -1;
    }

    std::string from(lua_State* L, int index)
    {
        return std::string(lua_tostring(L, index), lua_strlen(L, index));
    }

    void to(lua_State* L, std::string const& value)
    {
        lua_pushlstring(L, value.data(), value.size());
    }
};

template <>
struct default_converter<std::string const>
  : default_converter<std::string>
{};

template <>
struct default_converter<std::string const&>
  : default_converter<std::string>
{};

template <>
struct default_converter<char const*>
{
    typedef boost::mpl::true_ is_native;

    int const consumed_args(...)
    {
        return 1;
    }

    template <class U>
    static int match(lua_State* L, U, int index)
    {
        int type = lua_type(L, index);
        return (type == LUA_TSTRING || type == LUA_TNIL) ? 0 : -1;
    }

    template <class U>
    char const* apply(lua_State* L, U, int index)
    {
        return lua_tostring(L, index);
    }

    void apply(lua_State* L, char const* str)
    {
        lua_pushstring(L, str);
    }

    template <class U>
    void converter_postcall(lua_State*, U, int)
    {}
};

template <>
struct default_converter<const char* const>
  : default_converter<char const*>
{};

template <>
struct default_converter<char*>
  : default_converter<char const*>
{};

template <std::size_t N>
struct default_converter<char const[N]>
  : default_converter<char const*>
{};

template <std::size_t N>
struct default_converter<char[N]>
  : default_converter<char const*>
{};

template <>
struct default_converter<lua_State*>
{
    int const consumed_args(...)
    {
        return 0;
    }

    template <class U>
    lua_State* apply(lua_State* L, U, int)
    {
        return L;
    }

    template <class U>
    static int match(lua_State*, U, int)
    {
        return 0;
    }

    template <class U>
    void converter_postcall(lua_State*, U, int) {}
};

namespace detail
{

	struct default_policy : converter_policy_tag
	{
		BOOST_STATIC_CONSTANT(bool, has_arg = true);

		template<class T>
		static void precall(lua_State*, T, int) {}

		template<class T, class Direction>
		struct apply
		{
            typedef default_converter<T> type;
        };
	};

    template<class T>
    struct is_primitive
      : default_converter<T>::is_native
    {};

// ============== new policy system =================

	template<int, class> struct find_conversion_policy;

	template<bool IsConverter = false>
	struct find_conversion_impl
	{
		template<int N, class Policies>
		struct apply
		{
			typedef typename find_conversion_policy<N, typename Policies::tail>::type type;
		};
	};

	template<>
	struct find_conversion_impl<true>
	{
		template<int N, class Policies>
		struct apply
		{
			typedef typename Policies::head head;
			typedef typename Policies::tail tail;

			BOOST_STATIC_CONSTANT(bool, found = (N == head::index));

			typedef typename
				boost::mpl::if_c<found
					, head
					, typename find_conversion_policy<N, tail>::type
				>::type type;
		};
	};

	template<class Policies>
	struct find_conversion_impl2
	{
		template<int N>
		struct apply
			: find_conversion_impl<
				boost::is_base_and_derived<conversion_policy_base, typename Policies::head>::value
			>::template apply<N, Policies>
		{
		};
	};

	template<>
	struct find_conversion_impl2<detail::null_type>
	{
		template<int N>
		struct apply
		{
			typedef default_policy type;
		};
	};

	template<int N, class Policies>
	struct find_conversion_policy : find_conversion_impl2<Policies>::template apply<N>
	{
	};

	template<class List>
	struct policy_list_postcall
	{
		typedef typename List::head head;
		typedef typename List::tail tail;

		static void apply(lua_State* L, const index_map& i)
		{
			head::postcall(L, i);
			policy_list_postcall<tail>::apply(L, i);
		}
	};

	template<>
	struct policy_list_postcall<detail::null_type>
	{
		static void apply(lua_State*, const index_map&) {}
	};

// ==================================================

// ************** precall and postcall on policy_cons *********************


	template<class List>
	struct policy_precall
	{
		typedef typename List::head head;
		typedef typename List::tail tail;

		static void apply(lua_State* L, int index)
		{
			head::precall(L, index);
			policy_precall<tail>::apply(L, index);
		}
	};

	template<>
	struct policy_precall<detail::null_type>
	{
		static void apply(lua_State*, int) {}
	};

	template<class List>
	struct policy_postcall
	{
		typedef typename List::head head;
		typedef typename List::tail tail;

		static void apply(lua_State* L, int index)
		{
			head::postcall(L, index);
			policy_postcall<tail>::apply(L, index);
		}
	};

	template<>
	struct policy_postcall<detail::null_type>
	{
		static void apply(lua_State*, int) {}
	};

}} // namespace luabind::detail


namespace luabind { namespace
{
#if defined(__GNUC__) && ( \
    (BOOST_VERSION < 103500) \
 || (BOOST_VERSION < 103900 && (__GNUC__ * 100 + __GNUC_MINOR__ <= 400)) \
 || (__GNUC__ * 100 + __GNUC_MINOR__ < 400))
  static inline boost::arg<0> return_value()
  {
	  return boost::arg<0>();
  }

  static inline boost::arg<0> result()
  {
	  return boost::arg<0>();
  }
# define LUABIND_PLACEHOLDER_ARG(N) boost::arg<N>(*)()
#elif defined(BOOST_MSVC) || defined(__MWERKS__) \
  || (BOOST_VERSION >= 103900 && defined(__GNUC__) \
        && (__GNUC__ * 100 + __GNUC_MINOR__ == 400))
  static boost::arg<0> return_value;
  static boost::arg<0> result;
# define LUABIND_PLACEHOLDER_ARG(N) boost::arg<N>
#else
  boost::arg<0> return_value;
  boost::arg<0> result;
# define LUABIND_PLACEHOLDER_ARG(N) boost::arg<N>
#endif
}}

#endif // LUABIND_POLICY_HPP_INCLUDED

