/* -----------------------------------------------------------------------------
 * scicontainer.swg
 *
 * Scilab list <-> C++ container wrapper
 *
 * This wrapper, and its iterator, allows a general use (and reuse) of
 * the mapping between C++ and Scilab, thanks to the C++ templates.
 *
 * Of course, it needs the C++ compiler to support templates, but
 * since we will use this wrapper with the STL containers, that should
 * be the case.
 * ----------------------------------------------------------------------------- */

%{
#include <iostream>
%}

#if !defined(SWIG_NO_EXPORT_ITERATOR_METHODS)
# if !defined(SWIG_EXPORT_ITERATOR_METHODS)
#  define SWIG_EXPORT_ITERATOR_METHODS SWIG_EXPORT_ITERATOR_METHODS
# endif
#endif


// #define (SWIG_SCILAB_EXTRA_NATIVE_CONTAINERS)
// if defined: sequences in return are converted from/to Scilab lists or matrices
// if not defined: sequences are passed from/to Scilab as pointers

%{
#define SWIG_STD_NOASSIGN_STL
%}

%include <sciiterators.swg>
%include <scisequence.swg>

%{
#include <stdexcept>
%}

%include <exception.i>
%include <std_except.i>

%fragment("SciSequence_Cont", "header",
	  fragment="StdTraits",
	  fragment="SwigSciIterator_T")
{
namespace swig
{
  template <class T>
  struct SciSequence_Ref
  {
    SciSequence_Ref(const SwigSciObject& seq, int index)
      : _seq(seq), _index(index)
    {
      if (traits_as_sequence<T>::get(_seq, &piSeqAddr) != SWIG_OK)
      {
        throw std::invalid_argument("Cannot get sequence data.");
      }
    }

    operator T () const
    {
      try
      {
        return traits_asval_sequenceitem<T>::asval(_seq, piSeqAddr, _index);
      }
      catch (std::exception& e)
      {
        SWIG_exception(SWIG_RuntimeError, e.what());
      }
    }

    SciSequence_Ref& operator=(const T& v)
    {
      // TODO
      return *this;
    }

    private:
      SwigSciObject _seq;
      int _index;
      void *piSeqAddr;
  };


  template <class T>
  struct SciSequence_ArrowProxy
  {
    SciSequence_ArrowProxy(const T& x): m_value(x) {}
    const T* operator->() const { return &m_value; }
    operator const T*() const { return &m_value; }
    T m_value;
  };

  template <class T, class Reference >
  struct SwigSciSequence_InputIterator
  {
    typedef SwigSciSequence_InputIterator<T, Reference > self;

    typedef std::random_access_iterator_tag iterator_category;
    typedef Reference reference;
    typedef T value_type;
    typedef T* pointer;
    typedef int difference_type;

    SwigSciSequence_InputIterator()
    {
    }

    SwigSciSequence_InputIterator(const SwigSciObject& seq, int index)
      : _seq(seq), _index(index)
    {
    }

    reference operator*() const
    {
      return reference(_seq, _index);
    }

    SciSequence_ArrowProxy<T>
    operator->() const {
      return SciSequence_ArrowProxy<T>(operator*());
    }

    bool operator==(const self& ri) const
    {
      return (_index == ri._index);
    }

    bool operator!=(const self& ri) const
    {
      return !(operator==(ri));
    }

    self& operator ++ ()
    {
      ++_index;
      return *this;
    }

    self& operator -- ()
    {
      --_index;
      return *this;
    }

    self& operator += (difference_type n)
    {
      _index += n;
      return *this;
    }

    self operator +(difference_type n) const
    {
      return self(_seq, _index + n);
    }

    self& operator -= (difference_type n)
    {
      _index -= n;
      return *this;
    }

    self operator -(difference_type n) const
    {
      return self(_seq, _index - n);
    }

    difference_type operator - (const self& ri) const
    {
      return _index - ri._index;
    }

    bool operator < (const self& ri) const
    {
      return _index < ri._index;
    }

    reference
    operator[](difference_type n) const
    {
      return reference(_seq, _index + n);
    }

  private:
    SwigSciObject _seq;
    difference_type _index;
  };

  template <class T>
  struct SciSequence_Cont
  {
    typedef SciSequence_Ref<T> reference;
    typedef const SciSequence_Ref<T> const_reference;
    typedef T value_type;
    typedef T* pointer;
    typedef int difference_type;
    typedef int size_type;
    typedef const pointer const_pointer;
    typedef SwigSciSequence_InputIterator<T, reference> iterator;
    typedef SwigSciSequence_InputIterator<T, const_reference> const_iterator;

    SciSequence_Cont(const SwigSciObject& seq) : _seq(seq)
    {
    }

    ~SciSequence_Cont()
    {
    }

    size_type size() const
    {
      int iSeqSize;
      if (traits_as_sequence<value_type>::size(_seq, &iSeqSize) == SWIG_OK)
      {
        return iSeqSize;
      }
      else
      {
        return SWIG_ERROR;
      }
    }

    bool empty() const
    {
      return size() == 0;
    }

    iterator begin()
    {
      return iterator(_seq, 0);
    }

    const_iterator begin() const
    {
      return const_iterator(_seq, 0);
    }

    iterator end()
    {
      return iterator(_seq, size());
    }

    const_iterator end() const
    {
      return const_iterator(_seq, size());
    }

    reference operator[](difference_type n)
    {
      return reference(_seq, n);
    }

    const_reference operator[](difference_type n)  const
    {
      return const_reference(_seq, n);
    }

  private:
    SwigSciObject _seq;
  };
}
}

