// ==========================================================================
//                 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>
// ==========================================================================
//
// The TraceSegment structure is used to store the traceback in a common
// structure such that we can easiely adapt them afterwards in the
// user-defined structure, such as Align or AlignmentGraph objects.
// ==========================================================================

#ifndef SEQAN_INCLUDE_SEQAN_ALIGN_DP_TRACE_SEGMENT_H_
#define SEQAN_INCLUDE_SEQAN_ALIGN_DP_TRACE_SEGMENT_H_

namespace seqan {

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

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

// ----------------------------------------------------------------------------
// Class TraceSegment
// ----------------------------------------------------------------------------

// TraceSegments are used as a common interface to all structures that can represent an alignment.
//
// See alignment_dp_traceback_adaptor.h to find methods for adaption.
template <typename TPosition, typename TSize>
class TraceSegment_
{
public:
    typedef typename TraceBitMap_<>::Type TTraceValue;

    TPosition _horizontalBeginPos;      // the begin position in horizontal dimension
    TPosition _verticalBeginPos;        // the begin position in vertical dimension
    TSize _length;                      // the length of the segment
    TTraceValue _traceValue;            // the trace direction

    TraceSegment_() :
        _horizontalBeginPos(0), _verticalBeginPos(0), _length(0), _traceValue(TraceBitMap_<TTraceValue>::NONE){}

    TraceSegment_(TraceSegment_ const & other) :
        _horizontalBeginPos(other._horizontalBeginPos),
        _verticalBeginPos(other._verticalBeginPos),
        _length(other._length),
        _traceValue(other._traceValue) {}

    TraceSegment_(TPosition const & horizontalBeginPos, TPosition const & verticalBeginPos, TSize const & length,
                  TTraceValue const & traceValue) :
        _horizontalBeginPos(horizontalBeginPos),
        _verticalBeginPos(verticalBeginPos),
        _length(length),
        _traceValue(traceValue) {}

