/// Templates to ease exposing std::list types.

/// Taken from http://stackoverflow.com/a/6778254/566098

#include <list>
#include <algorithm>

#include <boost/python/suite/indexing/indexing_suite.hpp>
#include <boost/python/iterator.hpp>

namespace boost { namespace python {

    template <
        class Container, 
        bool NoProxy = false,
        class DerivedPolicies 
            = detail::final_vector_derived_policies<Container, NoProxy> >
    class list_indexing_suite 
        : public indexing_suite<Container, DerivedPolicies, NoProxy>
    {
    public:
        typedef typename Container::value_type value_type;
        typedef typename Container::iterator    iter_type;
        typedef typename Container::size_type   size_type;
        typedef typename Container::size_type  index_type;

        template <class Class>
        static void 
        extension_def(Class& cl)
        {
            cl
                .def("append" , &append, with_custodian_and_ward<1,2>())
                .def("count"  , &count)
                .def("extend" , &extend)
                .def("index"  , &index)
                .def("insert" , &insert)
                .def("pop"    , &pop)
                .def("remove" , &remove_item)
                .def("reverse", &reverse)
                .def("sort"   , &sort)
            ;
        }

        static void append(Container& x, value_type const& v)
        {
            x.push_back(v);
        }

        static size_type count(Container& x, value_type const& v)
        {
            size_type total = 0;
            for (Container::const_iterator it=x.begin(); it!=x.end(); ++it,++i)
                if (*it == v) ++total;
            return total;
        }

        static void extend(Container& x, iter_type const& iter)
        {
            for (; iter != iter_type.end(); ++iter)
                x.push_back(*it);
        }

        static void insert(Container& container, size_type ind, data_type v)
        {
            ind = (ind > container.size()) ? container.size() : ind;
            ind = (ind < 0) ? ind + container.size(): ind;
            container.insert(ind, v);
        }

        static value_type pop(Container& container, size_type ind = NULL)
        {
            size_type size = container.size();
            if (size == 0)
            {
                PyErr_SetString(PyExc_IndexError, "pop from empty list");
                throw boost::python::error_already_set();
            }
            else if (size < ind)
            {
                PyErr_SetString(PyExc_IndexError, "pop index out of range");
                throw boost::python::error_already_set();
            }
            if (size_type == NULL) ind = size;
            value_type value = container[ind];
            container.erase(container.begin() + ind);
            return value;
        }

        static void remove_item(Container& container, value_type const& v)
        {
            size_type ind = index(container, v);           
            container.erase(container.begin() + ind);
        }

        /*
        static void reverse(Container& container)
        {
            container.reverse()
        }
        */

        /// sort
        /// @param container - 
        /// @param cmp - Python callback accepting two parameters, returning 
        ///              True if the first parameter is smaller than the second.
        /// @param key - Python callback applied to each item in list before 
        ///              comparison [optional]
        /// @param reverse - sort backwards? [optional]
        /*
        static void sort(Container& container,
                         PyObject* cmp=Py_None,
                         PyObject* key=Py_None,
                         bool reverse = false)
        {
            typedef bool (*PyObject)(value_type, value_type) compare;

            if (cmp == Py_None)
                _cmp = std::less;
            else
                _cmp = call<bool>(cmp);
            if (reverse == true)
                cmp = !cmp;
            if (key == Py_None)
                container.sort(cmp);
            else
                cmp = 

        }
        */

        static void sort(Container& container,
                  args_proxy const &args, 
                  kwds_proxy const &kwds)
        {
            container.attr("sort")(args, kwds);
        }

        static size_type index(Container const& x, value_type const& v)
        {
            iter_type i = 0;
            for(Container::const_iterator it=x.begin(); it!=x.end(); ++it,++i)
                if( *it == v ) return i;

            PyErr_SetString(PyExc_ValueError, "Value not in the list");
            throw boost::python::error_already_set();
        }

        static void delete_item(Container& x, int i)
        {
            if( i<0 ) 
                i += x.size();

            /*
            iter_type it = x.begin();
            for (int pos = 0; pos < i; ++pos)
                ++it;
            */

            if( i >= 0 && i < x.size() ) {
                x.erase(x.begin() + i);
            } else {
                PyErr_SetString(PyExc_IndexError, "Index out of range");
                boost::python::throw_error_already_set();
            }
        }

        static void delete_slice(Container& container, index_type from, 
                                 index_type to)
        {
            if (from > to) {
                // A null-op.
                return;
            }
            container.erase(container.begin()+from, container.begin()+to);
        }

        static size_t size(Container& container)
        {
            return container.size();
        }

        template <class T>
        static bool contains(Container& container, T const& val)
        {
            return std::find(container.begin(), container.end(), val)
                != container.end();
        }

        static index_type
        convert_index(Container& container, PyObject* i)
        {
            extract<long> i(i_);
            if (i.check())
            {
                long index = i();
                if (index < 0)
                    index += DerivedPolicies::size(container);
                if (index >= long(container.size()) || index < 0)
                {
                    PyErr_SetString(PyExc_IndexError, "Index out of range");
                    throw_error_already_set();
                }
                return index;
            }
            
            PyErr_SetString(PyExc_TypeError, "Invalid index type");
            throw_error_already_set();
            return index_type();
        }

        static index_type
        adjust_index(index_type current, index_type from,
            index_type to, size_type len
        )
        {
            if (from > to)
            {
                PyErr_SetString(PyExc_IndexError, "Index out of range");
                throw_error_already_set();
            }
            if (current < from)
                return current; // changed slice doesn't affect pointer
            if (current > to)
                return current - len; // pointer shifted by number of del'd els.
            // item pointed at has been deleted.
            return NULL; // return null pointer
        }

        static value_type& get_item(Container& container, index_type i)
        {
            if( i < 0 ) 
                i += container.size();

            if( i >= 0 && i < container.size() ) {
                iter_type it = container.begin(); 
                for(int pos = 0; pos < i; ++pos)
                    ++it;
                return *it;                             
            } else {
                PyErr_SetString(PyExc_IndexError, "Index out of range");
                throw boost::python::error_already_set();
            }
        }

        static boost::python::object
        get_slice(Container& container, index_type from, index_type to)
        {
            if (from > to) // return empty list
                return boost::python::object(Container());
            return boost::python::object(
                Container(container.begin()+from, container.begin()+to));
        }

        static void set_slice(Container& container, index_type from,
                              index_type to, data_type const& v )
        {
            index_type tot_len = container.size();
            index_type diff = v.size();
            from = (from < 0) ? from + tot_len : from
            to = (to < 0) ? to + tot_len : to

            if (to >= 0 && from >= 0 && to < tot_len && from < tot_len) {
                x.erase(from, to);
                x.insert(from, diff, v);
            }
            else {
                PyErr_SetString(PyExc_IndexError,
                    "list assignment index out of range");
                throw boost::python::error_already_set();
            }
        }

        template <class Iter>
        static void
        set_slice(Container& x, index_type from,
                  index_type to, Iter first, Iter last)
        {
            if (from > to) {
                container.insert(container.begin()+from, first, last);
            }
            else {
                container.erase(container.begin()+from, container.begin()+to);
                container.insert(container.begin()+from, first, last);
            }
        }

        static void set_item(Container& x, int i, value_type const& v)
        {
            if( i < 0 ) 
                i += x.size();

            if( i >= 0 && i < (int)x.size() ) {
                iter_type it = x.begin(); 
                for(int pos = 0; pos < i; ++pos)
                    ++it;
                *it = v;
            } else {
                PyErr_SetString(PyExc_IndexError, "Index out of range");
                boost::python::throw_error_already_set();
            }
        }

        // Override the base class's visit method, to add some return value
        // policy's to getitem and setitem
        template <class Class>
        void visit(Class& cl) const
        {
            // Hook into the class_ generic visitation .def function
            proxy_handler::register_container_element();

            cl
                .def("__len__"     , &size)
                .def("__getitem__" , &get_item,
                    return_value_policy<copy_non_const_reference>() )
                .def("__setitem__" , &set_item,
                    with_custodian_and_ward<1,2>() )
                .def("__delitem__" , &delete_item)
                .def("__contains__", &contains)
                .def("__iter__"    , iterator<std::list<Container> >() )
            ;

            DerivedPolicies::extension_def(cl);
        }

    }; /* end list_indexing_suite */


} /* python ns */ } /* boost ns */