/* Copyright (c) 1997-2010
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Darmstadt, Germany)
   http://www.polymake.de

   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.
--------------------------------------------------------------------------------
   $Project: polymake $$Id: SelectedSubset.h 9991 2010-12-12 23:36:28Z gawrilow $
*/

#ifndef POLYMAKE_SELECTED_SUBSET_H
#define POLYMAKE_SELECTED_SUBSET_H

#include "polymake/Series.h"
#include "polymake/internal/modified_containers.h"

namespace pm {

template <typename Iterator, typename Predicate>
class unary_predicate_selector : public Iterator {
protected:
   typedef Iterator super;
   typedef unary_helper<Iterator,Predicate> helper;
   typename helper::operation pred;

   void valid_position()
   {
      while (!this->at_end() && !pred(*helper::get(*this))) super::operator++();
   }

   template <typename,typename> friend class unary_predicate_selector;
public:
   typedef typename least_derived< cons<typename iterator_traits<Iterator>::iterator_category,
                                        bidirectional_iterator_tag> >::type
      iterator_category;
   typedef unary_predicate_selector<typename iterator_traits<Iterator>::iterator, Predicate> iterator;
   typedef unary_predicate_selector<typename iterator_traits<Iterator>::const_iterator, Predicate> const_iterator;

   unary_predicate_selector() {}

   template <typename Predicate2>
   unary_predicate_selector(const unary_predicate_selector<typename iterator_traits<Iterator>::iterator, Predicate2>& it)
      : super(it),
        pred(helper::create(it.pred)) {}

   template <typename Predicate2>
   explicit unary_predicate_selector(const unary_predicate_selector<typename iterator_reversed<Iterator>::type, Predicate2>& it)
      : super(iterator_reversed<Iterator>::make(it)),
        pred(helper::create(it.pred)) {}

   unary_predicate_selector(const Iterator& cur_arg, const Predicate& pred_arg=Predicate(), bool at_valid_position=false)
      : super(cur_arg),
        pred(helper::create(pred_arg))
   {
      if (!at_valid_position) valid_position();
   }

   unary_predicate_selector& operator++ ()
   {
      super::operator++();
      valid_position();
      return *this;
   }

   const unary_predicate_selector operator++ (int)
   {
      unary_predicate_selector copy=*this;  operator++();  return copy;
   }

   void rewind()
   {
      typedef typename enable_if<super, check_iterator_feature<super, rewindable>::value>::type error_if_unimplemented;
      super::rewind();
      valid_position();
   }

   // it's the applications' responsibility not to call this at the first position
   unary_predicate_selector& operator-- ()
   {
      typedef typename enable_if<super, iterator_traits<super>::is_bidirectional>::type error_if_unimplemented;
      do
         error_if_unimplemented::operator--();
      while (!pred(*helper::get(*this)));
      return *this;
   }

   const unary_predicate_selector operator-- (int)
   {
      unary_predicate_selector copy=*this;  operator--();  return copy;
   }

private:
   // make them undefined for the case of random_access Iterator
   void operator+=(int);
   void operator-=(int);
   void operator+(int);
   void operator-(int);
   void operator[](int);
};

template <typename Iterator, typename Predicate, typename Feature>
struct check_iterator_feature<unary_predicate_selector<Iterator, Predicate>, Feature>
   : check_iterator_feature<Iterator, Feature> {};

struct unary_predicate_selector_constructor {
   template <typename Iterator, typename Predicate, typename ExpectedFeatures>
   struct defs {
      typedef typename mix_features<ExpectedFeatures, end_sensitive>::type
         needed_features;
      typedef unary_predicate_selector<Iterator,Predicate> iterator;
   };
};

template <typename Iterator, typename Predicate> inline
unary_predicate_selector<Iterator, Predicate>
make_unary_predicate_selector(Iterator it, const Predicate& pred)
{
   return unary_predicate_selector<Iterator, Predicate>(it,pred);
}

template <typename Iterator, typename Predicate>
class input_truncator : public Iterator {
protected:
   typedef Iterator super;
   typedef unary_helper<Iterator,Predicate> helper;
   typename helper::operation pred;

