// ==========================================================================
//                 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>
// ==========================================================================

#ifndef INCLUDE_SEQAN_JOURNALED_STRING_TREE_JOURNALED_STRING_TREE_TRAVERSER_H_
#define INCLUDE_SEQAN_JOURNALED_STRING_TREE_JOURNALED_STRING_TREE_TRAVERSER_H_

namespace seqan
{

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

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

template <typename TJst, typename TSpec>
class TraverserImpl<TJst, JstTraversalSpec<TSpec> >
{
public:
    typedef typename Member<TraverserImpl, TraverserStackMember>::Type  TStack;
    typedef typename Value<TStack>::Type                                TNode;
    typedef typename Size<TraverserImpl>::Type                          TSize;
    typedef std::shared_ptr<TStack>                                     TStackPtr;
    typedef JstBuffer_<TJst>                                            TBuffer;  // Provides a sequence context.
    typedef std::shared_ptr<TBuffer>                                    TBufferPtr;
    typedef typename Member<TJst, JstDeltaMapMember>::Type              TDeltaMap;
    typedef typename DeltaCoverage<TDeltaMap>::Type                     TCoverage;

    // __ Member Variables ____________________________________________________

    TJst *      _contPtr;  // TODO(rmaerker): Underlying container should be const.
    TSize       _contextSize    = 1;
    TSize       _branchLength   = 1;
    TStackPtr   _stackPtr;
    TBufferPtr  _bufferPtr;
    TCoverage   _baseCov;
    bool        _needInitialization = true;

    // __ Constructors ________________________________________________________

    // Default c'tor.
    TraverserImpl() :
        _contPtr(nullptr),
        _stackPtr(impl::createStack<TStack>()),
        _bufferPtr(impl::createBuffer<TBuffer>())
    {}

    // C'tor with just the jst.
    TraverserImpl(TJst & jst) :
        _contPtr(&jst),
        _stackPtr(impl::createStack<TStack>()),
        _bufferPtr(impl::createBuffer<TBuffer>())
    {}

    // C'tor with the jst and the context or branch size.
    template <typename TSize>
    TraverserImpl(TJst & jst, TSize const contextSize) :
        _contPtr(&jst),
        _contextSize(contextSize),
        _branchLength(contextSize),
        _stackPtr(impl::createStack<TStack>()),
        _bufferPtr(impl::createBuffer<TBuffer>())
    {}

    // C'tor with the jst and the context or branch size.
    template <typename TSize>
    TraverserImpl(TJst & jst, TSize const contextSize, TSize const branchLength) :
        _contPtr(&jst),
        _contextSize(contextSize),
        _branchLength(branchLength),
        _stackPtr(impl::createStack<TStack>()),
        _bufferPtr(impl::createBuffer<TBuffer>())
    {}

    // Copy c'tor.
    template <typename TOtherJst>
    TraverserImpl(TraverserImpl<TOtherJst, JstTraversalSpec<TSpec> > const & other,
                  SEQAN_CTOR_ENABLE_IF(IsConstructible<TJst, TOtherJst>)) :
        _contPtr(other._contPtr),
        _contextSize(other._contextSize),
        _branchLength(other._branchLength),
        _stackPtr(other._stackPtr),
        _bufferPtr(other._bufferPtr),
        _baseCov(other._baseCov),
        _needInitialization(other._needInitialization)

    {
        ignoreUnusedVariableWarning(dummy);
    }

    // Move c'tor.
    template <typename TOtherJst>
    TraverserImpl(TraverserImpl<TOtherJst, JstTraversalSpec<TSpec> > && other,
                  SEQAN_CTOR_ENABLE_IF(IsConstructible<TJst, TOtherJst>)) :
        _contPtr(std::move(other._contPtr)),
        _contextSize(std::move(other._contextSize)),
        _branchLength(std::move(other._branchLength)),
        _stackPtr(std::move(other._stackPtr)),
        _bufferPtr(std::move(other._bufferPtr)),
        _baseCov(std::move(other._baseCov)),
        _needInitialization(std::move(other._needInitialization))
    {
        ignoreUnusedVariableWarning(dummy);
    }

    // __ Member Functions ____________________________________________________

    template <typename TOtherJst>
    inline SEQAN_FUNC_ENABLE_IF(IsConstructible<TJst, TOtherJst>, TraverserImpl &)
    operator=(TraverserImpl<TOtherJst, JstTraversalSpec<TSpec> > other)
    {
        impl::swap(*this, other);
        return *this;
    }

