#include "MismatchScores.h"

CMismatchScores::CMismatchScores(void)
{
    this->uiNoOfReads = 0;
    this->mismatchScore = NULL;
    // Set function pointers for updates the best records
    this->switchUpdatesAndIsBest(true, false /* Default is normal update */);
}

CMismatchScores::CMismatchScores(unsigned int uiNoOfReads)
{
    this->isBest = &CMismatchScores::dummyBest;
    this->update = &CMismatchScores::normalUpdate;
    this->uiNoOfReads = uiNoOfReads;
    this->mismatchScore = new short[uiNoOfReads];
    this->noOfBestMappings = new unsigned char[uiNoOfReads];
    memset(mismatchScore, CHAR_MAX, uiNoOfReads);
    memset(noOfBestMappings, 0, uiNoOfReads);
    // Set function pointers for updates the best records
}

CMismatchScores::~CMismatchScores(void)
{
    delete [] this->mismatchScore;
    delete [] this->noOfBestMappings;
}

int CMismatchScores::printArray(char* filename)
{
    ofstream ofile(filename);
    for (unsigned int i = 0; i < this->uiNoOfReads; i++) {
        ofile << (int)this->mismatchScore[i] << '\n';
    }
    ofile.close();
    return(0);
}

int CMismatchScores::doStatistics(unsigned int uiNoOfReads, char* dataSet, int numOfSNP)
{
    const int maxTolerateSNP = 5;

    int misMatchCount[maxTolerateSNP];
    for (int i = 0; i < (int)maxTolerateSNP; i++) {
        misMatchCount[i] = 0;
    }
    int complementSNPcount = 0;
    int transversionSNPCount = 0;
    int transitionSNPCount = 0;
    int doubleSNPcount = 0;
    int totalMapedRead = 0;

    for (int i = 0; i < (int)uiNoOfReads; i++) {
        int mis = ((int)this->mismatchScore[i] >> 3);
        if (mis <= maxTolerateSNP) { // There is an alignment
            int SNPtype = (int)(this->mismatchScore[i] & 0x07);
            switch (SNPtype) {
            case 1:
                complementSNPcount++;
                break;
            case 2:
                transversionSNPCount++;
                break;
            case 3:
                transitionSNPCount++;
                break;
            case 4:
                doubleSNPcount++;
                break;
            default:
                break;
            }
            misMatchCount[mis]++;
            totalMapedRead++;
        }
    }
    int noReadsSupportValidSnp = complementSNPcount +
                                 transversionSNPCount + transitionSNPCount + doubleSNPcount;

    cout << "There are " << totalMapedRead << " reads mapped." << endl;
    cout << "Total " << noReadsSupportValidSnp << " reads indicating valid SNP " << endl;
    cout << complementSNPcount << " reads indicate complement SNPs " << endl;
    cout << transversionSNPCount << " reads indicate transversion SNPs " << endl;
    cout << transitionSNPCount << " reads indicate transition SNPs " << endl;
    cout << doubleSNPcount <<  " reads indicate more than one SNPs " << endl;
    cout << "Identify " << numOfSNP << endl;
    cout << " The color mismatches histogram:\n" << endl;
    for (int i = 0; i < maxTolerateSNP; i++) {
        cout << i << " mismatches " << misMatchCount[i] << endl;
    }

    // Print in the log file
    ofstream ofile("SNP.log", ofstream::app);
    if (ofile.good()) {
        ofile << dataSet << ',' << totalMapedRead << ',';
        ofile << misMatchCount[0] << ',' << misMatchCount[1] << ',' << misMatchCount[2] << ','\
              << misMatchCount[3] << ',' << misMatchCount[4] << ',';
        ofile << noReadsSupportValidSnp << ',' << complementSNPcount << ',' << transversionSNPCount << ','\
              << transitionSNPCount << ',' << doubleSNPcount << ',' << numOfSNP << endl;
        ofile.close();
    }
    /* DEBUG, print the read id of the one mismatch.
    ofstream debugfile("debug.3sub", ofstream::app);
    {
        for(unsigned int i = 0; i < this->uiNoOfReads; i++) {
            if((int)this->mismatchScore[i] < 0) {
                debugfile << i << endl;
            }
        }
    } debugfile.close(); */
    return(0);
}

