// ==========================================================================
//                 SeqAn - The Library for Sequence Analysis
// ==========================================================================
// Copyright (c) 2006-2013, 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: Manuel Holtgrewe <manuel.holtgrewe@fu-berlin.de>
// ==========================================================================

#ifndef SEQAN_EXTRAS_INCLUDE_PROFILE_SCORE_PROFILE_SEQ_H_
#define SEQAN_EXTRAS_INCLUDE_PROFILE_SCORE_PROFILE_SEQ_H_

namespace seqan {

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

// ProfileSeqScore

struct ProfileSeqScore_;
typedef Tag<ProfileSeqScore_> ProfileSeqScore;

template <typename TValue>
class Score<TValue, ProfileSeqScore>;

template <typename TValue, typename TString>
inline void
assignProfile(Score<TValue, ProfileSeqScore> & me, TString const & profile);

// ProfileSeqFracScore

struct ProfileSeqFracScore_;
typedef Tag<ProfileSeqFracScore_> ProfileSeqFracScore;

template <typename TValue>
class Score<TValue, ProfileSeqFracScore>;

template <typename TValue, typename TString>
inline void
assignProfile(Score<TValue, ProfileSeqFracScore> & me, TString const & profile);

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

// ----------------------------------------------------------------------------
// Class ProfileSeq Score
// ----------------------------------------------------------------------------

struct ProfileSeqScore_;
typedef Tag<ProfileSeqScore_> ProfileSeqScore;

/*!
 * @class ProfileSeqScore ProfileSeq Score
 * @extends Score
 * @headerfile <seqan/align_profile.h>
 * @brief Score for sequence-to-profile alignments.
 *
 * Using this class, you can align sequences to profiles.  The profile is assumed to be in the horizontal direction
 * (first row), the sequence in the vertical direction (second row).
 *
 * Scoring works as follows.
 *
 * The integer <tt>SEQAN_CONSENSUS_UNITY</tt> and fractions thereof are used to express scores.  Gap opens in the
 * profile are scored proportional to the number of gaps in the profile with two times unity, gap extends with one times
 * unity at the position.
 *
 * Gap opens in the sequence are scored with two times unity, gap extends in the sequence with unity.  Alignments of
 * profile characters to sequence characters are scored with the fraction of profile characters that match the sequence
 * characters times unity.
 *
 * @signature template <typename TValue>
 *            class Score<TValue, ProfileSeqScore>;
 *
 * @tparam TValue The integer type to use for representing scores.
 *
 * @section Examples
 * The following example uses the ProfileSeq Score to align a sequence against a profile.
 * Note that we print the gap state for each position since profiles cannot be printed to one stdout character.
 * 
 * @include demos/align_profile/profile_seq_score.cpp
 *
 * The output is as follows:
 *
 * @code
 * score value = -2097152
 * gaps in profile/sequence
 * pos G   S
 * 0   0   0
 * 1   1   0
 * 2   0   0
 * 3   1   0
 * 4   0   0
 * 5   0   0
 * @endcode
 */

/*!
 * @fn ProfileSeqScore::Score
 * @brief Constructor
 * 
 * @signature Score::Score();
 * @signature Score::Score(profile);
 *
 * @param profile The profile to copy from (@link AllocString @endlink of @link ProfileChar @endlink objects).
 *
 * When providing <tt>profile</tt>, the function @link ProfileSeqScore#assignProfile @endlink is automatically used to
 * assign the profile to this class.
 */

/**
.Spec.ProfileSeq Score
..cat:Profile Alignment
..general:Class.Score
..summary:Score for profile-to-sequence alignments.
..description:
Using this @Class.Score@ specialization, you can use profiles for the horizontal and sequences for the vertical
sequence in the pairwise DP alignment functions.
..description:
Scoring works as follows.
The integer $SEQAN_CONSENSUS_UNITY$ and fractions thereof are used to express scores.
Gap opens in the profile are scored proportional to the number of gaps in the profile with two times unity, gap extends
with one times unity at the position.
Gap opens in the sequence are scored with two times unity, gap extends in the sequence with unity.
Alignments of profile characters to sequence characters are scored with the fraction of profile characters that
match the sequence characters times unity.
..signature:class Score<TValue, ProfileSeqScore>;
..param.TValue:The integer type to use for representing scores.
..example.text:
The following example uses the ProfileSeq Score to align a sequence against a profile.
Note that we print the gap state for each position since profiles cannot be printed to one stdout character.
..example.file:demos/align_profile/profile_seq_score.cpp
..example.output:
score value = -2097152
gaps in profile/sequence
pos G   S
0   0   0
1   1   0
2   0   0
3   1   0
4   0   0
5   0   0
..include:seqan/align_profile.h

.Memfunc.ProfileSeq Score#Score
..class:Spec.ProfileSeq Score
..summary:Constructor.
..signature:Score::Score();
..signature:Score::Score(profile);
..descripton:Can be default constructed or directly be initialized with a profile.
When providing $profile$, the function @Function.ProfileSeq Score#assignProfile@ is automatically used to assign
the profile to this class.
..see:Function.ProfileSeq Score#assignProfile
*/

template <typename TValue>
class Score<TValue, ProfileSeqScore>
{
  public:
    // A table of position x (ord value) giving the counts of the characters at the given positions.
    String<int> consensusSet;