   template <typename,typename> friend class input_truncator;
public:
   typedef forward_iterator_tag iterator_category;
   typedef input_truncator<typename iterator_traits<Iterator>::iterator, Predicate> iterator;
   typedef input_truncator<typename iterator_traits<Iterator>::const_iterator, Predicate> const_iterator;

   input_truncator() {}

   template <typename Predicate2>
   input_truncator(const input_truncator<typename iterator_traits<Iterator>::iterator, Predicate2>& it)
      : super(it),
        pred(helper::create(it.pred)) {}

   input_truncator(const Iterator& cur_arg, const Predicate& pred_arg=Predicate())
      : super(cur_arg),
        pred(helper::create(pred_arg)) {}

   input_truncator& operator++ ()
   {
      super::operator++();
      return *this;
   }
   const input_truncator operator++ (int)
   {
      input_truncator copy=*this; operator++(); return copy;
   }

   bool at_end() const
   {
      return super::at_end() || !pred(*helper::get(*this));
   }

   bool operator== (const iterator& it) const
   {
      return at_end() ? it.at_end() : static_cast<const Iterator&>(*this)==it;
   }
   bool operator== (typename assign_const<const_iterator, !identical<iterator,const_iterator>::value>::type& it) const
   {
      return at_end() ? it.at_end() : static_cast<const Iterator&>(*this)==it;
   }
   bool operator!= (const iterator& it) const
   {
      return !operator==(it);
   }
   bool operator!= (typename assign_const<const_iterator, !identical<iterator,const_iterator>::value>::type& it) const
   {
      return !operator==(it);
   }
private:
   // make them undefined for the case of bidirectional or random_access Iterator
   void operator--();
   void operator--(int);
   void operator+=(int);
   void operator-=(int);
   void operator+(int);
   void operator-(int);
   void operator[](int);
};

template <typename Iterator, typename Predicate, typename Feature>
struct check_iterator_feature<input_truncator<Iterator, Predicate>, Feature>
   : check_iterator_feature<Iterator, Feature> {};

struct input_truncator_constructor {
   template <typename Iterator, typename Predicate, typename ExpectedFeatures>
   struct defs : public unary_predicate_selector_constructor::defs<Iterator, Predicate, ExpectedFeatures> {
      typedef input_truncator<Iterator,Predicate> iterator;
   };
};

template <typename ContainerRef, typename Predicate>
class SelectedSubset
   : public modified_container_base<ContainerRef, Predicate>,
     public modified_container_impl< SelectedSubset<ContainerRef,Predicate>,
                                     list( Container< ContainerRef >,
                                           Operation< Predicate >,
                                           IteratorConstructor< unary_predicate_selector_constructor > ) >,
     public generic_of_subset< SelectedSubset<ContainerRef,Predicate>,
                               typename deref<ContainerRef>::type> {
   typedef modified_container_base<ContainerRef, Predicate> _base;
   typedef modified_container_impl<SelectedSubset> _super;
public:
   typedef typename least_derived< cons<bidirectional_iterator_tag, typename _super::container_category> >::type
      container_category;

   SelectedSubset(typename _base::arg_type src_arg, const Predicate& pred_arg=Predicate())
      : _base(src_arg,pred_arg) {}

   using _base::get_operation;
};

template <typename ContainerRef, typename Predicate>
class TruncatedContainer
   : public modified_container_base<ContainerRef, Predicate>,
     public modified_container_impl< TruncatedContainer<ContainerRef,Predicate>,
                                     list( Container< ContainerRef >,
                                           Operation< Predicate >,
                                           IteratorConstructor< input_truncator_constructor > ) >,
     public generic_of_subset< TruncatedContainer<ContainerRef,Predicate>,
                               typename deref<ContainerRef>::type> {
   typedef modified_container_base<ContainerRef, Predicate> _base;
   typedef modified_container_impl<TruncatedContainer> _super;
public:
   typedef forward_iterator_tag container_category;

   TruncatedContainer(typename _base::arg_type src_arg, const Predicate& pred_arg=Predicate())
      : _base(src_arg,pred_arg) {}

   using _base::get_operation;

   typename _super::reference front()
   {
      return this->get_container().front();
   }
   typename _super::const_reference front() const
   {
      return this->get_container().front();
   }
};

template <typename ContainerRef, typename Predicate>
struct spec_object_traits< SelectedSubset<ContainerRef, Predicate> >
   : spec_object_traits<is_container> {
   static const bool is_temporary=true,
                     is_always_const=effectively_const<ContainerRef>::value;
};

template <typename ContainerRef, typename Predicate>
struct spec_object_traits< TruncatedContainer<ContainerRef, Predicate> >
   : spec_object_traits<is_container> {
   static const bool is_temporary=true,
                     is_always_const=effectively_const<ContainerRef>::value;
};

class index_truncator {
protected:
   int last;
public:
   index_truncator(int l=-1) : last(l) {}