// Currently mismatch Score are encoded as # of mismatches * 8 + the SNP type.
// Only the best mapping are record. The order of "better mapping as follows.
// Exact match << 1 Sub << 1 SNP << 2 Sub << 1 SNP 1Sub << 3 Sub << 2 SNP << 2 Sub 1 SNP << 4 Sub
int CMismatchScores::normalUpdate(unsigned int readId, int score)
{
    int updateFlag = 0; // No update
    if ((char)score < this->mismatchScore[readId]) {
        this->mismatchScore[readId] = (char)score;
        updateFlag = 1;
    }

    if (updateFlag > 0) {
        noOfBestMappings[readId] = 1; // New best record
    } else if ((char)score == this->mismatchScore[readId]) {
        // No update but same score
        addCounter(readId); //Ambiguous reads
    }  // else the score is worse than the best
    return(updateFlag);
}

int CMismatchScores::solidUpdate(unsigned int readId, int score)
{
    const int DIGITS4_SNP_TYPE = 3;
    const char SNP_TYPE_MASK = 0x07; // get last three digit

    enum UPDATED_FLAG { NEW_BEST_RECORD, SAME_BEST_RECORD, WORSE_RECORD };

    int updateFlag = WORSE_RECORD;
    int noOfSub = (score >> DIGITS4_SNP_TYPE);
    int noOfBestSubInRecord = (this->mismatchScore[readId] >> DIGITS4_SNP_TYPE);

    if (noOfSub < noOfBestSubInRecord) {
        this->mismatchScore[readId] = (short)score;
        updateFlag = NEW_BEST_RECORD;
    } else if (noOfSub == noOfBestSubInRecord) {
        // If the new alignment has valid SNP while the recored alignment don't
        if (((score & 0x07) > 1) && ((this->mismatchScore[readId] & SNP_TYPE_MASK) == 0)) {
            this->mismatchScore[readId] = (short)score;
            updateFlag = NEW_BEST_RECORD;
        } else {
            updateFlag = SAME_BEST_RECORD; // has the same best record;
        }
    } // else no updated

    if (updateFlag == NEW_BEST_RECORD) {
        noOfBestMappings[readId] = 1; // New best record
    } else if (updateFlag == SAME_BEST_RECORD) {
        addCounter(readId); //Ambiguous reads
    }  // else the score is worse than the best in record

    return(updateFlag);
}

int CMismatchScores::dummyUpdate(unsigned int readId , int score)
{
    score = 0;
    readId = 0;
    return(0);
}

bool CMismatchScores::callIsBest(unsigned int readId , int score)
{
    return((this->*isBest)(readId, score));
}

bool CMismatchScores::isBestInRecords(unsigned int readId , int score)
{
    return((char) score ==  this->mismatchScore[readId]);
}

inline bool CMismatchScores::compareRecord4Solid(unsigned int readId , int score)
{
    const int DIGITS4_SNP_TYPE = 3;
    const char SNP_TYPE_MASK = 0x07; // get last three digit

    int diff = (score >> DIGITS4_SNP_TYPE);
    int minDiffInRecord = ((int)this->mismatchScore[readId]) >> DIGITS4_SNP_TYPE;
    if (diff > minDiffInRecord) {
        return(false);
    } else {
        bool isRecordSupportSNP = ((int)(this->mismatchScore[readId] & SNP_TYPE_MASK) > 0);
        bool isAlignmentSupportSNP = ((score & SNP_TYPE_MASK) > 0);
        return(isRecordSupportSNP == isAlignmentSupportSNP);
    }
}

bool CMismatchScores::isBestInRecords4Solid(unsigned int readId , int score)
{
    if ((char) score ==  this->mismatchScore[readId]) {
        return(true);
    } else {
        if (score >= 0x08) { //SOliD encoding (TODO) find a better way to change this
            return(compareRecord4Solid(readId, score));
        } else {
            return(false);
        }
    }
}

bool CMismatchScores::dummyBest(unsigned int readId , int score)
{
    score = 0;
    readId = 0;
    return(true);
}

void CMismatchScores::switchUpdatesAndIsBest(bool firstRun, bool solidScore)
{
    if (firstRun) {
        if (solidScore) {
            this->update = &CMismatchScores::solidUpdate;
        } else {
            this->update = &CMismatchScores::normalUpdate;
        }
        this->isBest = &CMismatchScores::dummyBest;
    } else {
        this->update = &CMismatchScores::dummyUpdate;
        if (solidScore) {
            this->isBest = &CMismatchScores::isBestInRecords4Solid;
        } else {
            this->isBest = &CMismatchScores::isBestInRecords;
        }
    }
}

int CMismatchScores::callUpdate(unsigned int readId , int score)
{
    return((this->*update)(readId, score));
}

inline unsigned char CMismatchScores::addCounter(int readId)
{
    if (noOfBestMappings[readId] < UCHAR_MAX) {
        noOfBestMappings[readId]++;
    }
    return(noOfBestMappings[readId]);
}