    Score() {}

    // Construct given a profile string.
    template <typename TProfile>
    explicit
    Score(TProfile const & profile)
    {
        assignProfile(*this, profile);
    }
};

// ----------------------------------------------------------------------------
// Class ProfileSeqFrac Score
// ----------------------------------------------------------------------------

struct ProfileSeqFracScore_;
typedef Tag<ProfileSeqFracScore_> ProfileSeqFracScore;

/*!
 * @class ProfileSeqFracScore ProfileSeqFrac Score
 * @extends Score
 * @headerfile <seqan/align_profile.h>
 * @brief Score for sequence-to-profile alignments.
 *
 * Using this class, you can align sequences to profiles.  The profile is assumed to be in the horizontal direction
 * (first row), the sequence in the vertical direction (second row).
 *
 * Scoring works as follows.
 *
 * The integer <tt>SEQAN_CONSENSUS_UNITY</tt> and fractions thereof are used to express scores.  Gap opens in the
 * profile are scored proportional to the number of gaps in the profile two times unity, gap extends are scored
 * proportional to the number of gaps in the profile at the position.  Gap opens in the sequence are scored with two
 * times unity, gap extends with one times unity.
 *
 * @signature template <typename TValue>
 *            class Score<TValue, ProfileSeqFracScore>;
 *
 * @tparam TValue The integer type to use for representing scores.
 *
 * @section Examples
 *
 * The following example uses the ProfileSeqFrac Score to align a sequence against a profile.  Note that we print the
 * gap state for each position since profiles cannot be printed to one stdout character.
 * 
 * @include demos/align_profile/profile_seq_frac_score.cpp
 *
 * The output is as follows:
 *
 * @code
 * score value = -2097152
 * gaps in profile/sequence
 * pos G   S
 * 0   0   0
 * 1   1   0
 * 2   0   0
 * 3   1   0
 * 4   0   0
 * 5   0   0
 * @endcode
 */

/*!
 * @fn ProfileSeqFracScore::Score
 * @brief Constructor
 * 
 * @signature Score::Score();
 * @signature Score::Score(profile);
 *
 * @param profile The profile to copy from (@link AllocString @endlink of @link ProfileChar @endlink objects).
 *
 * When providing <tt>profile</tt>, the function @link ProfileSeqFracScore#assignProfile @endlink is automatically used to
 * assign the profile to this class.
 */


/**
.Spec.ProfileSeqFrac Score
..cat:Profile Alignment
..general:Class.Score
..summary:Score for profile-to-sequence alignments.
..description:
Using this @Class.Score@ specialization, you can use profiles for the horizontal and sequences for the vertical
sequence in the pairwise DP alignment functions.
..description:
Scoring works as follows.
The integer $SEQAN_CONSENSUS_UNITY$ and fractions thereof are used to express scores.
Gap opens in the profile are scored proportional to the number of gaps in the profile two times unity,
gap extends are scored proportional to the number of gaps in the profile at the position.
Gap opens in the sequence are scored with two times unity, gap extends with one times unity.
..signature:class Score<TValue, ProfileSeqFracScore>;
..param.TValue:The integer type to use for representing scores.
..example.text:
The following example uses the ProfileSeqFrac Score to align a sequence against a profile.
Note that we print the gap state for each position since profiles cannot be printed to one stdout character.
..example.file:demos/align_profile/profile_seq_frac_score.cpp
..example.output:
score value = -2097152
gaps in profile/sequence
pos G   S
0   0   0
1   1   0
2   0   0
3   1   0
4   0   0
5   0   0
..include:seqan/align_profile.h

.Memfunc.ProfileSeqFrac Score#Score
..class:Spec.ProfileSeqFrac Score
..summary:Constructor.
..signature:Score::Score();
..signature:Score::Score(profile);
..descripton:Can be default constructed or directly be initialized with a profile.
When providing $profile$, the function @Function.ProfileSeqFrac Score#assignProfile@ is automatically used to assign
the profile to this class.
..see:Function.ProfileSeqFrac Score#assignProfile
*/

template <typename TValue>
class Score<TValue, ProfileSeqFracScore>
{
  public:
    // Total number of profile characters in each column
    String<int> sum;

