// ==========================================================================
//                 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 a data structure to store deltas efficiently.
// ==========================================================================

#ifndef EXTRAS_INCLUDE_SEQAN_JOURNALED_STRING_TREE_DELTA_STORE_H_
#define EXTRAS_INCLUDE_SEQAN_JOURNALED_STRING_TREE_DELTA_STORE_H_

namespace seqan
{

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

template <typename TDeltaStore, typename TDeltaType>
struct DeltaValue;

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

/*!
 * @defgroup DeltaTypeTags Delta Type Tags
 * @brief Tags used for the different delta types.
 */

/*!
 * @tag DeltaTypeTags#DeltaTypeSnp
 * @brief Tag used to select SNPs.
 * @headerfile <seqan/journaled_string_tree.h>
 *
 * @signature struct DeltaTypeSnp_;
 *            typedef Tag<DeltaTypeSnp_> DeltaTypeSnp;
 */
struct DeltaTypeSnp_;
typedef Tag<DeltaTypeSnp_> DeltaTypeSnp;

/*!
 * @tag DeltaTypeTags#DeltaTypeDel
 * @brief Tag used to select deletions.
 * @headerfile <seqan/journaled_string_tree.h>
 *
 * @signature struct DeltaTypeDel_;
 *            typedef Tag<DeltaTypeDel_> DeltaTypeDel;
 */
struct DeltaTypeDel_;
typedef Tag<DeltaTypeDel_> DeltaTypeDel;

/*!
 * @tag DeltaTypeTags#DeltaTypeIns
 * @brief Tag used to select insertions.
 * @headerfile <seqan/journaled_string_tree.h>
 *
 * @signature struct DeltaTypeIns_;
 *            typedef Tag<DeltaTypeIns_> DeltaTypeIns;
 */
struct DeltaTypeIns_;
typedef Tag<DeltaTypeIns_> DeltaTypeIns;

/*!
 * @tag DeltaTypeTags#DeltaTypeSV
 * @brief Tag used to select SVs.
 * @headerfile <seqan/journaled_string_tree.h>
 *
 * @signature struct DeltaTypeSV_;
 *            typedef Tag<DeltaTypeSV_> DeltaTypeSV;
 */
struct DeltaTypeSV_;
typedef Tag<DeltaTypeSV_> DeltaTypeSV;

typedef TagList<DeltaTypeSnp,
        TagList<DeltaTypeDel,
        TagList<DeltaTypeIns,
        TagList<DeltaTypeSV> > > > DeltaTypeTagList;

// ----------------------------------------------------------------------------
// Class DeltaTypeSelector
// ----------------------------------------------------------------------------

typedef TagSelector<DeltaTypeTagList> DeltaTypeSelector;

// ----------------------------------------------------------------------------
// Enum DeltaType
// ----------------------------------------------------------------------------

/*!
 * @enum DeltaType
 * @headerfile <seqan/journaled_string_tree.h>
 * @brief Keys for specifying the delta type to be accessed.
 *
 * @val DeltaType DELTA_TYPE_SNP
 * @brief Id to denote SNP events.
 *
 * @val DeltaType DELTA_TYPE_DEL
 * @brief Id to denote deletion events.
 *
 * @val DeltaType DELTA_TYPE_INS
 * @brief Id to denote insertion events.
 *
 * @val DeltaType DElTA_TYPE_INDEL
 * @brief Id to denote replacement events.
 */

enum DeltaType
{
    DELTA_TYPE_SNP = Find<DeltaTypeSelector, DeltaTypeSnp>::VALUE,
    DELTA_TYPE_DEL = Find<DeltaTypeSelector, DeltaTypeDel>::VALUE,
    DELTA_TYPE_INS = Find<DeltaTypeSelector, DeltaTypeIns>::VALUE,
    DELTA_TYPE_SV = Find<DeltaTypeSelector, DeltaTypeSV>::VALUE
};

namespace impl
{

// ----------------------------------------------------------------------------
// Class DeltaStore
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel = uint32_t, typename TIns = String<TSnp>, typename TSV = Pair<TDel, TIns> >
class DeltaStore
{
public:
    typedef typename Member<DeltaStore, DeltaTypeSnp>::Type TSnpData;
    typedef typename Member<DeltaStore, DeltaTypeDel>::Type TDelData;
    typedef typename Member<DeltaStore, DeltaTypeIns>::Type TInsData;
    typedef typename Member<DeltaStore, DeltaTypeSV>::Type  TSVData;

    // TODO(rmaerker): Elaborate on these ideas!
    // Idea a) Use ConcatStringSet for insertions. Use as global insertion buffer for all journal sequences.
    // Idea b) Use bit encoding for DNA alphabet.
    // Idea c) Instead of insertion buffer, we append the inserted strings to the reference and only use original nodes.

    TSnpData    _snpData;
    TDelData    _delData;
    TInsData    _insData;
    TSVData     _svData;