    TraceSegment_ &
    operator=(TraceSegment_ const & other)
    {
        if (this != &other)
        {
            _horizontalBeginPos = other._horizontalBeginPos;
            _verticalBeginPos = other._verticalBeginPos;
            _length = other._length;
            _traceValue = other._traceValue;
        }
        return *this;
    }

};


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

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

template <typename TPosition, typename TSize>
struct Position<TraceSegment_<TPosition, TSize> >
{
    typedef TPosition Type;
};

template <typename TPosition, typename TSize>
struct Position<TraceSegment_<TPosition, TSize> const>:
    Position<TraceSegment_<TPosition, TSize> >{};

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

template <typename TPosition, typename TSize>
struct Size<TraceSegment_<TPosition, TSize> >
{
    typedef TSize Type;
};

template <typename TPosition, typename TSize>
struct Size<TraceSegment_<TPosition, TSize> const>:
    Size<TraceSegment_<TPosition, TSize> >{};

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

// ----------------------------------------------------------------------------
// Function _getBeginHorizontal()
// ----------------------------------------------------------------------------

// The begin position of the segment in horizontal dimension.
template <typename TPosition, typename TSize>
inline TPosition
_getBeginHorizontal(TraceSegment_<TPosition, TSize> const & traceSegment)
{
    return traceSegment._horizontalBeginPos;
}

// ----------------------------------------------------------------------------
// Function _getBeginVertical()
// ----------------------------------------------------------------------------

// The begin position of the segment in vertical dimension.
template <typename TPosition, typename TSize>
inline TPosition
_getBeginVertical(TraceSegment_<TPosition, TSize> const & traceSegment)
{
    return traceSegment._verticalBeginPos;
}

// ----------------------------------------------------------------------------
// Function _getEndHorizontal()
// ----------------------------------------------------------------------------

// The end position of the segment in horizontal dimension.
template <typename TPosition, typename TSize>
inline TPosition
_getEndHorizontal(TraceSegment_<TPosition, TSize> const & traceSegment)
{
    typedef typename std::remove_const<decltype(traceSegment._traceValue)>::type TTraceValue;
    if (traceSegment._traceValue & (TraceBitMap_<TTraceValue>::HORIZONTAL | TraceBitMap_<TTraceValue>::DIAGONAL))
    {
        return traceSegment._horizontalBeginPos + traceSegment._length;
    }
    return traceSegment._horizontalBeginPos;
}

// ----------------------------------------------------------------------------
// Function _getEndVertical()
// ----------------------------------------------------------------------------

// The end position of the segment in vertical dimension.
template <typename TPosition, typename TSize>
inline TPosition
_getEndVertical(TraceSegment_<TPosition, TSize> const & traceSegment)
{
    typedef typename std::remove_const<decltype(traceSegment._traceValue)>::type TTraceValue;
    if (traceSegment._traceValue & (TraceBitMap_<TTraceValue>::VERTICAL | TraceBitMap_<TTraceValue>::DIAGONAL))
    {
        return traceSegment._verticalBeginPos + traceSegment._length;
    }
    return traceSegment._verticalBeginPos;
}

// ----------------------------------------------------------------------------
// Function _getTraceValue()
// ----------------------------------------------------------------------------

// The end position of the segment in vertical dimension.
template <typename TPosition, typename TSize>
inline typename TraceSegment_<TPosition, TSize>::TTraceValue
_getTraceValue(TraceSegment_<TPosition, TSize> const & traceSegment)
{
    return traceSegment._traceValue;
}

// ----------------------------------------------------------------------------
// Function length()
// ----------------------------------------------------------------------------

// The length of the segment.
template <typename TPosition, typename TSize>
inline TSize
length(TraceSegment_<TPosition, TSize> const & traceSegment)
{
    return traceSegment._length;
}

// ----------------------------------------------------------------------------
// Function _setLength()
// ----------------------------------------------------------------------------

// The length of the segment.
template <typename TPosition, typename TSize>
inline void
_setLength(TraceSegment_<TPosition, TSize> & traceSegment, TSize newLength)
{
    traceSegment._length = newLength;
}

// ----------------------------------------------------------------------------
// Function _translateTraceValue()
// ----------------------------------------------------------------------------

// Translates the trace value into a human-readable format.
//
// Note, used for debugging reasons only.
template <typename TTraceValue>
String<char> _translateTraceValue(TTraceValue const & traceValue)
{
    String<char> transcript;

    if ((traceValue & TraceBitMap_<TTraceValue>::DIAGONAL) == TraceBitMap_<TTraceValue>::DIAGONAL)
        append(transcript, 'D');
    if ((traceValue & TraceBitMap_<TTraceValue>::VERTICAL) == TraceBitMap_<TTraceValue>::VERTICAL)
        append(transcript, 'V');
    if ((traceValue & TraceBitMap_<TTraceValue>::HORIZONTAL) == TraceBitMap_<TTraceValue>::HORIZONTAL)
        append(transcript, 'H');
    if ((traceValue & TraceBitMap_<TTraceValue>::VERTICAL_OPEN) == TraceBitMap_<TTraceValue>::VERTICAL_OPEN)
        append(transcript, 'v');
    if ((traceValue & TraceBitMap_<TTraceValue>::HORIZONTAL_OPEN) == TraceBitMap_<TTraceValue>::HORIZONTAL_OPEN)
        append(transcript, 'h');
    if ((traceValue & TraceBitMap_<TTraceValue>::MAX_FROM_VERTICAL_MATRIX) == TraceBitMap_<TTraceValue>::MAX_FROM_VERTICAL_MATRIX)
        append(transcript, '|');
    if ((traceValue & TraceBitMap_<TTraceValue>::MAX_FROM_HORIZONTAL_MATRIX) == TraceBitMap_<TTraceValue>::MAX_FROM_HORIZONTAL_MATRIX)
        append(transcript, '-');

    if ((traceValue) == TraceBitMap_<TTraceValue>::NONE)
        append(transcript, '0');
    return transcript;
}

// ----------------------------------------------------------------------------
// Function oerpator<<()
// ----------------------------------------------------------------------------

template <typename TStream, typename TSize, typename TPosition>
TStream & operator<<(TStream & stream, TraceSegment_<TSize, TPosition> const & traceSegment)
{
    stream << _translateTraceValue(traceSegment._traceValue) << "-";
    stream << "(" << traceSegment._horizontalBeginPos << ", " << traceSegment._verticalBeginPos << ", " <<
    traceSegment._length << ")";
    return stream;
}

// ----------------------------------------------------------------------------
// Function oerpator==()
// ----------------------------------------------------------------------------

template <typename TPosition, typename TSize>
inline bool operator==(TraceSegment_<TPosition, TSize> const & left, TraceSegment_<TPosition, TSize> const & right)
{
    if (left._horizontalBeginPos != right._horizontalBeginPos)
        return false;

    if (left._verticalBeginPos != right._verticalBeginPos)
        return false;

    if (left._length != right._length)
        return false;

    if (left._traceValue != right._traceValue)
        return false;

    return true;
}

// ----------------------------------------------------------------------------
// Function oerpator!=()
// ----------------------------------------------------------------------------

template <typename TPosition, typename TSize>
inline bool operator!=(TraceSegment_<TPosition, TSize> const & left, TraceSegment_<TPosition, TSize> const & right)
{
    return !(left == right);
}

// ----------------------------------------------------------------------------
// Function recordSegment()
// ----------------------------------------------------------------------------

// Records a segment given the horizontal and vertical begin position, the length,
// and the corrsponding trace value.
//
// The first parameter is the container the segment is recorded to.
template <typename TTraceSegments, typename TPositionH, typename TPositionV, typename TSize, typename TTraceValue>
inline void _recordSegment(TTraceSegments & traceSegments,
                           TPositionH const & horizontalBeginPos,
                           TPositionV const & verticalBeginPos,
                           TSize const & segmentLength,
                           TTraceValue const & traceValue)
{
    typedef typename Value<TTraceSegments>::Type TTraceSegment;

    if (segmentLength == 0)
        return;  // we don't store empty segments

    if (traceValue & TraceBitMap_<TTraceValue>::DIAGONAL)
        appendValue(traceSegments, TTraceSegment(horizontalBeginPos, verticalBeginPos, segmentLength, TraceBitMap_<TTraceValue>::DIAGONAL));
    else if (traceValue & TraceBitMap_<TTraceValue>::VERTICAL)
        appendValue(traceSegments, TTraceSegment(horizontalBeginPos, verticalBeginPos, segmentLength, TraceBitMap_<TTraceValue>::VERTICAL));
    else if (traceValue & TraceBitMap_<TTraceValue>::HORIZONTAL)
        appendValue(traceSegments, TTraceSegment(horizontalBeginPos, verticalBeginPos, segmentLength, TraceBitMap_<TTraceValue>::HORIZONTAL));
    // everything else is not tracked.
}

}  // namespace seqan

#endif  // #ifndef SEQAN_INCLUDE_SEQAN_ALIGN_DP_TRACE_SEGMENT_H_