    Score() {}

    // Construct given a profile string.
    template <typename TProfile>
    explicit
    Score(TProfile const & profile)
    {
        assignProfile(*this, profile);
    }
};

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

// --------------------------------------------------------------------------
// Metafunction SequenceEntryForScore                      [ProfileSeq Score]
// --------------------------------------------------------------------------

// Returns the type that holds a sequence entry.  This is used for abstracting away the access to sequence characters.

template <typename TValue, typename TSequence>
struct SequenceEntryForScore<Score<TValue, ProfileSeqScore>, TSequence>
{
    typedef ConsensusScoreSequenceEntry<TSequence> Type;
};

template <typename TValue, typename TSequence>
struct SequenceEntryForScore<Score<TValue, ProfileSeqScore> const, TSequence> :
            SequenceEntryForScore<Score<TValue, ProfileSeqScore>, TSequence>
{};

// --------------------------------------------------------------------------
// Metafunction SequenceEntryForScore                  [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

// Returns the type that holds a sequence entry.  This is used for abstracting away the access to sequence characters.

template <typename TValue, typename TSequence>
struct SequenceEntryForScore<Score<TValue, ProfileSeqFracScore>, TSequence>
{
    typedef ConsensusScoreSequenceEntry<TSequence> Type;
};

template <typename TValue, typename TSequence>
struct SequenceEntryForScore<Score<TValue, ProfileSeqFracScore> const, TSequence> :
            SequenceEntryForScore<Score<TValue, ProfileSeqFracScore>, TSequence>
{};

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

// --------------------------------------------------------------------------
// Function sequenceEntryForScore()                        [ProfileSeq Score]
// --------------------------------------------------------------------------

template <typename TScoreValue, typename TSequence, typename TPosition>
inline ConsensusScoreSequenceEntry<TSequence>
sequenceEntryForScore(Score<TScoreValue, ProfileSeqScore> const & /*sScheme*/,
                      TSequence const & seq, TPosition pos)
{
    return ConsensusScoreSequenceEntry<TSequence>(seq, pos);
}

// --------------------------------------------------------------------------
// Function assignProfile()                                [ProfileSeq Score]
// --------------------------------------------------------------------------

/*!
 * @fn ProfileSeqScore#assignProfile
 * @brief Assign profile to ProfileSeqScore.
 *
 * @signature void assignProfile(score, profile);
 *
 * @param[out] score   The ProfileSeqScore object to assign the profile for.
 * @param[in]  profile The profile to assign to the score.  @link AllocString @endlink of @link ProfileChar @endlink.
 */

/**
.Function.ProfileSeq Score#assignProfile
..class:Spec.ProfileSeq Score
..summary:Assign profile to ProfileSeg Score.
..signature:void assignProfile(score, profile);
..param.score:The @Spec.ProfileSeq Score@ object to assign the profile for.
...type:Spec.ProfileSeq Score
..param.profile:A string of @Class.ProfileChar@ with the profile to align against.
*/

template <typename TValue, typename TString>
inline void
assignProfile(Score<TValue, ProfileSeqScore> & me,
              TString const & profile)
{
    typedef typename Size<TString>::Type TSize;
    TSize alphSize = ValueSize<typename Value<TString>::Type>::VALUE;
    resize(me.consensusSet, alphSize * length(profile));

    typedef typename Iterator<TString const, Standard>::Type TIter;
    typedef typename Iterator<String<TValue>, Standard>::Type TConsSetIter;
    TConsSetIter itConsSet = begin(me.consensusSet, Standard());
    TIter it = begin(profile, Standard());
    TIter itEnd = end(profile, Standard());
    TSize maxCount = 0;
    for (;it!=itEnd;++it)
    {
        maxCount = 0;
        for (TSize i = 0; i<alphSize; ++i)
            if ((TSize)(*it).count[i] > maxCount)
                maxCount = (*it).count[i];
        for (TSize i = 0; i<alphSize; ++i, ++itConsSet)
            *itConsSet = ((TSize)(*it).count[i] == maxCount)? 0 : (-SEQAN_CONSENSUS_UNITY);
    }
}

// --------------------------------------------------------------------------
// Function scoreGapExtendHorizontal()                     [ProfileSeq Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapExtendHorizontal(
        Score<TValue, ProfileSeqScore> const & me,
        ConsensusScoreSequenceEntry<TSeq1> const & entry1,
        ConsensusScoreSequenceEntry<TSeq2> const & entry2)
{
    typedef typename Value<TSeq1>::Type TValue1;
    if ((int)position(entry2) < 0)
        return -SEQAN_CONSENSUS_UNITY;
    else
        return me.consensusSet[position(entry1) * (ValueSize<TValue1>::VALUE) + (ValueSize<TValue1>::VALUE - 1)];
}

