// -*- mode:c++ -*-
//
// Header file slice_handler.hpp
//
// Copyright (c) 2003 Raoul M. Gough
//
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy
// at http://www.boost.org/LICENSE_1_0.txt)
//
// History
// =======
// 2003/ 9/ 9   rmg     File creation
// 2008/12/08   Roman   Change indexing suite layout
// 2009/01/30   Roman   patch from Maik Beckmann was applied - fixes error:
//                      // error: no class template named 'extract_return_type'
// $Id: slice_handler.hpp,v 1.1.2.10 2003/11/24 16:35:52 raoulgough Exp $
//

#ifndef BOOST_PYTHON_INDEXING_SLICE_HANDLER_HPP
#define BOOST_PYTHON_INDEXING_SLICE_HANDLER_HPP

#include <boost/python/handle.hpp>
#include <boost/python/object.hpp>
#include <boost/python/list.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/make_function.hpp>
#include <boost/mpl/apply.hpp>
#include <algorithm>

#include <indexing_suite/slice.hpp>
#include <indexing_suite/python_iterator.hpp>

namespace boost { namespace python { namespace indexing {
  template<class Algorithms, class Policy>
  struct slice_handler
  {
    static boost::python::object make_getitem (Policy const &);
    static boost::python::object make_setitem (Policy const &);
    static boost::python::object make_delitem (Policy const &);
    static boost::python::object make_extend  (Policy const &);

  private:
    typedef typename Algorithms::container container;
    typedef typename Algorithms::reference reference;
    typedef typename Algorithms::slice_helper slice_helper;

    static boost::python::list get_slice (container &, slice);
    static void set_slice (container &, slice, boost::python::object);
    static void del_slice (container &, slice);
    static void extend (container &, boost::python::object);
  };

  namespace detail {
    template<typename Policy>
    struct postcall_override
    {
      // This class overrides the Policy's postcall function and
      // result_conveter to handle the list returned from get_slice.
      // The Policy's result_converter is removed, since it gets
      // applied within get_slice. Our postcall override applies the
      // original postcall to each element of the Python list returned
      // from get_slice.

      typedef boost::python::default_result_converter result_converter;
      typedef typename Policy::argument_package argument_package;

      template<class Sig> struct extract_return_type : 
      Policy::template extract_return_type<Sig> { };

      postcall_override (Policy const &p);

      bool precall (PyObject *args);
      PyObject* postcall (PyObject *args, PyObject *result);

    private:
      Policy m_base;
    };
  }

  //////////////////////////////////////////////////////////////////////////
  // Return a function object that implements the slice version of __getitem__
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  boost::python::object
  slice_handler<Algorithms, Policy>
  ::make_getitem (Policy const &policy)
  {
    return
      boost::python::make_function(
          get_slice, detail::postcall_override<Policy> (policy));
  }

  //////////////////////////////////////////////////////////////////////////
  // Return a function object that implements the slice version of __setitem__
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  boost::python::object
  slice_handler<Algorithms, Policy>
  ::make_setitem (Policy const &policy)
  {
    // should we try to get funky with policy::precall?
    return boost::python::make_function (set_slice, policy);
  }

  //////////////////////////////////////////////////////////////////////////
  // Return a function object that implements the slice version of __delitem__
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  boost::python::object
  slice_handler<Algorithms, Policy>
  ::make_delitem (Policy const &policy)
  {
    // should we try to get funky with policy::precall?
    return boost::python::make_function (del_slice, policy);
  }

  //////////////////////////////////////////////////////////////////////////
  // Return a function object that implements extend
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  boost::python::object
  slice_handler<Algorithms, Policy>
  ::make_extend (Policy const &policy)
  {
    // should we try to get funky with policy::precall?
    return boost::python::make_function (extend, policy);
  }

  namespace detail {
    ////////////////////////////////////////////////////////////////////////
    // postcall_override constructor
    ////////////////////////////////////////////////////////////////////////

    template<class Policy>
    postcall_override<Policy>::postcall_override (Policy const &p)
      : m_base (p)
    {
    }