   typedef void argument_type;
   typedef bool result_type;

   template <typename Iterator>
   bool operator() (const Iterator& it) const
   {
      return it.index() <= last;
   }
};

template <typename Container, typename Predicate> inline
SelectedSubset<Container&, Predicate>
attach_selector(Container& c, const Predicate& pred)
{
   return SelectedSubset<Container&, Predicate> (c,pred);
}

template <typename Container, typename Predicate> inline
SelectedSubset<const Container&, Predicate>
attach_selector(const Container& c, const Predicate& pred)
{
   return SelectedSubset<const Container&, Predicate> (c,pred);
}

template <typename Container, typename Predicate> inline
TruncatedContainer<Container&, Predicate>
attach_truncator(Container& c, const Predicate& pred)
{
   return TruncatedContainer<Container&, Predicate> (c,pred);
}

template <typename Container, typename Predicate> inline
TruncatedContainer<const Container&, Predicate>
attach_truncator(const Container& c, const Predicate& pred)
{
   return TruncatedContainer<const Container&, Predicate> (c,pred);
}

/** Selecting output iterator

    This is a combination of an output iterator (called below `basis iterator') and a predicate.

    When a data item is assigned to the output iterator, it is first evaluated by the predicate object.
    Only items mapped to \\true\\ are passed through to the assignment method of the basis iterator.
*/
template <typename Iterator, typename Predicate>
class output_predicate_selector : public iterator_traits<Iterator>::derivable_type {
protected:
   Predicate pred;

   typedef typename iterator_traits<Iterator>::derivable_type super;
public:
   typedef output_iterator_tag iterator_category;
   typedef typename iterator_traits<Iterator>::value_type value_type;

   output_predicate_selector() {}

   output_predicate_selector(const Iterator& cur_arg, const Predicate& pred_arg=Predicate())
      : super(cur_arg), pred(pred_arg) {}

   output_predicate_selector& operator= (typename function_argument<value_type>::type arg)
   {
      if (pred(arg))
         *static_cast<super*>(this)=arg;
      return *this;
   }

   template <class Arg>
   output_predicate_selector& operator= (const Arg& arg)
   {
      if (pred(arg))
         *static_cast<super*>(this)=arg;
      return *this;
   }

   output_predicate_selector& operator* () { return *this; }
   output_predicate_selector& operator++ () { return *this; }
   output_predicate_selector& operator++ (int) { return *this; }
};

template <typename Iterator, typename Predicate> inline
output_predicate_selector<Iterator,Predicate>
make_output_predicate_selector(Iterator it, Predicate pred)
{
   return output_predicate_selector<Iterator,Predicate>(it,pred);
}

struct output_predicate_selector_constructor {
   template <typename Iterator, typename Predicate, typename ExpectedFeatures>
   struct defs {
      typedef ExpectedFeatures needed_features;
      typedef output_predicate_selector<Iterator,Predicate> iterator;
   };
};

template <typename IteratorPair, typename Predicate>
class binary_predicate_selector : public IteratorPair {
protected:
   typedef IteratorPair super;
   typedef binary_helper<IteratorPair,Predicate> helper;
   typename helper::operation pred;