    DeltaStore()
    {}
};

}  // namespace impl

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

// ----------------------------------------------------------------------------
// Metafunction BitsPerValue
// ----------------------------------------------------------------------------

template <>
struct BitsPerValue<DeltaType>
{
    static const unsigned VALUE = 2;
};

// ----------------------------------------------------------------------------
// Metafunction Member
// ----------------------------------------------------------------------------

// DeltaTypeSnp.
template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct Member<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeSnp>
{
    typedef String<TSnp> Type;
};

// DeltaTypeDel.
template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct Member<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeDel>
{
    typedef String<TDel> Type;
};

// DeltaTypeIns.
template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct Member<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeIns>
{
    typedef String<TIns> Type;
};

// DeltaTypeSV.
template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct Member<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeSV>
{
    typedef String<TSV> Type;
};

// Generic const version.
template <typename TSnp, typename TDel, typename TIns, typename TSV, typename TDeltaType>
struct Member<impl::DeltaStore<TSnp, TDel, TIns, TSV> const, TDeltaType>
{
    typedef impl::DeltaStore<TSnp, TDel, TIns, TSV> TStore_;
    typedef typename Member<TStore_, TDeltaType>::Type const Type;
};

// ----------------------------------------------------------------------------
// Metafunction DeltaValue                                     [DELTA_TYPE_SNP]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeSnp>
{
    typedef TSnp Type;
};

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeSnp>
{
    typedef TSnp const Type;
};

// ----------------------------------------------------------------------------
// Metafunction DeltaValue                                     [DELTA_TYPE_DEL]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeDel>
{
    typedef TDel Type;
};

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeDel>
{
    typedef TDel const Type;
};

// ----------------------------------------------------------------------------
// Metafunction DeltaValue                                     [DELTA_TYPE_INS]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeIns>
{
    typedef TIns Type;
};

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeIns>
{
    typedef TIns const Type;
};

// ----------------------------------------------------------------------------
// Metafunction DeltaValue                                   [DELTA_TYPE_SV]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeSV>
{
    typedef TSV Type;
};

template <typename TSnp, typename TDel, typename TIns, typename TSV>
struct DeltaValue<impl::DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeSV>
{
    typedef TSV const Type;
};

// ============================================================================
// Punlic Functions
// ============================================================================

// ----------------------------------------------------------------------------
// Function isDeltaType()
// ----------------------------------------------------------------------------

template <typename TTag>
inline bool
isDeltaType(DeltaType type, TTag const & /*tag*/)
{
    return type == static_cast<DeltaType>(Find<DeltaTypeSelector, TTag>::VALUE);
}

// ----------------------------------------------------------------------------
// Function selectDeltaType()                                    [DeltaTypeSnp]
// ----------------------------------------------------------------------------

template <typename TTag>
constexpr inline DeltaType
selectDeltaType(TTag const & /*tag*/)
{
    return static_cast<DeltaType>(Find<DeltaTypeSelector, TTag>::VALUE);
}

// ----------------------------------------------------------------------------
// Function setDeltaType()
// ----------------------------------------------------------------------------

inline void
setDeltaType(DeltaTypeSelector & selector, DeltaType const deltaType)
{
    value(selector) = deltaType;
}

// ============================================================================
// Private Functions
// ============================================================================

namespace impl
{

// ----------------------------------------------------------------------------
// Function getDeltaStore()                                     [DeltaTypeSnp]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeSnp>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> & store, DeltaTypeSnp /*tag*/)
{
    return store._snpData;
}

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeSnp>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> const & store, DeltaTypeSnp /*tag*/)
{
    return store._snpData;
}

// ----------------------------------------------------------------------------
// Function getDeltaStore()                                     [DeltaTypeDel]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeDel>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> & store, DeltaTypeDel /*tag*/)
{
    return store._delData;
}

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeDel>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> const & store, DeltaTypeDel /*tag*/)
{
    return store._delData;
}

// ----------------------------------------------------------------------------
// Function getDeltaStore()                                     [DeltaTypeIns]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeIns>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> & store, DeltaTypeIns /*tag*/)
{
    return store._insData;
}

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeIns>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> const & store, DeltaTypeIns /*tag*/)
{
    return store._insData;
}

// ----------------------------------------------------------------------------
// Function getDeltaStore()                                      [DeltaTypeSV]
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV>, DeltaTypeSV>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> & store, DeltaTypeSV /*tag*/)
{
    return store._svData;
}

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline typename Member<DeltaStore<TSnp, TDel, TIns, TSV> const, DeltaTypeSV>::Type &
getDeltaStore(DeltaStore<TSnp, TDel, TIns, TSV> const & store, DeltaTypeSV /*tag*/)
{
    return store._svData;
}

// ----------------------------------------------------------------------------
// Function addDeltaValue()
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV, typename TTag>
inline typename Size<DeltaStore<TSnp, TDel, TIns, TSV> >::Type
addDeltaValue(DeltaStore<TSnp, TDel, TIns, TSV> & store,
              typename DeltaValue<DeltaStore<TSnp, TDel, TIns, TSV>, TTag>::Type const & value,
              TTag /*deltaType*/)
{
    appendValue(getDeltaStore(store, TTag()), value);
    return length(getDeltaStore(store, TTag())) - 1;
}