%define %swig_sequence_iterator(Sequence...)
#if defined(SWIG_EXPORT_ITERATOR_METHODS)
  class iterator;
  class reverse_iterator;
  class const_iterator;
  class const_reverse_iterator;

  %typemap(out,noblock=1,fragment="SciSequence_Cont")
    iterator, reverse_iterator, const_iterator, const_reverse_iterator {
    %set_output(SWIG_NewPointerObj(swig::make_output_iterator(%static_cast($1,const $type &)),
           swig::SciSwigIterator::descriptor(),SWIG_POINTER_OWN));
  }
  %typemap(out,fragment="SciSequence_Cont")
    std::pair<iterator, iterator>, std::pair<const_iterator, const_iterator> {
    // TODO: return a Scilab list from the pair (see code for Octave)
  }

  %fragment("SciSwigPairBoolOutputIterator", "header",
    fragment=SWIG_From_frag(bool), fragment="SciSequence_Cont") {}

  %typemap(out,fragment="SciSwigPairBoolOutputIterator")
    std::pair<iterator, bool>, std::pair<const_iterator, bool> {
    // TODO: return a Scilab list from the pair (see code for Octave)
  }

  %typemap(in,noblock=1,fragment="SciSequence_Cont")
    iterator(swig::SciSwigIterator *iter = 0, int res),
    reverse_iterator(swig::SciSwigIterator *iter = 0, int res),
    const_iterator(swig::SciSwigIterator *iter = 0, int res),
    const_reverse_iterator(swig::SciSwigIterator *iter = 0, int res) {
    res = SWIG_ConvertPtr((SwigSciObject)$input, %as_voidptrptr(&iter), swig::SciSwigIterator::descriptor(), 0);
    if (!SWIG_IsOK(res) || !iter) {
      %argument_fail(SWIG_TypeError, "$type", $symname, $argnum);
    } else {
      swig::SwigSciIterator_T<$type > *iter_t = dynamic_cast<swig::SwigSciIterator_T<$type > *>(iter);
      if (iter_t) {
	$1 = iter_t->get_current();
      } else {
	%argument_fail(SWIG_TypeError, "$type", $symname, $argnum);
      }
    }
  }

  %typecheck(%checkcode(ITERATOR),noblock=1,fragment="SciSequence_Cont")
    iterator, reverse_iterator, const_iterator, const_reverse_iterator {
    swig::SciSwigIterator *iter = 0;
    int res = SWIG_ConvertPtr((SwigSciObject)$input, %as_voidptrptr(&iter), swig::SciSwigIterator::descriptor(), 0);
    $1 = (SWIG_IsOK(res) && iter && (dynamic_cast<swig::SwigSciIterator_T<$type > *>(iter) != 0));
  }

  %fragment("SciSequence_Cont");