    // Destructor
    ~TraverserImpl() = default;
};

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

// ----------------------------------------------------------------------------
// Metafunction Container
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
struct Container<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >
{
    typedef TJst Type;
};

template <typename TJst, typename TSpec>
struct Container<TraverserImpl<TJst, JstTraversalSpec<TSpec> > const>
    : Container<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >
{};

// ----------------------------------------------------------------------------
// Metafunction Position
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
struct Position<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >
{
    using TPos_ = typename Position<TJst>::Type;
    using TSize_ = typename Size<TJst>::Type;

    using Type = String<Pair<TSize_, TPos_> >;
};

// ----------------------------------------------------------------------------
// Metafunction Size
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
struct Size<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >
{
    typedef typename Size<TJst>::Type    Type;
};

// ----------------------------------------------------------------------------
// Metafunction TraverserImpl
// ----------------------------------------------------------------------------

template <typename TSeq, typename TConfig, typename TSpec>
struct Traverser<JournaledStringTree<TSeq, TConfig, TSpec> >
{
    typedef JournaledStringTree<TSeq, TConfig, TSpec> TJst_;
    typedef TraverserImpl<TJst_, JstTraversalSpec<void> > Type;
};

template <typename TSeq, typename TConfig, typename TSpec>
struct Traverser<JournaledStringTree<TSeq, TConfig, TSpec> const>
{
    typedef JournaledStringTree<TSeq, TConfig, TSpec> const TJst_;
    typedef TraverserImpl<TJst_, JstTraversalSpec<void> > Type;
};

// ----------------------------------------------------------------------------
// Metafunction ContextIterator
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
struct ContextIterator<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >
{
    typedef TraverserImpl<TJst, JstTraversalSpec<TSpec> >               TThis_;
    typedef typename Container<TThis_>::Type                            TContainer_;
    typedef typename Member<TContainer_, JstSourceMember>::Type         TSource_;

    typedef typename Iterator<TSource_, Standard>::Type                 Type;
};

template <typename TJst, typename TSpec>
struct ContextIterator<TraverserImpl<TJst, JstTraversalSpec<TSpec> > const>
{
    typedef TraverserImpl<TJst, JstTraversalSpec<TSpec> > TTraverser_;