// ----------------------------------------------------------------------------
// Function eraseDeltaValue()
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV, typename TPos, typename TTag>
inline typename Size<DeltaStore<TSnp, TDel, TIns, TSV> >::Type
eraseDeltaValue(DeltaStore<TSnp, TDel, TIns, TSV> & store,
                TPos recordPos,
                TTag /*deltaType*/)
{
    typedef typename Size<DeltaStore<TSnp, TDel, TIns, TSV> >::Type TSize;
    if (SEQAN_LIKELY(static_cast<TSize>(recordPos) < length(getDeltaStore(store, TTag()))))
        erase(getDeltaStore(store, TTag()), recordPos);
    return length(getDeltaStore(store, TTag()));
}

// ----------------------------------------------------------------------------
// Function clear()
// ----------------------------------------------------------------------------

template <typename TSnp, typename TDel, typename TIns, typename TSV>
inline void
clear(DeltaStore<TSnp, TDel, TIns, TSV> & deltaStore)
{
    clear(deltaStore._delData);
    clear(deltaStore._svData);
    clear(deltaStore._insData);
    clear(deltaStore._snpData);
}

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

template <typename TSnp, typename TDel, typename TIns, typename TSV, typename TPos, typename TTag>
inline typename DeltaValue<DeltaStore<TSnp, TDel, TIns, TSV>, TTag>::Type &
deltaValue(DeltaStore<TSnp, TDel, TIns, TSV> & store, TPos pos, TTag const & tag)
{
    typedef typename Size<DeltaStore<TSnp, TDel, TIns, TSV> >::Type TSize SEQAN_TYPEDEF_FOR_DEBUG;
    SEQAN_ASSERT_LT(static_cast<TSize>(pos), length(getDeltaStore(store, tag)));

    return value(getDeltaStore(store, tag), pos);
}

template <typename TSnp, typename TDel, typename TIns, typename TSV, typename TPos, typename TTag>
inline typename DeltaValue<DeltaStore<TSnp, TDel, TIns, TSV> const, TTag>::Type &
deltaValue(DeltaStore<TSnp, TDel, TIns, TSV> const & store, TPos pos, TTag const & tag)
{
    typedef typename Size<DeltaStore<TSnp, TDel, TIns, TSV> const>::Type TSize SEQAN_TYPEDEF_FOR_DEBUG;
    SEQAN_ASSERT_LT(static_cast<TSize>(pos), length(getDeltaStore(store, tag)));

    return value(getDeltaStore(store, tag), pos);
}

// ----------------------------------------------------------------------------
// Function deletionSize()
// ----------------------------------------------------------------------------

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
deletionSize(typename DeltaValue<TDeltaStore, DeltaTypeSnp>::Type const & /*snp*/)
{
    return 1;
}

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
deletionSize(typename DeltaValue<TDeltaStore, DeltaTypeIns>::Type const & /*ins*/)
{
    return 0;
}

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
deletionSize(typename DeltaValue<TDeltaStore, DeltaTypeDel>::Type const & del)
{
    return del;
}

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
deletionSize(typename DeltaValue<TDeltaStore, DeltaTypeSV>::Type const & sv)
{
    return sv.i1;
}

template <typename TStore, typename TPos, typename TTag>
inline typename Size<TStore>::Type
deletionSize(TStore const & store, TPos const pos, TTag const & /*tag*/)
{
    return deletionSize<TStore>(getDeltaStore(store, TTag())[pos]);
}

// ----------------------------------------------------------------------------
// Function insertionSize()
// ----------------------------------------------------------------------------

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
insertionSize(typename DeltaValue<TDeltaStore, DeltaTypeSnp>::Type const & /*snp*/)
{
    return 1;
}

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
insertionSize(typename DeltaValue<TDeltaStore, DeltaTypeIns>::Type const & ins)
{
    return length(ins);
}

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
insertionSize(typename DeltaValue<TDeltaStore, DeltaTypeDel>::Type const & /*del*/)
{
    return 0;
}

template <typename TDeltaStore>
inline typename Size<TDeltaStore>::Type
insertionSize(typename DeltaValue<TDeltaStore, DeltaTypeSV>::Type const & sv)
{
    return length(sv.i2);
}

template <typename TStore, typename TPos, typename TTag>
inline typename Size<TStore>::Type
insertionSize(TStore const & store, TPos const pos, TTag const & /*tag*/)
{
    return insertionSize<TStore>(getDeltaStore(store, TTag())[pos]);
}

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

template <typename TStore, typename TPos, typename TTag>
inline typename MakeSigned<typename Size<TStore>::Type>::Type
netSize(TStore const & store, TPos const pos, TTag const & /*tag*/)
{
    typedef typename MakeSigned<typename Size<TStore>::Type>::Type TSignedSize;
    return static_cast<TSignedSize>(insertionSize(store, pos, TTag())) -
           static_cast<TSignedSize>(deletionSize(store, pos, TTag()));
}

}  // namespace impl

}  // namespace seqan

#endif // EXTRAS_INCLUDE_SEQAN_JOURNALED_STRING_TREE_DELTA_STORE_H_