   void valid_position()
   {
      while (!this->at_end() && !pred(*helper::get1(*this), *helper::get2(this->second)))
         super::operator++();
   }

   template <typename,typename> friend class binary_predicate_selector;
public:
   typedef typename least_derived< cons<typename IteratorPair::iterator_category,
                                        bidirectional_iterator_tag> >::type
      iterator_category;
   typedef binary_predicate_selector<typename iterator_traits<IteratorPair>::iterator, Predicate> iterator;
   typedef binary_predicate_selector<typename iterator_traits<IteratorPair>::const_iterator, Predicate> const_iterator;

   binary_predicate_selector() {}

   template <typename Predicate2>
   binary_predicate_selector(const binary_predicate_selector<typename iterator_traits<IteratorPair>::iterator, Predicate2>& it)
      : super(it),
        pred(helper::create(it.pred)) {}

   binary_predicate_selector(const IteratorPair& cur_arg, const Predicate& pred_arg=Predicate(), bool at_valid_position=false)
      : super(cur_arg),
        pred(helper::create(pred_arg))
   {
      if (!at_valid_position) valid_position();
   }

   binary_predicate_selector(const typename IteratorPair::first_type& first_arg,
                             const typename IteratorPair::second_type& second_arg,
                             const Predicate& pred_arg=Predicate(), bool at_valid_position=false)
      : super(first_arg,second_arg),
        pred(helper::create(pred_arg))
   {
      if (!at_valid_position) valid_position();
   }

   binary_predicate_selector(typename alt_constructor<typename IteratorPair::first_type>::arg_type& first_arg,
                             typename alt_constructor<typename IteratorPair::second_type>::arg_type& second_arg,
                             const Predicate& pred_arg=Predicate(), bool at_valid_position=false)
      : super(first_arg,second_arg),
        pred(helper::create(pred_arg))
   {
      if (!at_valid_position) valid_position();
   }

   binary_predicate_selector& operator++ ()
   {
      super::operator++();
      valid_position();
      return *this;
   }
   const binary_predicate_selector operator++ (int)
   {
      binary_predicate_selector copy=*this; operator++(); return copy;
   }

   binary_predicate_selector& operator-- ()
   {
      typedef typename enable_if<super, iterator_traits<super>::is_bidirectional>::type error_if_unimplemented;
      do
         super::operator--();
      while (!pred(*helper::get1(*this), *helper::get2(this->second)));
      return *this;
   }
   const binary_predicate_selector operator-- (int)
   {
      binary_predicate_selector copy=*this; operator--(); return copy;
   }