    typedef typename ContextIterator<TTraverser_>::Type  const      Type;
};

// ----------------------------------------------------------------------------
// Metafunction TraverserNode
// ----------------------------------------------------------------------------

template <typename TTraverser>
struct TraverserNode;

template <typename TJst, typename TSpec>
struct TraverserNode<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >
{
    using Type = JstTraversalNode<TJst>;
};

template <typename TJst, typename TSpec>
struct TraverserNode<TraverserImpl<TJst, JstTraversalSpec<TSpec> > const>
{
    using Type = JstTraversalNode<TJst> const;
};

// ----------------------------------------------------------------------------
// Metafunction Member<TraverserStackMember>
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
struct Member<TraverserImpl<TJst, JstTraversalSpec<TSpec> >, TraverserStackMember>
{
    using TNode = typename TraverserNode<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >::Type;
    using Type  = String<TNode, Block<> >;
};

// ============================================================================
// Public Functions
// ============================================================================

// ----------------------------------------------------------------------------
// Function contextIterator();
// ----------------------------------------------------------------------------

// Returns current sequence iterator.
template <typename TJst, typename TSpec>
inline typename ContextIterator<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >::Type
contextIterator(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me)
{
    return impl::getContextIterator(me);
}

// ----------------------------------------------------------------------------
// Function contextBegin();
// ----------------------------------------------------------------------------

// Returns current sequence iterator.
template <typename TJst, typename TSpec>
inline typename ContextIterator<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >::Type
contextBegin(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me)
{
    return impl::getContextBegin(me);
}

// Returns current sequence iterator.
template <typename TJst, typename TSpec>
inline typename ContextIterator<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >::Type
contextEnd(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me)
{
    return impl::getContextEnd(me);
}

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

template <typename TJst, typename TSpec>
inline typename Container<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >::Type &
container(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me)
{
    return *me._contPtr;
}

template <typename TJst, typename TSpec>
inline typename Container<TraverserImpl<TJst, JstTraversalSpec<TSpec> > const>::Type &
container(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me)
{
    return *me._contPtr;
}

// ----------------------------------------------------------------------------
// Function setContainer();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
inline void
setContainer(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
             TJst & jst)
{
    me._needInitialization = true;
    me._contPtr = &jst;
}

// ----------------------------------------------------------------------------
// Function contextSize();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
inline auto
contextSize(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me) -> decltype(me._contextSize)
{
    return me._contextSize;
}

// ----------------------------------------------------------------------------
// Function setContextSize();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec, typename TSize>
inline void
setContextSize(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
               TSize contextSize)
{
    me._needInitialization = true;
    me._contextSize = contextSize;
}

// ----------------------------------------------------------------------------
// Function branchSize();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
inline auto
branchSize(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me) -> decltype(me._branchLength)
{
    return me._branchLength;
}

// ----------------------------------------------------------------------------
// Function setBranchSize();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec, typename TSize>
inline void
setBranchSize(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
              TSize newSize)
{
    me._needInitialization = true;
    me._branchLength = newSize;
}

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

template <typename TJst, typename TSpec>
inline typename Position<TraverserImpl<TJst, JstTraversalSpec<TSpec> > >::Type
position(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me)
{

    if (length(impl::stack(me)) == 1)
        return impl::positionReference(me);
    else
        return impl::positionBranch(me);
}

// ----------------------------------------------------------------------------
// Function init();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec,
          typename TObserver,
          typename TProxySelector>
inline void
init(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
     TObserver & observer,
     Tag<TProxySelector> const & /*tag*/)
{
    impl::init(me, observer, Tag<TProxySelector>());
    me._needInitialization = false;
}

template <typename TJst, typename TSpec,
          typename TObserver>
inline void
init(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
     TObserver & observer)
{
    init(me, observer, SelectValidProxy());
}

// ----------------------------------------------------------------------------
// Function goNext();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec,
          typename TSize,
          typename TObserver,
          typename TProxySelector>
inline void
advance(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
        TSize stepSize,
        TObserver & observer,
        Tag<TProxySelector> const & /*tag*/)
{
    SEQAN_ASSERT(me._stackPtr != nullptr);

    if (SEQAN_UNLIKELY(me._needInitialization))
        init(me, observer);

    auto nodePtr = &back(*me._stackPtr);

#if defined (JST_FIND_DEBUG)
    if (nodePtr->isBase && getDeltaPosition(*nodePtr->nextDelta) == 40)
        std::cout << "     STOP! " << std::endl;

    std::cout << *nodePtr << std::endl;
#endif // defined (JST_FIND_DEBUG)
#if defined (DEBUG_JST_TRAVERSAL)
    std::cout << "\n\nGO NEXT" << std::endl;
    std::cout << "Node: (" << length(*me._stackPtr) << ") " << *nodePtr << std::endl;
#endif  // DEBUG_JST_TRAVERSAL
    stepSize = impl::moveWindow(me, nodePtr, stepSize, observer, Tag<TProxySelector>());

    if ((stepSize > 0 && nodePtr->remainingSize == 0))
    {
        SEQAN_ASSERT_NOT(impl::activeNode(me).isBase);
        impl::popNode(me, observer);
        SEQAN_ASSERT(length(impl::stack(me)) >= 1u);
    }

    // Check if the condition changed.
    nodePtr = &impl::activeNode(me);
    if (SEQAN_UNLIKELY(atEnd(nodePtr->curDelta))) // Current branch is at final end.
    {
        if (nodePtr->isBase)
            return;

        while (atEnd(nodePtr->curDelta) && !(nodePtr->isBase))
        {
            SEQAN_ASSERT(nodePtr->curEdgeIt == nodePtr->endEdgeIt); // edgeIt must be at end of current branch.
            impl::popNode(me, observer);  // Might not be the parent one.
            nodePtr = &impl::activeNode(me);
        }
    }
}

template <typename TJst, typename TSpec,
          typename TSize,
          typename TProxySelector>
inline void
advance(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
        TSize const stepSize,
        Tag<TProxySelector> const & /*tag*/)
{
    auto observer = makeObserverList();
    advance(me, stepSize, observer);
}

template <typename TJst, typename TSpec,
          typename TSize,
          typename TObserver>
inline void
advance(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
        TSize const stepSize,
        TObserver & observer)
{
    advance(me, stepSize, observer, SelectValidProxy());
}

template <typename TJst, typename TSpec,
          typename TSize>
inline void
advance(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me,
        TSize const stepSize)
{
    auto observer = makeObserverList();
    advance(me, stepSize, observer, SelectValidProxy());
}

// ----------------------------------------------------------------------------
// Function atEnd();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
inline bool
atEnd(TraverserImpl<TJst, JstTraversalSpec<TSpec> > & me)
{
    SEQAN_ASSERT(me._stackPtr != nullptr);
    return length(*me._stackPtr) == 1 && back(*me._stackPtr).curEdgeIt == sourceEnd(impl::buffer(me));
}

// ----------------------------------------------------------------------------
// Function isBase();
// ----------------------------------------------------------------------------

template <typename TJst, typename TSpec>
inline bool
isBase(TraverserImpl<TJst, JstTraversalSpec<TSpec> > const & me)
{
    SEQAN_ASSERT(me._stackPtr != nullptr);
    return length(*me._stackPtr) == 1;
}

}  // namespace seqan

#endif  // #ifndef INCLUDE_SEQAN_JOURNALED_STRING_TREE_JOURNALED_STRING_TREE_TRAVERSER_H_