#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_
