/* Copyright (c) 1997-2016
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.polymake.org

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
--------------------------------------------------------------------------------
*/

#ifndef POLYMAKE_INTERNAL_ITERATOR_ZIPPER_H
#define POLYMAKE_INTERNAL_ITERATOR_ZIPPER_H

#include "polymake/internal/comparators_basic_defs.h"
#include "polymake/ContainerUnion.h"
#include "polymake/internal/operations.h"

namespace pm {

enum {
   zipper_lt=1, zipper_eq=2, zipper_gt=4,       // correspond to 1<<cmp_XX+1
   zipper_cmp=zipper_lt+zipper_eq+zipper_gt,    // mask for all of them
   zipper_first= (zipper_lt<<6),                // first iterator is in a dereferenceable position
   zipper_second= (zipper_gt<<3),               // second iterator is ...
   zipper_both= zipper_first+zipper_second      // both are ...
};

struct forward_zipper {
   static int state(int cmp) { return 1 << 1+cmp; }
};

struct set_union_zipper : forward_zipper {
   static int stable(int) { return true; }
   static const bool end1=false, end2=false;
   static bool contains(bool c1, bool c2) { return c1 || c2; }
};

struct set_difference_zipper : forward_zipper {
   static int stable(int state) { return state & zipper_lt; }
   static const bool end1=true, end2=false;
   static bool contains(bool c1, bool c2) { return c1 && !c2; }
};

struct set_intersection_zipper : forward_zipper {
   static int stable(int state) { return state & zipper_eq; }
   static const bool end1=true, end2=true;
   static bool contains(bool c1, bool c2) { return c1 && c2; }
};

struct set_symdifference_zipper : forward_zipper {
   static int stable(int state) { return state & zipper_lt+zipper_gt; }
   static const bool end1=false, end2=false;
   static bool contains(bool c1, bool c2) { return c1 ^ c2; }
};

template <typename Zipper>
struct reverse_zipper : public Zipper {
   static int state(int cmp) { return 1 << 1-cmp; }
};

template <typename Iterator1, typename Iterator2, typename Comparator, typename Controller,
          bool use_index1=false, bool use_index2=false>
class iterator_zipper : public Iterator1 {
public:
   typedef Iterator1 first_type;
   typedef Iterator2 second_type;
   typedef typename least_derived_class<bidirectional_iterator_tag,
                                        typename iterator_traits<Iterator1>::iterator_category,
                                        typename iterator_traits<Iterator2>::iterator_category>::type
      iterator_category;

   typedef iterator_zipper<typename iterator_traits<Iterator1>::iterator, typename iterator_traits<Iterator2>::iterator,
                           Comparator, Controller, use_index1, use_index2>
      iterator;
   typedef iterator_zipper<typename iterator_traits<Iterator1>::const_iterator, typename iterator_traits<Iterator2>::const_iterator,
                           Comparator, Controller, use_index1, use_index2>
      const_iterator;

   Iterator2 second;
protected:
   int state;
   Comparator cmp;
   Controller ctl;

   cmp_value compare(std::false_type, std::false_type) const { return cmp(**this, *second); }
   cmp_value compare(std::false_type, std::true_type) const { return cmp(**this, second.index()); }
   cmp_value compare(std::true_type, std::false_type) const { return cmp(first_type::index(), *second); }
   cmp_value compare(std::true_type, std::true_type) const { return cmp(first_type::index(), second.index()); }

   void compare()
   {
      state &= ~zipper_cmp;
      state += ctl.state(compare(bool_constant<use_index1>(), bool_constant<use_index2>()));
   }

   void incr()
   {
      const int cur_state=state;
      if (cur_state & int(zipper_lt)+int(zipper_eq)) {
         first_type::operator++();
         if (first_type::at_end()) {
            if (ctl.end1) {
               state=0; return;
            } else {
               state>>=3;
            }
         }
      }
      if (cur_state & int(zipper_gt)+int(zipper_eq)) {
         ++second;
         if (second.at_end()) {
            if (ctl.end2) {
               state=0;
            } else {
               state>>=6;
            }
         }
      }
   }

   void decr()
   {
      const int cur_state=state;
      state=zipper_both;
      if (!(cur_state & int(zipper_lt)))
         first_type::operator--();
      if (!(cur_state & int(zipper_gt)))
         --second;
   }

   void init()
   {
      state=zipper_both;
      if (first_type::at_end()) {
         if (ctl.end1) {
            state=0; return;
         } else {
            state>>=3;
         }
      }
      if (second.at_end()) {
         if (ctl.end2) {
            state=0;
         } else {
            state>>=6;
         }
         return;
      }
      while (state>=zipper_both) {
         compare();
         if (ctl.stable(state)) break;
         incr();
      }
   }