    ////////////////////////////////////////////////////////////////////////
    // precall forwarder
    ////////////////////////////////////////////////////////////////////////

    template<class Policy>
    bool postcall_override<Policy>::precall (PyObject *args)
    {
      return m_base.precall (args);
    }

    ////////////////////////////////////////////////////////////////////////
    // Apply base postcall to each element of the list returend by get_slice
    ////////////////////////////////////////////////////////////////////////

    template<class Policy>
    PyObject *
    postcall_override<Policy>::postcall (PyObject *args, PyObject *result)
    {
      int size = PyList_Size (result);

      for (int count = 0; count < size; ++count)
        {
          m_base.postcall (args, PyList_GetItem (result, count));
        }

      return result;
    }
  } // namespace detail

  //////////////////////////////////////////////////////////////////////////
  // Implementation for the slice version of __getitem__
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  boost::python::list
  slice_handler<Algorithms, Policy>
  ::get_slice (container &c, slice sl)
  {
    typedef typename Policy::result_converter converter_type;
    typedef typename Algorithms::reference reference;

    typename boost::mpl::apply1<converter_type, reference>::type
      converter;

    boost::python::list result;

    slice_helper helper (Algorithms::make_slice_helper (c, sl));

    while (helper.next())
      {
        // Apply the result converter (only) to each element before
        // appending. postcall is done in postcall_override

        result.append (boost::python::handle<> (converter (helper.current())));
      }

    return result;
  }

  //////////////////////////////////////////////////////////////////////////
  // Implementation for the slice version of __setitem__
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  void
  slice_handler<Algorithms, Policy>
  ::set_slice (container &c, slice sl, boost::python::object values)
  {
    python_iterator value_iter (values);

    // Try two kinds of extractors - the first is more efficient (using
    // a reference to existing object, if possible and sensible) and the
    // second allowing implicit conversions.

    typedef boost::python::extract<
        BOOST_DEDUCED_TYPENAME Algorithms::value_param> extractor1;

    typedef boost::python::extract<
        BOOST_DEDUCED_TYPENAME Algorithms::value_type> extractor2;

    // Note: any error during this operation will probably leave the
    // container partially updated. This can occur (for example) if the
    // replacement sequence is of a different length to the original
    // slice and the container does not support insertion/deletion.
    // This could be prevented if the length of the replacement sequence
    // is known in advance (via __len__, for example) but not otherwise.

    slice_helper write_helper (Algorithms::make_slice_helper (c, sl));

    // Overwrite and/or insert elements
    while (value_iter.next())
      {
        extractor1 ex1 (value_iter.current());

        if (ex1.check())
          {
            write_helper.write (ex1);
          }

        else
          {
            write_helper.write (extractor2 (value_iter.current()));
          }
      }

    if (write_helper.next())
      {
        // We've run out of elements to read, but write_helper is not
        // finished. Erase the remaining element(s) in the slice
        write_helper.erase_remaining();
      }
  }

  //////////////////////////////////////////////////////////////////////////
  // Implementation for the slice version of __delitem__
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  void
  slice_handler<Algorithms, Policy>
  ::del_slice (container &c, slice sl)
  {
    slice_helper helper (Algorithms::make_slice_helper (c, sl));

    if (helper.next())
      {
        helper.erase_remaining();
      }
  }

  //////////////////////////////////////////////////////////////////////////
  // Implementation of extend
  //////////////////////////////////////////////////////////////////////////

  template<class Algorithms, class Policy>
  void
  slice_handler<Algorithms, Policy>
  ::extend (container &c, boost::python::object values)
  {
    // Set up a slice at the tail end of the container, and use
    // set_slice to do all the hard work.

    boost::python::object length
      ((boost::python::handle<>
        (PyInt_FromLong (Algorithms::size (c)))));

    slice sl
      ((boost::python::handle<>
        (PySlice_New
         (length.ptr(),
          boost::python::object().ptr(),
          boost::python::object().ptr()))));

    set_slice (c, sl, values);
  }
} } }

#endif // BOOST_PYTHON_INDEXING_SLICE_HANDLER_HPP