// --------------------------------------------------------------------------
// Function scoreGapOpenHorizontal()                       [ProfileSeq Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapOpenHorizontal(
        Score<TValue, ProfileSeqScore> const & me,
        ConsensusScoreSequenceEntry<TSeq1> const & entry1,
        ConsensusScoreSequenceEntry<TSeq2> const & entry2)
{
    typedef typename Value<TSeq1>::Type TValue1;
    if ((int)position(entry2) < 0)
        return -2 * SEQAN_CONSENSUS_UNITY;
    else
        return 2 * me.consensusSet[position(entry1) * (ValueSize<TValue1>::VALUE) + (ValueSize<TValue1>::VALUE - 1)];
}

// --------------------------------------------------------------------------
// Function scoreGapExtendVertical()                       [ProfileSeq Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapOpenVertical(
        Score<TValue, ProfileSeqScore> const &,
        ConsensusScoreSequenceEntry<TSeq1> const & /*entry1*/,
        ConsensusScoreSequenceEntry<TSeq2> const & /*entry2*/)
{
    return -2 * SEQAN_CONSENSUS_UNITY;
}

// --------------------------------------------------------------------------
// Function scoreGapOpenVertical()                         [ProfileSeq Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapExtendVertical(
        Score<TValue, ProfileSeqScore> const &,
        ConsensusScoreSequenceEntry<TSeq1> const & /*entry1*/,
        ConsensusScoreSequenceEntry<TSeq2> const & /*entry2*/)
{
    return -SEQAN_CONSENSUS_UNITY;
}

// --------------------------------------------------------------------------
// Function score()                                        [ProfileSeq Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
score(Score<TValue, ProfileSeqScore> const & me,
      ConsensusScoreSequenceEntry<TSeq1> const & entry1,
      ConsensusScoreSequenceEntry<TSeq2> const & entry2)
{
    typedef typename Value<TSeq1>::Type TValue1;
    return me.consensusSet[position(entry1) * (ValueSize<TValue1>::VALUE) + ordValue(value(entry2))];
}

// --------------------------------------------------------------------------
// Function sequenceEntryForScore()                    [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

template <typename TScoreValue, typename TSequence, typename TPosition>
inline ConsensusScoreSequenceEntry<TSequence>
sequenceEntryForScore(Score<TScoreValue, ProfileSeqFracScore> const & /*sScheme*/,
                      TSequence const & seq, TPosition pos)
{
    return ConsensusScoreSequenceEntry<TSequence>(seq, pos);
}

// --------------------------------------------------------------------------
// Function assignProfile()                            [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

/*!
 * @fn ProfileSeqFracScore#assignProfile
 * @brief Assign profile to ProfileSeqFrac Score.
 *
 * @signature void assignProfile(score, profile);
 *
 * @param[out] score   The ProfileSeqScore object to assign the profile for.
 * @param[in]  profile The profile to assign to the score.  @link AllocString @endlink of @link ProfileChar @endlink.
 */