#endif //SWIG_EXPORT_ITERATOR_METHODS
%enddef

// The Scilab container methods

%define %swig_container_methods(Container...)
%enddef

%define %swig_sequence_methods_common(Sequence...)
  %swig_sequence_iterator(%arg(Sequence))
  %swig_container_methods(%arg(Sequence))

%enddef

%define %swig_sequence_methods(Sequence...)
  %swig_sequence_methods_common(%arg(Sequence))
%enddef

%define %swig_sequence_methods_val(Sequence...)
  %swig_sequence_methods_common(%arg(Sequence))
%enddef

//
// Common fragments
//

%fragment("StdSequenceTraits","header",
	  fragment="StdTraits",
	  fragment="SciSequence_Cont",
    fragment=SWIG_Traits_SequenceItem_frag(ptr))
{
namespace swig {
  template <class SciSeq, class Seq>
  inline void
  assign(const SciSeq& sciSeq, Seq* seq) {
%#ifdef SWIG_STD_NOASSIGN_STL
    typedef typename SciSeq::value_type value_type;
    typename SciSeq::const_iterator it = sciSeq.begin();
    for (;it != sciSeq.end(); ++it) {
      seq->insert(seq->end(),(value_type)(*it));
    }
%#else
    seq->assign(sciSeq.begin(), sciSeq.end());
%#endif
  }

  template <class Seq, class T = typename Seq::value_type >
  struct traits_asptr_stdseq {
    typedef Seq sequence;
    typedef T value_type;

    static int asptr(const SwigSciObject& obj, sequence **seq)
    {
      swig_type_info *typeInfo = swig::type_info<sequence>();
      if (typeInfo)
      {
          sequence *p;
          if (SWIG_ConvertPtr(obj, (void**)&p, typeInfo, 0) == SWIG_OK)
          {
            if (seq)
              *seq = p;
            return SWIG_OLDOBJ;
          }
      }

      if (traits_as_sequence<value_type>::check(obj) == SWIG_OK)
      {
        try
        {
          SciSequence_Cont<value_type> sciSeq(obj);
          if (seq)
          {
            *seq = new sequence();
            assign(sciSeq, *seq);
            return SWIG_NEWOBJ;
          }
          else
          {
            return true;
          }
        }
        catch (std::exception& e)
        {
          SWIG_exception(SWIG_RuntimeError, e.what());
        }
      }
    }
  };

  template <class Seq, class T = typename Seq::value_type >
  struct traits_from_stdseq {
    typedef Seq sequence;
    typedef T value_type;
    typedef typename Seq::size_type size_type;
    typedef typename sequence::const_iterator const_iterator;

    static SwigSciObject from(const sequence& seq)
    {
      %#ifdef SWIG_SCILAB_EXTRA_NATIVE_CONTAINERS
      swig_type_info *typeInfo = swig::type_info<sequence>();
      if (typeInfo)
      {
        return SWIG_NewPointerObj(new sequence(seq), typeInfo, SWIG_POINTER_OWN);
      }
      %#endif

      try
      {
        void *data;
        size_type size = seq.size();
        if (traits_from_sequence<value_type>::create(size, &data) == SWIG_OK) {
          const_iterator it;
          int index = 0;
          for (it = seq.begin(); it != seq.end(); ++it)
          {
            traits_from_sequenceitem<value_type>::from(data, index, *it);
            index++;
          }
          return traits_from_sequence<value_type>::set(size, data);
        }
        return SWIG_OK;
      }
      catch (std::exception& e)
      {
        SWIG_exception(SWIG_RuntimeError, e.what());
      }
    }
  };
}
}
