// ==========================================================================
//                 SeqAn - The Library for Sequence Analysis
// ==========================================================================
// Copyright (c) 2006-2018, Knut Reinert, FU Berlin
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of Knut Reinert or the FU Berlin nor the names of
//       its contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
//
// ==========================================================================
// Author: Rene Rahn <rene.rahn@fu-berlin.de>
// ==========================================================================
// Implements the iterator over the delta map.
// ==========================================================================

#ifndef EXTRAS_INCLUDE_SEQAN_JOURNALED_STRING_TREE_DELTA_MAP_ITERATOR_H_
#define EXTRAS_INCLUDE_SEQAN_JOURNALED_STRING_TREE_DELTA_MAP_ITERATOR_H_

namespace seqan
{

// ============================================================================
// Forwards
// ============================================================================

template <typename TType, typename TOtherType> struct IsConstructible;

// ============================================================================
// Tags, Classes, Enums
// ============================================================================

/*!
 * @class DeltaMapIterator
 * @implements BidirectionalIteratorConcept
 * @extends Iter
 *
 * @headerfile <seqan/journaled_string_tree.h>
 *
 * @brief Bidirectional iterator over a @link DeltaMap @endlink.
 *
 * @signature template <typename TDeltaMap>
 *            class Iter<TDeltaMap, DeltaMapIteratorSpec>;
 *
 * @tparam TDeltaMap The delta map to iterate.
 */

template <typename TDeltaMap>
class Iter<TDeltaMap, DeltaMapIteratorSpec>
{
public:
    typedef typename RemoveConst<TDeltaMap>::Type TNonConstDeltaMap_;

    typedef typename Member<TDeltaMap, DeltaMapEntriesMember>::Type TMapEntries_;
    typedef typename Iterator<TMapEntries_, Standard>::Type TMapIterator;

    TDeltaMap*   _mapPtr;
    TMapIterator _mapIter;

    //Default C'tor.
    Iter() : _mapPtr(NULL), _mapIter()
    {}

    // Copy C'tor.
    template <typename TDeltaMapOther>
    Iter(Iter<TDeltaMapOther, DeltaMapIteratorSpec> const & other,
         SEQAN_CTOR_ENABLE_IF(IsConstructible<TDeltaMap, TDeltaMapOther>)) :
        _mapPtr(other._mapPtr),
        _mapIter(other._mapIter)
    {
        ignoreUnusedVariableWarning(dummy);
    }

    // Custom C'tor.
    template <typename TPos>
    Iter(TDeltaMap & container, TPos pos) : _mapPtr(&container), _mapIter()
    {
        _mapIter = begin(_mapPtr->_entries, Standard()) + pos;
    }