   void rewind()
   {
      typedef typename enable_if<super, check_iterator_feature<super, rewindable>::value>::type error_if_unimplemented;
      super::rewind();
      valid_position();
   }
private:
   void operator+=(int);
   void operator-=(int);
   void operator+(int);
   void operator-(int);
   void operator[](int);
};

template <typename IteratorPair, typename Predicate, typename Feature>
struct check_iterator_feature<binary_predicate_selector<IteratorPair, Predicate>, Feature>
   : check_iterator_feature<IteratorPair, Feature> {};

template <typename Iterator1, typename Iterator2, typename Predicate> inline
binary_predicate_selector<iterator_pair<Iterator1, Iterator2>, Predicate>
make_binary_predicate_selector(Iterator1 first, Iterator2 second, const Predicate& pred)
{
   return binary_predicate_selector<iterator_pair<Iterator1, Iterator2>, Predicate>(first,second,pred);
}

struct binary_predicate_selector_constructor {
   template <typename IteratorPair, typename Predicate, typename ExpectedFeatures>
   struct defs {
      typedef binary_helper<IteratorPair,Predicate> helper;
      typedef typename mix_features<ExpectedFeatures, end_sensitive>::type
         needed_pair_features;
      typedef void needed_features1;
      typedef void needed_features2;
      typedef binary_predicate_selector<IteratorPair, Predicate> iterator;
   };
};

template <typename ContainerRef1, typename ContainerRef2, typename Predicate>
class SelectedContainerPairSubset
   : public modified_container_pair_base<ContainerRef1, ContainerRef2, Predicate>,
     public modified_container_pair_impl< SelectedContainerPairSubset<ContainerRef1,ContainerRef2,Predicate>,
                                          list( Container1< ContainerRef1 >,
                                                Container2< ContainerRef2 >,
                                                IteratorConstructor< binary_predicate_selector_constructor >,
                                                Operation< Predicate > ) >,
     public generic_of_subset< SelectedContainerPairSubset<ContainerRef1,ContainerRef2,Predicate>,
                               typename deref<ContainerRef1>::type > {
   typedef modified_container_pair_base<ContainerRef1, ContainerRef2, Predicate> _base;
   typedef modified_container_pair_impl<SelectedContainerPairSubset> _super;
public:
   typedef typename least_derived< cons<bidirectional_iterator_tag, typename _super::container_category> >::type
      container_category;

   SelectedContainerPairSubset(typename _base::first_arg_type src1_arg, typename _base::second_arg_type src2_arg,
                               const Predicate& pred_arg=Predicate())
      : _base(src1_arg,src2_arg,pred_arg) {}

   using _base::get_operation;
};

template <typename ContainerRef1, typename ContainerRef2, typename Predicate>
struct spec_object_traits< SelectedContainerPairSubset<ContainerRef1, ContainerRef2, Predicate> >
   : spec_object_traits<is_container> {
   static const bool is_temporary=true,
                     is_always_const=effectively_const<ContainerRef1>::value;
};

template <typename Container1, typename Container2, typename Predicate> inline
SelectedContainerPairSubset<Container1&, Container2&, Predicate>
attach_selector(Container1& c1, Container2& c2, const Predicate& pred)
{
   return SelectedContainerPairSubset<Container1&, Container2&, Predicate> (c1,c2,pred);
}

template <typename Container1, typename Container2, typename Predicate> inline
SelectedContainerPairSubset<const Container1&, Container2&, Predicate>
attach_selector(const Container1& c1, Container2& c2, Predicate pred)
{
   return SelectedContainerPairSubset<const Container1&, Container2&, Predicate> (c1,c2,pred);
}

template <typename Container1, typename Container2, typename Predicate> inline
SelectedContainerPairSubset<Container1&, const Container2&, Predicate>
attach_selector(Container1& c1, const Container2& c2, Predicate pred)
{
   return SelectedContainerPairSubset<Container1&, const Container2&, Predicate> (c1,c2,pred);
}

template <typename Container1, typename Container2, typename Predicate> inline
SelectedContainerPairSubset<const Container1&, const Container2&, Predicate>
attach_selector(const Container1& c1, const Container2& c2, Predicate pred)
{
   return SelectedContainerPairSubset<const Container1&, const Container2&, Predicate> (c1,c2,pred);
}

template <typename Container1, typename Container2> inline
SelectedContainerPairSubset<Container1&, const Container2&, operations::apply2< BuildUnaryIt<operations::dereference> > >
attach_mask(Container1& data, const Container2& boolean)
{
   return SelectedContainerPairSubset<Container1&, const Container2&, operations::apply2< BuildUnaryIt<operations::dereference> > >
          (data,boolean);
}

template <typename Container1, typename Container2> inline
SelectedContainerPairSubset<const Container1&, const Container2&, operations::apply2< BuildUnaryIt<operations::dereference> > >
attach_mask(const Container1& data, const Container2& boolean)
{
   return SelectedContainerPairSubset<const Container1&, const Container2&, operations::apply2< BuildUnaryIt<operations::dereference> > >
          (data,boolean);
}

} // end namespace pm

namespace polymake {

using pm::SelectedSubset;
using pm::TruncatedContainer;
using pm::SelectedContainerPairSubset;
using pm::make_unary_predicate_selector;
using pm::make_binary_predicate_selector;
using pm::make_output_predicate_selector;
using pm::attach_selector;
using pm::attach_mask;

} // end namespace polymake

#endif // POLYMAKE_SELECTED_SUBSET_H

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