   template <typename, typename, typename, typename, bool, bool> friend class iterator_zipper;
   template <typename, typename, bool, bool> friend class iterator_product;
public:
   iterator_zipper() : state(0) {}
   iterator_zipper(const iterator& it)
      : first_type(static_cast<const typename iterator::first_type&>(it))
      , second(it.second)
      , state(it.state)
      , ctl(it.ctl) {}

   template <typename SourceIterator1, typename SourceIterator2,
             typename enable=typename std::enable_if<is_const_compatible_with<SourceIterator1, Iterator1>::value &&
                                                     is_const_compatible_with<SourceIterator2, Iterator2>::value>::type>
   iterator_zipper(const SourceIterator1& first_arg, const SourceIterator2& second_arg)
      : first_type(first_arg)
      , second(second_arg)
   {
      init();
   }

   iterator_zipper& operator++ ()
   {
      do {
         incr();
         if (state<zipper_both) break;
         compare();
      } while (!ctl.stable(state));
      return *this;
   }
   const iterator_zipper operator++ (int) { iterator_zipper copy=*this; operator++(); return copy; }

   iterator_zipper& operator-- ()
   {
      static_assert(iterator_pair_traits<Iterator1, Iterator2>::is_bidirectional, "iterator is not bidirectional");
      do {
         decr(); compare();
      } while (!ctl.stable(state));
      return *this;
   }
   const iterator_zipper operator-- (int) { iterator_zipper copy=*this; operator--(); return copy; }

   bool at_end() const { return state==0; }

   template <typename Other>
   typename std::enable_if<is_derived_from_any<Other, iterator, const_iterator>::value, bool>::type
   operator== (const Other& it) const
   {
      typedef typename is_derived_from_any<Other, iterator, const_iterator>::type other_iterator;

      return at_end() ? it.at_end() :
             static_cast<const first_type&>(*this)==static_cast<const typename other_iterator::first_type&>(it)
             && second==static_cast<const other_iterator&>(it).second;
   }

   template <typename Other>
   typename std::enable_if<is_derived_from_any<Other, iterator, const_iterator>::value, bool>::type
   operator!= (const Other& it) const
   {
      return !operator==(it);
   }

   void rewind()
   {
      static_assert(check_iterator_feature<first_type, rewindable>::value && check_iterator_feature<second_type, rewindable>::value,
                    "iterator is not rewindable");
      first_type::rewind(); second.rewind();
      init();
   }
};

template <typename Iterator1, typename Iterator2, typename Comparator, typename Controller,
          bool use_index1, bool use_index2, typename Feature>
struct check_iterator_feature<iterator_zipper<Iterator1, Iterator2, Comparator, Controller, use_index1, use_index2>,
                              Feature> {
   static const bool value=check_iterator_feature<Iterator1, Feature>::value &&
                           check_iterator_feature<Iterator2, Feature>::value;
};

template <typename Iterator1, typename Iterator2, typename Comparator, typename Controller,
          bool use_index1, bool use_index2>
struct has_partial_state< iterator_zipper<Iterator1, Iterator2, Comparator, Controller, use_index1, use_index2> > : std::true_type {};

template <typename Comparator, typename Controller, bool use_index1=false, bool use_index2=false>
struct zipping_coupler {
   template <typename Iterator1, typename Iterator2, typename ExpectedFeatures>
   struct defs {
      typedef iterator_zipper<Iterator1, Iterator2, Comparator, Controller, use_index1, use_index2> iterator;

      typedef typename mix_features<ExpectedFeatures,
                                    typename concat_if<use_index1, indexed, end_sensitive>::type >::type
         needed_features1;
      typedef typename mix_features<ExpectedFeatures,
                                    typename concat_if<use_index2, indexed, end_sensitive>::type >::type
         needed_features2;
   };
};

template <typename Comparator, typename Controller, bool use_index1, bool use_index2>
struct reverse_coupler< zipping_coupler<Comparator, Controller, use_index1, use_index2> > {
   typedef zipping_coupler< Comparator, reverse_zipper<Controller>, use_index1, use_index2> type;
};

namespace operations {

template <typename OpRef1, typename OpRef2,
          bool _equal=identical_minus_const_ref<OpRef1,OpRef2>::value,
          bool _numeric=(std::numeric_limits<typename deref<OpRef1>::type>::is_specialized &&
                         std::numeric_limits<typename deref<OpRef2>::type>::is_specialized)>
struct zipper_helper
   : union_reference<OpRef1, OpRef2> {};

template <typename OpRef1, typename OpRef2>
struct zipper_helper<OpRef1, OpRef2, false, true>
   : add_result<typename deref<OpRef1>::type, typename deref<OpRef2>::type> {};

template <typename Iterator1Ref, typename Iterator2Ref>
struct zipper {
   typedef Iterator1Ref first_argument_type;
   typedef Iterator2Ref second_argument_type;
   typedef typename zipper_helper<typename iterator_traits<typename deref<Iterator1Ref>::type>::reference,
                                  typename iterator_traits<typename deref<Iterator2Ref>::type>::reference>::type
      result_type;