    // Assignment Operator.
    template <typename TDeltaMapOther>
    SEQAN_FUNC_DISABLE_IF(IsConstructible<TDeltaMap, TDeltaMapOther>, Iter<TDeltaMap, DeltaMapIteratorSpec> &)
    operator=(Iter<TDeltaMapOther, DeltaMapIteratorSpec> const & other)
    {
        _mapPtr = other._mapPtr;
        _mapIter = other._mapIter;
        return *this;
    }
};

// ============================================================================
// Metafunctions
// ============================================================================

// ----------------------------------------------------------------------------
// Metafunction Difference
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
struct Difference<Iter<TDeltaMap, DeltaMapIteratorSpec> >
{
    typedef Iter<TDeltaMap, DeltaMapIteratorSpec> TIter_;
    typedef typename TIter_::TMapIterator TMapIterator_;
    typedef typename Difference<TMapIterator_>::Type Type;
};

template <typename TDeltaMap>
struct Difference<Iter<TDeltaMap, DeltaMapIteratorSpec> const > :
    Difference<Iter<TDeltaMap, DeltaMapIteratorSpec> >{};

// ============================================================================
// Functions
// ============================================================================

// ----------------------------------------------------------------------------
// Function container()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline typename Container<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type &
container(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter)
{
    return *iter._mapPtr;
}

template <typename TDeltaMap>
inline typename Container<Iter<TDeltaMap, DeltaMapIteratorSpec> const>::Type &
container(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter)
{
    return *iter._mapPtr;
}

// ----------------------------------------------------------------------------
// Function deltaType()
// ----------------------------------------------------------------------------

/*!
 * @fn DeltaMapIterator#deltaType
 *
 * @headerfile <seqan/journaled_string_tree.h>
 *
 * @brief Returns the delta type associated with the current value the iterator is pointing to.
 *
 * @signature DeltaType deltaType(it);
 * @param[in]   it   The iterator pointing to the value for which the delta type should be determined.
 *
 * @return DeltaType The type of the current value. Of type @link DeltaType @endlink.
 */

template <typename TDeltaMap>
inline DeltaType
deltaType(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter)
{
    return getDeltaType(*iter);
}

// ----------------------------------------------------------------------------
// Function deltaValue()
// ----------------------------------------------------------------------------

// TODO(rrahn): Let iterator return proxy and access values using the proxy.
/*!
 * @fn DeltaMapIterator#deltaValue
 *
 * @headerfile <seqan/journaled_string_tree.h>
 *
 * @brief Returns the delta value associated with the current iterator position.
 *
 * @signature TDeltaValue deltaValue(it, tag);
 * @param[in]   it  The iterator to query the delta event for.
 * @param[in]   tag The tag used to select the requested delta type.
 *
 * @return TDeltaValue A reference to the delta at the current iterator position of type @link DeltaMap#DeltaValue @endlink.
 * @remark Note the expression <tt>isDeltaType(deltaType(it), tag)<\tt> must evaluate to <tt>true<\tt>, otherwise the result is undefined.
 */

template <typename TDeltaMap, typename TTag>
inline typename DeltaValue<typename Container<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type, TTag>::Type &
deltaValue(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter, TTag const & tag)
{
    SEQAN_ASSERT(isDeltaType(deltaType(iter), TTag()));
    return deltaValue(container(iter)._deltaStore, getDeltaRecord(*iter).i2, tag);
}

template <typename TDeltaMap, typename TTag>
inline typename DeltaValue<typename Container<Iter<TDeltaMap, DeltaMapIteratorSpec> const>::Type, TTag>::Type &
deltaValue(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter, TTag const & tag)
{
    return deltaValue(container(iter)._deltaStore, getDeltaRecord(*iter).i2, tag);
}

namespace impl
{

template <typename TIter>
struct GetInsSizeFunctor
{
    TIter & _it;
    decltype(impl::insertionSize(container(_it)._deltaStore, getStorePosition(*_it), DeltaTypeIns())) val;

    GetInsSizeFunctor()
    {}

    GetInsSizeFunctor(TIter & it) : _it(it)
    {}

    template <typename TDeltaType>
    inline void
    operator()(TDeltaType)
    {
        val = impl::insertionSize(container(_it)._deltaStore, getStorePosition(*_it), TDeltaType());
    }
};

template <typename TIter>
struct GetDelSizeFunctor
{
    TIter & _it;
    decltype(impl::deletionSize(container(_it)._deltaStore, getStorePosition(*_it), DeltaTypeDel())) val;

    GetDelSizeFunctor()
    {}

    GetDelSizeFunctor(TIter & it) : _it(it)
    {}

    template <typename TDeltaType>
    inline void
    operator()(TDeltaType)
    {
        val = impl::deletionSize(container(_it)._deltaStore, getStorePosition(*_it), TDeltaType());
    }
};

template <typename TIter>
struct GetNetSizeFunctor
{
    TIter & _it;
    decltype(impl::netSize(container(_it)._deltaStore, getStorePosition(*_it), DeltaTypeSV())) val;

    GetNetSizeFunctor()
    {}

    GetNetSizeFunctor(TIter & it) : _it(it)
    {}