/**
.Function.ProfileSeqFrac Score#assignProfile
..class:Spec.ProfileSeqFrac Score
..summary:Assign profile to ProfileSegFrac Score.
..signature:void assignProfile(score, profile);
..param.score:The @Spec.ProfileSeq Score@ object to assign the profile for.
...type:Spec.ProfileSeqFrac Score
..param.profile:A string of @Class.ProfileChar@ with the profile to align against.
*/
template <typename TValue, typename TString>
inline void
assignProfile(Score<TValue, ProfileSeqFracScore> & me,
              TString const & profile)
{
    typedef typename Size<TString>::Type TSize;
    resize(me.sum, length(profile));
    typedef typename Iterator<TString const, Standard>::Type TIter;
    typedef typename Iterator<String<int>, Standard>::Type TSumIter;
    TSumIter itSum = begin(me.sum, Standard());
    TIter it = begin(profile, Standard());
    TIter itEnd = end(profile, Standard());
    for (; it!=itEnd; ++it, ++itSum)
    {
        *itSum = 0;
        for (TSize i = 0; i < (TSize) ValueSize<typename Value<TString>::Type>::VALUE; ++i)
            *itSum += (*it).count[i];
    }
}

// --------------------------------------------------------------------------
// Function scoreGapExtendHorizontal()                 [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapExtendHorizontal(
        Score<TValue, ProfileSeqFracScore> const & me,
        ConsensusScoreSequenceEntry<TSeq1> const & entry1,
        ConsensusScoreSequenceEntry<TSeq2> const & entry2)
{
    if (((int)position(entry2) < 0) || (!me.sum[position(entry1)]))
        return -SEQAN_CONSENSUS_UNITY;
    else
        return ((TValue) (( (int)value(entry1).count[ValueSize<typename Value<TSeq1>::Type>::VALUE - 1] - me.sum[position(entry1)]) * SEQAN_CONSENSUS_UNITY) / me.sum[position(entry1)]);
}

// --------------------------------------------------------------------------
// Function scoreGapOpenHorizontal()                   [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapOpenHorizontal(
        Score<TValue, ProfileSeqFracScore> const & me,
        ConsensusScoreSequenceEntry<TSeq1> const & entry1,
        ConsensusScoreSequenceEntry<TSeq2> const & entry2)
{
    if (((int)position(entry2) < 0) || (!me.sum[position(entry1)]))
        return -SEQAN_CONSENSUS_UNITY;
    else
        return ((TValue) (((int)value(entry1).count[ValueSize<typename Value<TSeq1>::Type>::VALUE - 1] - me.sum[position(entry1)]) * SEQAN_CONSENSUS_UNITY) / me.sum[position(entry1)]);
}

// --------------------------------------------------------------------------
// Function scoreGapExtendVertical()                   [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapOpenVertical(
        Score<TValue, ProfileSeqFracScore> const &,
        ConsensusScoreSequenceEntry<TSeq1> const & /*entry1*/,
        ConsensusScoreSequenceEntry<TSeq2> const & /*entry2*/)
{
    return -SEQAN_CONSENSUS_UNITY;
}

// --------------------------------------------------------------------------
// Function scoreGapOpenVertical()                     [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
scoreGapExtendVertical(
        Score<TValue, ProfileSeqFracScore> const &,
        ConsensusScoreSequenceEntry<TSeq1> const & /*entry1*/,
        ConsensusScoreSequenceEntry<TSeq2> const & /*entry2*/)
{
    return -SEQAN_CONSENSUS_UNITY;
}

// --------------------------------------------------------------------------
// Function score()                                    [ProfileSeqFrac Score]
// --------------------------------------------------------------------------

template <typename TValue, typename TSeq1, typename TSeq2>
inline TValue
score(Score<TValue, ProfileSeqFracScore> const & me,
      ConsensusScoreSequenceEntry<TSeq1> const & entry1,
      ConsensusScoreSequenceEntry<TSeq2> const & entry2)
{
    if (!me.sum[position(entry1)])
        return -SEQAN_CONSENSUS_UNITY;
    else
        return ((TValue) (((int)value(entry1).count[ordValue(value(entry2))] - me.sum[position(entry1)]) * SEQAN_CONSENSUS_UNITY) / me.sum[position(entry1)]);
}

}  // namespace seqan

#endif  // #ifndef SEQAN_EXTRAS_INCLUDE_PROFILE_SCORE_PROFILE_SEQ_H_
