//
// Copyright 2013 Francisco Jerez
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//

#ifndef CLOVER_UTIL_ADAPTOR_HPP
#define CLOVER_UTIL_ADAPTOR_HPP

#include <iterator>

#include "util/tuple.hpp"
#include "util/pointer.hpp"
#include "util/functional.hpp"

namespace clover {
   namespace detail {
      ///
      /// Implementation of the iterator concept that transforms the
      /// value of the source iterators \a Is on dereference by use of
      /// a functor \a F.
      ///
      /// The exact category of the resulting iterator should be the
      /// least common denominator of the source iterator categories.
      ///
      template<typename F, typename... Is>
      class iterator_adaptor {
      public:
         typedef std::forward_iterator_tag iterator_category;
         typedef typename std::result_of<
               F(typename std::iterator_traits<Is>::reference...)
            >::type reference;
         typedef typename std::remove_reference<reference>::type value_type;
         typedef pseudo_ptr<value_type> pointer;
         typedef std::ptrdiff_t difference_type;

         iterator_adaptor() {
         }

         iterator_adaptor(F f, std::tuple<Is...> &&its) :
            f(f), its(std::move(its)) {
         }

         reference
         operator*() const {
            return tuple::apply(f, tuple::map(derefs(), its));
         }

         iterator_adaptor &
         operator++() {
            tuple::map(preincs(), its);
            return *this;
         }

         iterator_adaptor
         operator++(int) {
            auto jt = *this;
            ++*this;
            return jt;
         }

         bool
         operator==(const iterator_adaptor &jt) const {
            return its == jt.its;
         }

         bool
         operator!=(const iterator_adaptor &jt) const {
            return its != jt.its;
         }

         pointer
         operator->() const {
            return { **this };
         }

         iterator_adaptor &
         operator--() {
            tuple::map(predecs(), its);
            return *this;
         }

         iterator_adaptor
         operator--(int) {
            auto jt = *this;
            --*this;
            return jt;
         }

         iterator_adaptor &
         operator+=(difference_type n) {
            tuple::map(advances_by(n), its);
            return *this;
         }

         iterator_adaptor &
         operator-=(difference_type n) {
            tuple::map(advances_by(-n), its);
            return *this;
         }

         iterator_adaptor
         operator+(difference_type n) const {
            auto jt = *this;
            jt += n;
            return jt;
         }

         iterator_adaptor
         operator-(difference_type n) const {
            auto jt = *this;
            jt -= n;
            return jt;
         }

         difference_type
         operator-(const iterator_adaptor &jt) const {
            return std::get<0>(its) - std::get<0>(jt.its);
         }

         reference
         operator[](difference_type n) const {
            return *(*this + n);
         }

         bool
         operator<(iterator_adaptor &jt) const {
            return *this - jt < 0;
         }

         bool
         operator>(iterator_adaptor &jt) const {
            return *this - jt > 0;
         }

         bool
         operator>=(iterator_adaptor &jt) const {
            return !(*this < jt);
         }

         bool
         operator<=(iterator_adaptor &jt) const {
            return !(*this > jt);
         }

      protected:
         F f;
         std::tuple<Is...> its;
      };

      template<typename F, typename... Is>
      iterator_adaptor<F, Is...>
      operator+(typename iterator_adaptor<F, Is...>::difference_type n,
                const iterator_adaptor<F, Is...> &jt) {
         return (jt + n);
      }

      template<typename F, typename... Is>
      iterator_adaptor<F, Is...>
      operator-(typename iterator_adaptor<F, Is...>::difference_type n,
                const iterator_adaptor<F, Is...> &jt) {
         return (jt - n);
      }
   }
}

#endif