    template <typename TDeltaType>
    inline void
    operator()(TDeltaType)
    {
        val = impl::netSize(container(_it)._deltaStore, getStorePosition(*_it), TDeltaType());
    }
};

}  // namespace impl

// ----------------------------------------------------------------------------
// Function insSize();
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline auto
insSize(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter) -> decltype(impl::GetInsSizeFunctor<Iter<TDeltaMap, DeltaMapIteratorSpec> >().val)
{
    impl::GetInsSizeFunctor<Iter<TDeltaMap, DeltaMapIteratorSpec> const> f(iter);
    DeltaTypeSelector deltaTypes;
    applyOnDelta(f, getDeltaType(*iter), deltaTypes);
    return f.val;
}

// ----------------------------------------------------------------------------
// Function delSize();
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline auto
delSize(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter) -> decltype(impl::GetDelSizeFunctor<Iter<TDeltaMap, DeltaMapIteratorSpec> >().val)
{
    impl::GetDelSizeFunctor<Iter<TDeltaMap, DeltaMapIteratorSpec> const> f(iter);
    DeltaTypeSelector deltaTypes;
    applyOnDelta(f, getDeltaType(*iter), deltaTypes);
    return f.val;
}

// ----------------------------------------------------------------------------
// Function netSize();
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline auto
netSize(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter) -> decltype(impl::GetNetSizeFunctor<Iter<TDeltaMap, DeltaMapIteratorSpec> >().val)
{
    impl::GetNetSizeFunctor<Iter<TDeltaMap, DeltaMapIteratorSpec> const> f(iter);
    DeltaTypeSelector deltaTypes;
    applyOnDelta(f, getDeltaType(*iter), deltaTypes);
    return f.val;
}

// ----------------------------------------------------------------------------
// Function operator*()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline typename Reference<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type
operator*(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter)
{
    return *iter._mapIter;
}

template <typename TDeltaMap>
inline typename Reference<Iter<TDeltaMap, DeltaMapIteratorSpec> const>::Type
operator*(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter)
{
    return *iter._mapIter;
}

// ----------------------------------------------------------------------------
// Function operator++()                                               [prefix]
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline Iter<TDeltaMap, DeltaMapIteratorSpec> &
operator++(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter)
{
    ++iter._mapIter;
    return iter;
}

// ----------------------------------------------------------------------------
// Function operator++()                                              [postfix]
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline Iter<TDeltaMap, DeltaMapIteratorSpec>
operator++(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter,  int /*postfix*/)
{
    Iter<TDeltaMap, DeltaMapIteratorSpec> temp(iter);
    ++iter;
    return temp;
}

// ----------------------------------------------------------------------------
// Function operator+=()
// ----------------------------------------------------------------------------

template <typename TDeltaMap, typename TSize>
inline Iter<TDeltaMap, DeltaMapIteratorSpec> &
operator+=(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter,  TSize const & len)
{
    iter._mapIter += len;
    return iter;
}

// ----------------------------------------------------------------------------
// Function operator+()
// ----------------------------------------------------------------------------

template <typename TDeltaMap, typename TSize>
inline Iter<TDeltaMap, DeltaMapIteratorSpec>
operator+(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter,  TSize const & len)
{
    Iter<TDeltaMap, DeltaMapIteratorSpec> temp(iter);
    temp += len;
    return temp;
}

// ----------------------------------------------------------------------------
// Function operator--()                                              [postfix]
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline Iter<TDeltaMap, DeltaMapIteratorSpec> &
operator--(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter)
{
    --iter._mapIter;
    return iter;
}

// ----------------------------------------------------------------------------
// Function operator--()                                               [prefix]
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline Iter<TDeltaMap, DeltaMapIteratorSpec>
operator--(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter,  int /*postfix*/)
{
    Iter<TDeltaMap, DeltaMapIteratorSpec> temp(iter);
    --iter;
    return temp;
}

// ----------------------------------------------------------------------------
// Function operator-=()
// ----------------------------------------------------------------------------

template <typename TDeltaMap, typename TSize>
inline Iter<TDeltaMap, DeltaMapIteratorSpec> &
operator-=(Iter<TDeltaMap, DeltaMapIteratorSpec> & iter,  TSize const & len)
{
    iter._mapIter -= len;
    return iter;
}

// ----------------------------------------------------------------------------
// Function operator-()
// ----------------------------------------------------------------------------

template <typename TDeltaMap, typename TSize>
inline Iter<TDeltaMap, DeltaMapIteratorSpec>
operator-(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter,  TSize const & len)
{
    Iter<TDeltaMap, DeltaMapIteratorSpec> temp(iter);
    temp -= len;
    return temp;
}

// ----------------------------------------------------------------------------
// Function operator-()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline typename Difference<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type
operator-(Iter<TDeltaMap, DeltaMapIteratorSpec> const & lhs,
          Iter<TDeltaMap, DeltaMapIteratorSpec> const & rhs)
{
    return lhs._mapIter - rhs._mapIter;
}

template <typename TDeltaMap>
inline typename Difference<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type
operator-(Iter<TDeltaMap, DeltaMapIteratorSpec> const & lhs,
          typename IterComplementConst<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type const & rhs)
{
    return lhs._mapIter - rhs._mapIter;
}

// ----------------------------------------------------------------------------
// Function operator==()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline bool
operator==(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           Iter<TDeltaMap, DeltaMapIteratorSpec> const & b)
{
    return a._mapIter == b._mapIter;
}

template <typename TDeltaMap>
inline bool
operator==(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           typename IterComplementConst<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type const & b)
{
    return a._mapIter == b._mapIter;
}

// ----------------------------------------------------------------------------
// Function operator!=()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline bool
operator!=(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           Iter<TDeltaMap, DeltaMapIteratorSpec> const & b)
{
    return !(a == b);
}

template <typename TDeltaMap>
inline bool
operator!=(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           typename IterComplementConst<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type const & b)
{
    return !(a == b);
}

// ----------------------------------------------------------------------------
// Function operator<()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline bool
operator<(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           Iter<TDeltaMap, DeltaMapIteratorSpec> const & b)
{
    return a._mapIter < b._mapIter;
}

template <typename TDeltaMap>
inline bool
operator<(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
          typename IterComplementConst<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type const & b)
{
    return a._mapIter < b._mapIter;
}

// ----------------------------------------------------------------------------
// Function operator<=()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline bool
operator<=(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           Iter<TDeltaMap, DeltaMapIteratorSpec> const & b)
{
    return a._mapIter <= b._mapIter;
}

template <typename TDeltaMap>
inline bool
operator<=(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           typename IterComplementConst<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type const & b)
{
    return a._mapIter <= b._mapIter;
}

// ----------------------------------------------------------------------------
// Function operator>()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline bool
operator>(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           Iter<TDeltaMap, DeltaMapIteratorSpec> const & b)
{
    return a._mapIter > b._mapIter;
}

template <typename TDeltaMap>
inline bool
operator>(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           typename IterComplementConst<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type const & b)
{
    return a._mapIter > b._mapIter;
}

// ----------------------------------------------------------------------------
// Function operator>=()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline bool
operator>=(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           Iter<TDeltaMap, DeltaMapIteratorSpec> const & b)
{
    return a._mapIter >= b._mapIter;
}

template <typename TDeltaMap>
inline bool
operator>=(Iter<TDeltaMap, DeltaMapIteratorSpec> const & a,
           typename IterComplementConst<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type const & b)
{
    return a._mapIter >= b._mapIter;
}

// ----------------------------------------------------------------------------
// Function position()
// ----------------------------------------------------------------------------

template <typename TDeltaMap>
inline typename Position<Iter<TDeltaMap, DeltaMapIteratorSpec> >::Type
position(Iter<TDeltaMap, DeltaMapIteratorSpec> const & iter)
{
    return iter - begin(*iter._mapPtr, Standard());
}

//template <typename TDeltaMapLhs, typename TDeltaMapRhs>
//inline void
//swap(Iter<TDeltaMapLhs, DeltaMapIteratorSpec> & lhs,
//     Iter<TDeltaMapRhs, DeltaMapIteratorSpec> & rhs)
//{
//    swap(lhs._mapPtr, rhs._mapPtr);
//    swap(lhs._mapIter, rhs._mapIter);
//}

}  // namespace seqan

#endif  // EXTRAS_INCLUDE_SEQAN_JOURNALED_STRING_TREE_DELTA_MAP_ITERATOR_H_