    // Copyright (C) 2008  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_BOUND_FUNCTION_POINTER_KERNEl_1_
    #define DLIB_BOUND_FUNCTION_POINTER_KERNEl_1_

    #include "../algs.h"
    #include "../member_function_pointer.h"
    #include "bound_function_pointer_kernel_abstract.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------

        namespace bfp1_helpers
        {
            template <typename T> struct strip { typedef T type; };
            template <typename T> struct strip<T&> { typedef T type; };

        // ------------------------------------------------------------------------------------

            class bound_function_helper_base_base
            {
            public:
                virtual ~bound_function_helper_base_base(){}
                virtual void call() const = 0;
                virtual bool is_set() const = 0;
                virtual void clone(void* ptr) const = 0;
            };

        // ------------------------------------------------------------------------------------

            template <typename T1, typename T2, typename T3, typename T4>
            class bound_function_helper_base : public bound_function_helper_base_base
            {
            public:
                bound_function_helper_base():arg1(0), arg2(0), arg3(0), arg4(0) {}

                typename strip<T1>::type* arg1;
                typename strip<T2>::type* arg2;
                typename strip<T3>::type* arg3;
                typename strip<T4>::type* arg4;

                member_function_pointer<T1,T2,T3,T4> mfp;
            };

        // ----------------

            template <typename F, typename T1 = void, typename T2 = void, typename T3 = void, typename T4 = void>
            class bound_function_helper : public bound_function_helper_base<T1,T2,T3,T4>
            {
            public:
                void call() const
                {
                    (*fp)(*this->arg1, *this->arg2, *this->arg3, *this->arg4);
                }

                typename strip<F>::type* fp;
            };

            template <typename T1, typename T2, typename T3, typename T4>
            class bound_function_helper<void,T1,T2,T3,T4> : public bound_function_helper_base<T1,T2,T3,T4>
            {
            public:
                void call() const
                {
                    if (this->mfp)    this->mfp(*this->arg1, *this->arg2, *this->arg3, *this->arg4);
                    else if (fp) fp(*this->arg1, *this->arg2, *this->arg3, *this->arg4);
                }

                void (*fp)(T1, T2, T3, T4);
            };

        // ----------------

            template <typename F>
            class bound_function_helper<F,void,void,void,void> : public bound_function_helper_base<void,void,void,void>
            {
            public:
                void call() const
                {
                    (*fp)();
                }

                typename strip<F>::type* fp;
            };

            template <>
            class bound_function_helper<void,void,void,void,void> : public bound_function_helper_base<void,void,void,void>
            {
            public:
                void call() const
                {
                    if (this->mfp)    this->mfp();
                    else if (fp) fp();
                }

                void (*fp)();
            };

        // ----------------

            template <typename F, typename T1>
            class bound_function_helper<F,T1,void,void,void> : public bound_function_helper_base<T1,void,void,void>
            {
            public:
                void call() const
                {
                    (*fp)(*this->arg1);
                }

                typename strip<F>::type* fp;
            };

            template <typename T1>
            class bound_function_helper<void,T1,void,void,void> : public bound_function_helper_base<T1,void,void,void>
            {
            public:
                void call() const
                {
                    if (this->mfp)    this->mfp(*this->arg1);
                    else if (fp) fp(*this->arg1);
                }

                void (*fp)(T1);
            };

        // ----------------

            template <typename F, typename T1, typename T2>
            class bound_function_helper<F,T1,T2,void,void> : public bound_function_helper_base<T1,T2,void,void>
            {
            public:
                void call() const
                {
                    (*fp)(*this->arg1, *this->arg2);
                }

                typename strip<F>::type* fp;
            };

            template <typename T1, typename T2>
            class bound_function_helper<void,T1,T2,void,void> : public bound_function_helper_base<T1,T2,void,void>
            {
            public:
                void call() const
                {
                    if (this->mfp)    this->mfp(*this->arg1, *this->arg2);
                    else if (fp) fp(*this->arg1, *this->arg2);
                }

                void (*fp)(T1, T2);
            };

        // ----------------

            template <typename F, typename T1, typename T2, typename T3>
            class bound_function_helper<F,T1,T2,T3,void> : public bound_function_helper_base<T1,T2,T3,void>
            {
            public:
                void call() const
                {
                    (*fp)(*this->arg1, *this->arg2, *this->arg3);
                }

                typename strip<F>::type* fp;
            };

            template <typename T1, typename T2, typename T3>
            class bound_function_helper<void,T1,T2,T3,void> : public bound_function_helper_base<T1,T2,T3,void>
            {
            public:

                void call() const
                {
                    if (this->mfp)    this->mfp(*this->arg1, *this->arg2, *this->arg3);
                    else if (fp) fp(*this->arg1, *this->arg2, *this->arg3);
                }

                void (*fp)(T1, T2, T3);
            };

        // ------------------------------------------------------------------------------------
        // ------------------------------------------------------------------------------------

            template <typename T>
            class bound_function_helper_T : public T
            {
            public:
                bound_function_helper_T(){ this->fp = 0;}

                bool is_set() const
                {
                    return this->fp != 0 || this->mfp.is_set();
                }

                template <unsigned long mem_size>
                void safe_clone(stack_based_memory_block<mem_size>& buf)
                {
                    // This is here just to validate the assumption that our block of memory we have made
                    // in bf_memory is the right size to store the data for this object.  If you
                    // get a compiler error on this line then email me :)
                    COMPILE_TIME_ASSERT(sizeof(bound_function_helper_T) <= mem_size);
                    clone(buf.get());
                }

                void clone   (void* ptr) const
                {
                    bound_function_helper_T* p = new(ptr) bound_function_helper_T();
                    p->arg1 = this->arg1;
                    p->arg2 = this->arg2;
                    p->arg3 = this->arg3;
                    p->arg4 = this->arg4;
                    p->fp = this->fp;
                    p->mfp = this->mfp;
                }
            };

        }

    // ----------------------------------------------------------------------------------------

        class bound_function_pointer
        {
            typedef bfp1_helpers::bound_function_helper_T<bfp1_helpers::bound_function_helper<void,int> > bf_null_type;

        public:

            // These typedefs are here for backwards compatibility with previous versions of
            // dlib.
            typedef bound_function_pointer kernel_1a;
            typedef bound_function_pointer kernel_1a_c;

            bound_function_pointer (
            ) { bf_null_type().safe_clone(bf_memory); }

            bound_function_pointer (
                const bound_function_pointer& item
            ) { item.bf()->clone(bf_memory.get()); }

            ~bound_function_pointer()
            { destroy_bf_memory(); }

            bound_function_pointer& operator= (
                const bound_function_pointer& item
            ) { bound_function_pointer(item).swap(*this); return *this; }

            void clear (
            ) { bound_function_pointer().swap(*this); }

            bool is_set (
            ) const
            {
                return bf()->is_set();
            }

            void swap (
                bound_function_pointer& item
            )
            {
                // make a temp copy of item
                bound_function_pointer temp(item);

                // destory the stuff in item
                item.destroy_bf_memory();
                // copy *this into item
                bf()->clone(item.bf_memory.get());

                // destory the stuff in this
                destroy_bf_memory();
                // copy temp into *this
                temp.bf()->clone(bf_memory.get());
            }

            void operator() (
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(is_set() == true ,
                    "\tvoid bound_function_pointer::operator()"
                    << "\n\tYou must call set() before you can use this function"
                    << "\n\tthis: " << this
                );

                bf()->call();
            }

        private:
            struct dummy{ void nonnull() {}};
            typedef void (dummy::*safe_bool)();

        public:
            operator safe_bool () const { return is_set() ? &dummy::nonnull : 0; }
            bool operator!() const { return !is_set(); }

        // -------------------------------------------
        //      set function object overloads
        // -------------------------------------------

            template <typename F>
            void set (
                F& function_object
            )
            {
                COMPILE_TIME_ASSERT(is_function<F>::value == false);
                COMPILE_TIME_ASSERT(is_pointer_type<F>::value == false);

                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<F> > bf_helper_type;

                bf_helper_type temp;
                temp.fp = &function_object;

                temp.safe_clone(bf_memory);
            }

            template <typename F, typename A1 >
            void set (
                F& function_object,
                A1& arg1
            )
            {
                COMPILE_TIME_ASSERT(is_function<F>::value == false);
                COMPILE_TIME_ASSERT(is_pointer_type<F>::value == false);

                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<F,A1> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.fp = &function_object;

                temp.safe_clone(bf_memory);
            }

            template <typename F, typename A1, typename A2 >
            void set (
                F& function_object,
                A1& arg1,
                A2& arg2
            )
            {
                COMPILE_TIME_ASSERT(is_function<F>::value == false);
                COMPILE_TIME_ASSERT(is_pointer_type<F>::value == false);

                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<F,A1,A2> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.fp = &function_object;

                temp.safe_clone(bf_memory);
            }

            template <typename F, typename A1, typename A2, typename A3 >
            void set (
                F& function_object,
                A1& arg1,
                A2& arg2,
                A3& arg3
            )
            {
                COMPILE_TIME_ASSERT(is_function<F>::value == false);
                COMPILE_TIME_ASSERT(is_pointer_type<F>::value == false);

                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<F,A1,A2,A3> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.fp = &function_object;

                temp.safe_clone(bf_memory);
            }

            template <typename F, typename A1, typename A2, typename A3, typename A4>
            void set (
                F& function_object,
                A1& arg1,
                A2& arg2,
                A3& arg3,
                A4& arg4
            )
            {
                COMPILE_TIME_ASSERT(is_function<F>::value == false);
                COMPILE_TIME_ASSERT(is_pointer_type<F>::value == false);

                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<F,A1,A2,A3,A4> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.arg4 = &arg4;
                temp.fp = &function_object;

                temp.safe_clone(bf_memory);
            }

        // -------------------------------------------
        //      set mfp overloads
        // -------------------------------------------

            template <typename T>
            void set (
                T& object,
                void (T::*funct)()
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void> > bf_helper_type;

                bf_helper_type temp;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

            template <typename T >
            void set (
                const T& object,
                void (T::*funct)()const
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void> > bf_helper_type;

                bf_helper_type temp;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

        // -------------------------------------------

            template <typename T, typename T1, typename A1 >
            void set (
                T& object,
                void (T::*funct)(T1),
                A1& arg1
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

            template <typename T, typename T1, typename A1 >
            void set (
                const T& object,
                void (T::*funct)(T1)const,
                A1& arg1
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

        // ----------------

            template <typename T, typename T1, typename A1,
            typename T2, typename A2>
            void set (
                T& object,
                void (T::*funct)(T1, T2),
                A1& arg1,
                A2& arg2
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

            template <typename T, typename T1, typename A1,
            typename T2, typename A2>
            void set (
                const T& object,
                void (T::*funct)(T1, T2)const,
                A1& arg1,
                A2& arg2
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

        // ----------------

            template <typename T, typename T1, typename A1,
            typename T2, typename A2,
            typename T3, typename A3>
            void set (
                T& object,
                void (T::*funct)(T1, T2, T3),
                A1& arg1,
                A2& arg2,
                A3& arg3
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2,T3> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

            template <typename T, typename T1, typename A1,
            typename T2, typename A2,
            typename T3, typename A3>
            void set (
                const T& object,
                void (T::*funct)(T1, T2, T3)const,
                A1& arg1,
                A2& arg2,
                A3& arg3
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2,T3> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

        // ----------------

            template <typename T, typename T1, typename A1,
            typename T2, typename A2,
            typename T3, typename A3,
            typename T4, typename A4>
            void set (
                T& object,
                void (T::*funct)(T1, T2, T3, T4),
                A1& arg1,
                A2& arg2,
                A3& arg3,
                A4& arg4
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2,T3,T4> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.arg4 = &arg4;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

            template <typename T, typename T1, typename A1,
            typename T2, typename A2,
            typename T3, typename A3,
            typename T4, typename A4>
            void set (
                const T& object,
                void (T::*funct)(T1, T2, T3, T4)const,
                A1& arg1,
                A2& arg2,
                A3& arg3,
                A4& arg4
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2,T3,T4> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.arg4 = &arg4;
                temp.mfp.set(object,funct);

                temp.safe_clone(bf_memory);
            }

        // -------------------------------------------
        //      set fp overloads
        // -------------------------------------------

            void set (
                void (*funct)()
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void> > bf_helper_type;

                bf_helper_type temp;
                temp.fp = funct;

                temp.safe_clone(bf_memory);
            }

            template <typename T1, typename A1>
            void set (
                void (*funct)(T1),
                A1& arg1
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.fp = funct;

                temp.safe_clone(bf_memory);
            }

            template <typename T1, typename A1,
            typename T2, typename A2>
            void set (
                void (*funct)(T1, T2),
                A1& arg1,
                A2& arg2
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.fp = funct;

                temp.safe_clone(bf_memory);
            }

            template <typename T1, typename A1,
            typename T2, typename A2,
            typename T3, typename A3>
            void set (
                void (*funct)(T1, T2, T3),
                A1& arg1,
                A2& arg2,
                A3& arg3
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2,T3> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.fp = funct;

                temp.safe_clone(bf_memory);
            }

            template <typename T1, typename A1,
            typename T2, typename A2,
            typename T3, typename A3,
            typename T4, typename A4>
            void set (
                void (*funct)(T1, T2, T3, T4),
                A1& arg1,
                A2& arg2,
                A3& arg3,
                A4& arg4
            )
            {
                using namespace bfp1_helpers;
                destroy_bf_memory();
                typedef bound_function_helper_T<bound_function_helper<void,T1,T2,T3,T4> > bf_helper_type;

                bf_helper_type temp;
                temp.arg1 = &arg1;
                temp.arg2 = &arg2;
                temp.arg3 = &arg3;
                temp.arg4 = &arg4;
                temp.fp = funct;

                temp.safe_clone(bf_memory);
            }

        // -------------------------------------------

        private:

            stack_based_memory_block<sizeof(bf_null_type)> bf_memory;

            void destroy_bf_memory (
            )
            {
                // Honestly, this probably doesn't even do anything but I'm putting
                // it here just for good measure.
                bf()->~bound_function_helper_base_base();
            }

            bfp1_helpers::bound_function_helper_base_base*       bf ()
            { return static_cast<bfp1_helpers::bound_function_helper_base_base*>(bf_memory.get()); }

            const bfp1_helpers::bound_function_helper_base_base* bf () const
            { return static_cast<const bfp1_helpers::bound_function_helper_base_base*>(bf_memory.get()); }

        };

    // ----------------------------------------------------------------------------------------

        inline void swap (
            bound_function_pointer& a,
            bound_function_pointer& b
        ) { a.swap(b); }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_BOUND_FUNCTION_POINTER_KERNEl_1_