   result_type operator() (first_argument_type l, second_argument_type) const { return *l; }
   result_type operator() (partial_left, first_argument_type l, second_argument_type) const { return *l; }
   result_type operator() (partial_right, first_argument_type, second_argument_type r) const { return *r; }
};

template <typename Iterator1Ref, typename Iterator2Ref>
struct zipper_index {
   typedef Iterator1Ref first_argument_type;
   typedef Iterator2Ref second_argument_type;
   typedef const int result_type;

   result_type operator() (first_argument_type l, second_argument_type) const { return l.index(); }
   result_type operator() (partial_left, first_argument_type l, second_argument_type) const { return l.index(); }
   result_type operator() (partial_right, first_argument_type, second_argument_type r) const { return r.index(); }
};
} // end namespace operations

template <typename IteratorPair, typename Operation>
class binary_transform_eval<IteratorPair, Operation, true>
   : public transform_iterator_base<IteratorPair, Operation>::type {
   typedef typename transform_iterator_base<IteratorPair, Operation>::type base_t;
public:
   typedef binary_helper<IteratorPair, Operation> helper;
   typedef typename helper::operation operation;
protected:
   operation op;

   typedef Operation op_arg_type;

   binary_transform_eval() {}

   template <typename Operation2>
   binary_transform_eval(const binary_transform_eval<typename iterator_traits<IteratorPair>::iterator, Operation2, true>& it)
      : base_t(static_cast<const typename std::remove_reference_t<decltype(it)>::base_t&>(it))
      , op(helper::create(it.op)) {}

   template <typename SourceIteratorPair>
   binary_transform_eval(const SourceIteratorPair& cur_arg, const op_arg_type& op_arg)
      : base_t(cur_arg)
      , op(helper::create(op_arg)) {}

   template <typename SourceIterator1, typename SourceIterator2>
   binary_transform_eval(const SourceIterator1& first_arg, const SourceIterator2& second_arg, const op_arg_type& op_arg)
      : base_t(first_arg, second_arg)
      , op(helper::create(op_arg)) {}

   template <typename, typename, bool> friend class binary_transform_eval;
public:
   typedef typename operation::result_type reference;

   reference operator* () const
   {
      if (this->state & zipper_lt)
         return op(operations::partial_left(), *helper::get1(*this), this->second);
      if (this->state & zipper_gt)
         return op(operations::partial_right(),
                   static_cast<const typename IteratorPair::first_type&>(*this), *helper::get2(this->second));
      // (this->state & zipper_eq)
      return op(*helper::get1(*this), *helper::get2(this->second));
   }
};

template <typename IteratorPair, typename Operation, typename IndexOperation>
class binary_transform_eval<IteratorPair, pair<Operation, IndexOperation>, true>
   : public binary_transform_eval<IteratorPair, Operation, true> {
   typedef binary_transform_eval<IteratorPair, Operation, true> base_t;
protected:
   typedef binary_helper<IteratorPair,IndexOperation> ihelper;
   typename ihelper::operation iop;
   typedef pair<Operation, IndexOperation> op_arg_type;

   binary_transform_eval() {}

   template <typename Operation2, typename IndexOperation2>
   binary_transform_eval(const binary_transform_eval<typename iterator_traits<IteratorPair>::iterator, pair<Operation2, IndexOperation2>, true>& it)
      : base_t(it)
      , iop(ihelper::create(it.iop)) {}

   template <typename SourceIteratorPair>
   binary_transform_eval(const SourceIteratorPair& cur_arg, const op_arg_type& op_arg)
      : base_t(cur_arg, op_arg.first)
      , iop(ihelper::create(op_arg.second)) {}

   template <typename SourceIterator1, typename SourceIterator2>
   binary_transform_eval(const SourceIterator1& first_arg, const SourceIterator2& second_arg, const op_arg_type& op_arg)
      : base_t(first_arg, second_arg, op_arg.first)
      , iop(ihelper::create(op_arg.second)) {}

   template <typename, typename, bool> friend class binary_transform_eval;

protected:
   int index_impl(std::false_type) const
   {
      return iop(*ihelper::get1(*this), *ihelper::get2(this->second));
   }

   int index_impl(std::true_type) const
   {
      if (this->state & zipper_lt)
         return iop(operations::partial_left(), *ihelper::get1(*this), this->second);
      if (this->state & zipper_gt)
         return iop(operations::partial_right(),
                    static_cast<const typename IteratorPair::first_type&>(*this), *ihelper::get2(this->second));
      // (this->state & zipper_eq)
      return index_impl(std::false_type());
   }
public:
   int index() const
   {
      return index_impl(bool_constant<operations::is_partially_defined<typename ihelper::operation>::value>());
   }
};

} // end namespace pm

#endif // POLYMAKE_INTERNAL_ITERATOR_ZIPPER_H

// Local Variables:
// mode:C++
// c-basic-offset:3
// indent-tabs-mode:nil
// End:
