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

#include <vector>
#include "kernel_matrix_abstract.h"
#include "../matrix.h"
#include "../algs.h"
#include "../statistics/random_subset_selector.h"

namespace dlib
{

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

    namespace impl
    {
        template <typename kernel_type, typename T>
        inline const typename T::type& access ( const matrix_exp<T>& m, long i)
        {
            return m(i);
        }

        template <typename kernel_type, typename T, typename Rand_type>
        inline const T& access ( const random_subset_selector<T,Rand_type>& m, long i)
        {
            return m[i];
        }

        template <typename kernel_type, typename T, typename alloc>
        inline const T& access ( const std::vector<T,alloc>& m, long i)
        {
            return m[i];
        }

        template <typename kernel_type, typename T, typename alloc>
        inline const T& access ( const std_vector_c<T,alloc>& m, long i)
        {
            return m[i];
        }

        template <typename kernel_type>
        inline const typename kernel_type::sample_type& access ( 
            const typename kernel_type::sample_type& samp, 
            long 
        )
        {
            return samp;
        }

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

        template <typename kernel_type, typename T>
        inline unsigned long size ( const matrix_exp<T>& m)
        {
            return m.size();
        }

        template <typename kernel_type, typename T, typename Rand_type>
        inline unsigned long size ( const random_subset_selector<T,Rand_type>& m)
        {
            return m.size();
        }

        template <typename kernel_type, typename T, typename alloc>
        inline unsigned long size ( const std::vector<T,alloc>& m)
        {
            return m.size();
        }

        template <typename kernel_type, typename T, typename alloc>
        inline unsigned long size ( const std_vector_c<T,alloc>& m)
        {
            return m.size();
        }

        template <typename kernel_type>
        inline unsigned long size ( 
            const typename kernel_type::sample_type&  
        )
        {
            return 1;
        }

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

        template <typename T>
        typename disable_if<is_matrix<T> >::type assert_is_vector(const T&)
        {}

        template <typename T>
        // This funny #ifdef thing is here because gcc sometimes gives a warning 
        // about v being unused otherwise.
#ifdef ENABLE_ASSERTS
        void assert_is_vector(const matrix_exp<T>& v)
#else
        void assert_is_vector(const matrix_exp<T>& )
#endif
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(is_vector(v) == true,
                "\tconst matrix_exp kernel_matrix()"
                << "\n\t You have to supply this function with row or column vectors"
                << "\n\t v.nr(): " << v.nr()
                << "\n\t v.nc(): " << v.nc()
                );
        }

    }

    template <typename K, typename vect_type1, typename vect_type2>
    struct op_kern_mat  
    {
        op_kern_mat( 
            const K& kern_, 
            const vect_type1& vect1_,
            const vect_type2& vect2_
        ) : 
            kern(kern_), 
            vect1(vect1_),
            vect2(vect2_) 
        {
            // make sure the requires clauses get checked eventually
            impl::assert_is_vector(vect1);
            impl::assert_is_vector(vect2);
        }

        const K& kern;
        const vect_type1& vect1;
        const vect_type2& vect2;

        typedef typename K::scalar_type type;

        const static long cost = 100;
        const static long NR = (is_same_type<vect_type1,typename K::sample_type>::value) ? 1 : 0;
        const static long NC = (is_same_type<vect_type2,typename K::sample_type>::value) ? 1 : 0;

        typedef const type const_ret_type;
        typedef typename K::mem_manager_type mem_manager_type;
        typedef row_major_layout layout_type;

        const_ret_type apply (long r, long c ) const 
        { 
            return kern(impl::access<K>(vect1,r), impl::access<K>(vect2,c)); 
        }

        long nr () const { return impl::size<K>(vect1); }
        long nc () const { return impl::size<K>(vect2); }

        template <typename U> bool aliases               ( const matrix_exp<U>& item ) const { return alias_helper(item.ref()); }
        template <typename U> bool destructively_aliases ( const matrix_exp<U>& item ) const { return alias_helper(item.ref()); }

        template <typename U> bool alias_helper  ( const U& ) const { return false; }

        typedef typename K::sample_type samp_type;

        // Say we destructively alias if one of the vect* objects is actually item.
        bool alias_helper                   (const samp_type& item ) const { return are_same(item, vect1) || are_same(item, vect2); }
        template <typename U> bool are_same (const samp_type& , const U& )         const { return false; }
        bool are_same                       (const samp_type& a, const samp_type& b) const { return (&a == &b); }
    }; 

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

    template <
        typename K,
        typename V1,
        typename V2 
        >
    const matrix_op<op_kern_mat<K,V1,V2> > kernel_matrix (
        const K& kern,
        const V1& v1,
        const V2& v2
        )
    {
        typedef op_kern_mat<K,V1,V2> op;
        return matrix_op<op>(op(kern,v1,v2));
    }
    
// ----------------------------------------------------------------------------------------

    template <
        typename K,
        typename V
        >
    const matrix_op<op_kern_mat<K,V,V> > kernel_matrix (
        const K& kern,
        const V& v
        )
    {
        typedef op_kern_mat<K,V,V> op;
        return matrix_op<op>(op(kern,v,v));
    }
    
// ----------------------------------------------------------------------------------------

    template <
        typename matrix_dest_type,
        typename K,
        typename V
        >
    inline void matrix_assign (
        matrix_dest_type& dest,
        const matrix_exp<matrix_op<op_kern_mat<K,V,V> > >& src
    )
    /*!
        Overload matrix assignment so that when a kernel_matrix expression
        gets assigned it only evaluates half the kernel matrix (since it is symmetric)
    !*/
    {
        // if this is a symmetric kernel matrix
        if (&src.ref().op.vect1 == &src.ref().op.vect2)
        {
            for (long r = 0; r < src.nr(); ++r)
            {
                for (long c = r; c < src.nc(); ++c)
                {
                    dest(r,c) = dest(c,r) = src(r,c);
                }
            }
        }
        else
        {
            // This isn't a symmetric kernel matrix so just do a normal assignment loop
            for (long r = 0; r < src.nr(); ++r)
            {
                for (long c = 0; c < src.nc(); ++c)
                {
                    dest(r,c) = src(r,c);
                }
            }
        }
    }

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

}

#endif // DLIB_SVm_KERNEL_MATRIX_

