// ####################################################################################################
//
// NOTE -- THIS CODE IS SYNCHRONIZED WITH CODE IN THE SAMTOOLS REPOSITORY.  CHANGES TO THIS CODE SHOULD BE
// NOTE -- PUSHED BACK TO HENG LI
//
// ####################################################################################################
// NOTE -- this code is a cpp version of code originally written by Heng Li in JAVA for the samtools project
// ####################################################################################################
#pragma once

#include <cctype>
#include <cmath>
#include <cstdint>
#include <string>
#include <vector>

#include "util/bam_wrap.h"
#include "util/sam_data.h"
#include "util/stable_array.h"
#include "aux_arg.h"

using std::vector;
using std::string;

// base alignment quality (BAQ)

struct BAQ {
    static void StaticInit() {  }

    static vector<double> qual2prob;  // 质量分数转化概率
    static constexpr double EM = 0.33333333333;
    static constexpr double EI = 0.25;
    // Phred scaled now (changed 1/10/2011)
    static constexpr double DEFAULT_GOP = 40;
    static constexpr int DEFAULT_BANDWIDTH = 7;
    static constexpr int SAM_MAX_PHRED_SCORE = 93;
    // 94 = max qual score + 1
    static vector<vector<vector<double>>> EPSILONS;  // [ref][read][qual]
    
    double cd = -1;   // gap open probability [1e-3]
    double ce = 0.1;  // gap extension probability [0.1]
    int cb = DEFAULT_BANDWIDTH;  // band width [7]

    /**
     * Any bases with Q < MIN_BASE_QUAL are raised up to this base quality
     */
    uint8_t minBaseQual = 4;

    /**
     * Use defaults for everything
     */
    BAQ() : BAQ(DEFAULT_GOP) {}

    /**
     * Use defaults for everything
     */
    BAQ(const double gapOpenPenalty) {
        cd = convertFromPhredScale(gapOpenPenalty);
        initializeCachedData();
    }

    /*  Takes a Phred Scale quality score and returns the error probability.
     *
     *  Quick conversion function to maintain internal structure of BAQ calculation on
     *  probability scale, but take the user entered parameter in phred-scale.
     *
     *  @param x phred scaled score
     *  @return probability of incorrect base call
     */
    static double convertFromPhredScale(double x) { return (std::pow(10, (-x) / 10.)); }

    // 初始化一些静态全局数据
    void initializeCachedData() {
        for (int i = 0; i < 256; i++)
            for (int j = 0; j < 256; j++)
                for (int q = 0; q <= SAM_MAX_PHRED_SCORE; q++) EPSILONS[i][j][q] = 1.0;

        for (char b1 : "ACGTacgt") {
            for (char b2 : "ACGTacgt") {
                for (int q = 0; q <= SAM_MAX_PHRED_SCORE; q++) {
                    double qual = qual2prob[q < minBaseQual ? minBaseQual : q];
                    double e = std::tolower(b1) == std::tolower(b2) ? 1 - qual : qual * EM;
                    EPSILONS[(uint8_t)b1][(uint8_t)b2][q] = e;
                }
            }
        }
    }

    double calcEpsilon(uint8_t ref, uint8_t read, uint8_t qualB) { return EPSILONS[ref][read][qualB]; }

    // 计算baq数组，返回成功与否
    bool calcBAQFromHMM(SamData& ad, string ref, int refOffset, vector<int>& baqArray);

    // 简单计算baq数组，就是全部赋值为'@' (64)
    static bool flatBAQArray(SamData& sd, StableArray<uint8_t>& baqArray) {
        baqArray.resize_fill(sd.read_len, (uint8_t)'@');
        return true;
    }

    // 计算真实的baq数组，耗时更多，好像enable-baq参数默认是关闭的，那就先不实现这个了
    static bool calculateBAQArray(AuxVar& aux, BAQ& baq, SamData& sd, vector<uint8_t>& baqArray) {
        baqArray.resize(sd.read_len, 0);
        return true;
    }
};