#ifndef COMPLETE_STRIPED_SMITH_WATERMAN_CPP_H_
#define COMPLETE_STRIPED_SMITH_WATERMAN_CPP_H_

#include <stdint.h>
#include <string>
#include <vector>

namespace StripedSmithWaterman {

struct Alignment
{
    uint16_t sw_score;            // The best alignment score
    uint16_t sw_score_next_best;  // The next best alignment score
    int32_t ref_begin;            // Reference begin position of the best alignment
    int32_t ref_end;              // Reference end position of the best alignment
    int32_t query_begin;          // Query begin position of the best alignment
    int32_t query_end;            // Query end position of the best alignment
    int32_t ref_end_next_best;    // Reference end position of the next best alignment
    int32_t mismatches;           // Number of mismatches of the alignment
    std::string cigar_string;     // Cigar string of the best alignment
    std::vector<uint32_t> cigar;  // Cigar stored in the BAM format
                                  //   high 28 bits: length
                                  //   low 4 bits: M/I/D/S/X (0/1/2/4/8);
    void Clear()
    {
        sw_score = 0;
        sw_score_next_best = 0;
        ref_begin = 0;
        ref_end = 0;
        query_begin = 0;
        query_end = 0;
        ref_end_next_best = 0;
        mismatches = 0;
        cigar_string.clear();
        cigar.clear();
    };
};

struct Filter
{
    // NOTE: No matter the filter, those five fields of Alignment will be given anyway.
    //       sw_score; sw_score_next_best; ref_end; query_end; ref_end_next_best.
    // NOTE: Only need score of alignments, please set 'report_begin_position'
    //       and 'report_cigar' false.

    bool report_begin_position;  // Give ref_begin and query_begin.
                                 //   If it is not set, ref_begin and query_begin are -1.
    bool report_cigar;           // Give cigar_string and cigar.
                                 //   report_begin_position is automatically TRUE.

    // When *report_cigar* is true and alignment passes these two filters,
    //   cigar_string and cigar will be given.
    uint16_t score_filter;     // score >= score_filter
    uint16_t distance_filter;  // ((ref_end - ref_begin) < distance_filter) &&
                               // ((query_end - read_begin) < distance_filter)

    Filter()
        : report_begin_position(true)
        , report_cigar(true)
        , score_filter(0)
        , distance_filter(32767){};

    Filter(const bool& pos, const bool& cigar, const uint16_t& score, const uint16_t& dis)
        : report_begin_position(pos)
        , report_cigar(cigar)
        , score_filter(score)
        , distance_filter(dis){};
};

class Aligner
{
public:
    // =========
    // @function Construct an Aligner on default values.
    //             The function will build the {A.C,G,T,N} aligner.
    //             If you target for other character aligners, then please
    //             use the other constructor and pass the corresponding matrix in.
    // =========
    Aligner(void);

    // =========
    // @function Construct an Aligner by assigning scores.
    //             The function will build the {A.C,G,T,N} aligner.
    //             If you target for other character aligners, then please
    //             use the other constructor and pass the corresponding matrix in.
    // =========
    Aligner(const uint8_t& match_score, const uint8_t& mismatch_penalty,
            const uint8_t& gap_opening_penalty, const uint8_t& gap_extending_penalty);

    // =========
    // @function Construct an Aligner by the specific matrixs.
    // =========
    Aligner(const int8_t* score_matrix, const int& score_matrix_size,
            const int8_t* translation_matrix, const int& translation_matrix_size);

    ~Aligner(void);

    // =========
    // @function Build the reference sequence and thus make
    //             Align(const char* query, s_align* alignment) function;
    //             otherwise the reference should be given when aligning.
    //           [NOTICE] If there exists a sequence, that one will be deleted
    //                    and replaced.
    // @param    seq    The reference bases;
    //                  [NOTICE] It is not necessary null terminated.
    // @param    length The length of bases will be be built.
    // @return   The length of the built bases.
    // =========
    int SetReferenceSequence(const char* seq, const int& length);

    void CleanReferenceSequence(void);

    // =========
    // @function Set penalties for opening and extending gaps
    //           [NOTICE] The defaults are 3 and 1 respectively.
    // =========
    void SetGapPenalty(const uint8_t& opening, const uint8_t& extending)
    {
        gap_opening_penalty_ = opening;
        gap_extending_penalty_ = extending;
    };

    // =========
    // @function Align the query againt the reference that is set by
    //             SetReferenceSequence.
    // @param    query     The query sequence.
    // @param    filter    The filter for the alignment.
    // @param    alignment The container contains the result.
    // @return   True: succeed; false: fail.
    // =========
    bool Align(const char* query, const Filter& filter, Alignment* alignment) const;

    // =========
    // @function Align the query againt the reference.
    //           [NOTICE] The reference won't replace the reference
    //                      set by SetReferenceSequence.
    // @param    query     The query sequence.
    // @param    ref       The reference sequence.
    //                     [NOTICE] It is not necessary null terminated.
    // @param    ref_len   The length of the reference sequence.
    // @param    filter    The filter for the alignment.
    // @param    alignment The container contains the result.
    // @return   True: succeed; false: fail.
    // =========
    bool Align(const char* query, const char* ref, const int& ref_len, const Filter& filter,
               Alignment* alignment) const;

    // @function Clear up all containers and thus the aligner is disabled.
    //             To rebuild the aligner please use Build functions.
    void Clear(void);

    // =========
    // @function Rebuild the aligner's ability on default values.
    //           [NOTICE] If the aligner is not cleaned, rebuilding will fail.
    // @return   True: succeed; false: fail.
    // =========
    bool ReBuild(void);

    // =========
    // @function Rebuild the aligner's ability by the specific matrixs.
    //           [NOTICE] If the aligner is not cleaned, rebuilding will fail.
    // @return   True: succeed; false: fail.
    // =========
    bool ReBuild(const uint8_t& match_score, const uint8_t& mismatch_penalty,
                 const uint8_t& gap_opening_penalty, const uint8_t& gap_extending_penalty);

    // =========
    // @function Construct an Aligner by the specific matrixs.
    //           [NOTICE] If the aligner is not cleaned, rebuilding will fail.
    // @return   True: succeed; false: fail.
    // =========
    bool ReBuild(const int8_t* score_matrix, const int& score_matrix_size,
                 const int8_t* translation_matrix, const int& translation_matrix_size);

private:
    int8_t* score_matrix_;
    int score_matrix_size_;
    int8_t* translation_matrix_;

    uint8_t match_score_;            // default: 2
    uint8_t mismatch_penalty_;       // default: 2
    uint8_t gap_opening_penalty_;    // default: 3
    uint8_t gap_extending_penalty_;  // default: 1

    int8_t* translated_reference_;
    int32_t reference_length_;

    int TranslateBase(const char* bases, const int& length, int8_t* translated) const;
    void SetAllDefault(void);
    void BuildDefaultMatrix(void);
    void ClearMatrices(void);

    Aligner& operator=(const Aligner&);
    Aligner(const Aligner&);
};  // class Aligner

// ================
// inline functions
// ================
inline void Aligner::CleanReferenceSequence(void)
{
    if (reference_length_ == 0) return;

    // delete the current buffer
    if (reference_length_ > 1)
        delete[] translated_reference_;
    else
        delete translated_reference_;

    reference_length_ = 0;
}
}  // namespace StripedSmithWaterman

#endif  // COMPLETE_STRIPED_SMITH_WATERMAN_CPP_H_
