#pragma once
#include "stdafx.h"
#include "FileOutputBuffer.h"
#include "AlignmentsQ.h"
#include "ShortReadUtil.h"
#include "ColorSpaceRead.h" // TODO: Move that so Mapping Result is undependent to that

// class conatins the result of a mapping
const int READ_BUFFER_SIZE = MAX_READ_LENGTH * 2 + 1;
const int SCORES_BUFFER_SIZE = MAX_READ_LENGTH * 10 + 1;

class CMappingResult
{
public:
    CMappingResult(void);
    CMappingResult(CAlignmentsQ& que, unsigned int uiReadLength);
    ~CMappingResult(void);
    unsigned int uiReadLength;
    char QNAME[FILENAME_MAX];
    char RNAME[FILENAME_MAX];
    char caRef[READ_BUFFER_SIZE];
    char caRead[READ_BUFFER_SIZE];
    char rawScores[READ_BUFFER_SIZE];  // for calculate
    char QScores[SCORES_BUFFER_SIZE];  // for print
    unsigned int uiGlobalMappedPos, uiRefId, uiPOS;
    unsigned int uiDiff;
    int mismatchScore; //Sum of quality score of mismatches bases, if quality score are available.
    char strand;
    unsigned int MultipleMappedNo;
    char SNPtype; // If not SOLiD read, print a space. If SOLiD read, the SNP type indicated by N, C, V, T
    // Attribute only for SAM
    inline void getReverseReadandQual(void);
    inline void setSingleEndSamFields(void);
    inline void setSingleEndSamFlags(void);
    inline void setPairedEndSamFields(void);
    inline void setPairedEndSamFlags(bool firstRead, char mateStrand);
    // For SAM format, which prints read in ref direction
    char revComRead[READ_BUFFER_SIZE];
    char revQScores[SCORES_BUFFER_SIZE];
    int  FLAG;
    int  MAPQ;
    int  ISIZE;
    unsigned int  uiMPOS;
    char CIGAR[FILENAME_MAX];
    char MRNM[FILENAME_MAX];
    char TAG[FILENAME_MAX];
    bool isColorRead;
private:
    inline void initialization(void);
};

inline void CMappingResult::getReverseReadandQual(void)
{
    // if (strand == '-')
    {
        strcpy(revComRead, caRead);
        strcpy(revQScores, QScores);
        reverseComplementKmer(revComRead);
        reverseKmer(revQScores);
    }
}

inline void CMappingResult::setSingleEndSamFlags(void)
{
    this->FLAG = 0;
    if (this->strand == '-') {
        this->FLAG += 0x10;
    }
}

inline void CMappingResult::setPairedEndSamFields(void)
{
    // Assume flag, uiMPOS, and ISIZE are set outside
    this->MAPQ = 255;
    this->MRNM[0] = '=';
    this->MRNM[1] = '\0';
    sprintf(this->CIGAR, "%uM", this->uiReadLength);
    if (this->TAG[0] != '\0' && this->TAG[0] != 'N') {
        sprintf(this->TAG, "NM:i:%d\t%s", this->uiDiff, string(this->TAG).c_str());
    } else {
        sprintf(this->TAG, "NM:i:%d", this->uiDiff);
    }
}

inline void CMappingResult::setSingleEndSamFields(void)
{
    this->setSingleEndSamFlags(); // Single strang + -, 64, 80
    this->MAPQ = 255;
    this->MRNM[0] = '*';
    this->MRNM[1] = '\0';
    this->uiMPOS = 0;
    this->ISIZE = 0;
    sprintf(this->CIGAR, "%uM", this->uiReadLength);
    sprintf(this->TAG, "NM:i:%d", this->uiDiff);
}

inline int setFlag(int flags, int digit, bool value)
{
    int flag = (0x01 << digit);
    if (value) {
        return( flags | flag);
    } else {
        return( flags & (~flag));
    }
}

inline string getSamRG(const char* readsName, bool bSOLiD)
{
    char RG[MAX_LINE];
    const char* platform = bSOLiD ? "SOLiD" : "Illumina";
    sprintf(RG, "@RG\tID:%s\tSM:%s\tPU:%s ", readsName, readsName, platform);
    return(string(RG));
}

inline void setSamFlags(CMappingResult &m1, CMappingResult &m2, int m1No, int m2No, bool bInRange)
{
    // potential error if one of the read is not mapped however the strand flag is still set
    int flag = 1 + 0x02*(1-(int)bInRange);
    m1.FLAG = flag + 0x04*((int)(m1No == 0)) + 0x08*((int)(m2No == 0)) + 0x40;
    m1.FLAG += (int)(m1.strand == '-') * 0x10 + (int)(m2.strand == '-') * 0x20;
    m2.FLAG = flag + 0x04*((int)(m2No == 0)) + 0x08*((int)(m1No == 0)) + 0x80;
    m2.FLAG += (int)(m2.strand == '-') * 0x10 + (int)(m1.strand == '-') * 0x20;
}

inline void resetStrandSamFlag(CMappingResult &m)
{
    const int strandQDigit = 4;
    m.FLAG = setFlag(m.FLAG, strandQDigit, m.strand == '-');
}

inline void resetStrandSamFlags(CMappingResult &m1, CMappingResult &m2)
{
    const int strandQDigit = 4;
    const int strandMDigit = 5;
    m1.FLAG = setFlag(m1.FLAG, strandQDigit, m1.strand == '-');
    m2.FLAG = setFlag(m2.FLAG, strandQDigit, m2.strand == '-');
    m1.FLAG = setFlag(m1.FLAG, strandMDigit, m2.strand == '-');
    m2.FLAG = setFlag(m2.FLAG, strandMDigit, m1.strand == '-');
}

inline void setSamFlags4OnlyOneEndMapped(CMappingResult &m, bool firstRead)
{
    int flag = 0x01;
    m.FLAG = flag + 0x08 + (int)(m.strand == '-') * 0x40;
    if (firstRead) {
        m.FLAG += 0x40;
    } else {
        m.FLAG += 0x80;
    }
}

inline void printAMappingInSam(FileOutputBuffer* AlignResult, CMappingResult& m)
{
    char* SEQ = (m.strand == '+') ? m.caRead : m.revComRead;
    char* QUAL = (m.strand == '+') ? m.QScores : m.revQScores;
    resetStrandSamFlag(m);
    // fake quality score
    if (QUAL[0] == '\0') {
        memset(QUAL, 'I', sizeof(char) * m.uiReadLength);
    }
    QUAL[m.uiReadLength] = '\0';
    unsigned int uiMPOS = (m.MRNM[0] == '*' ? 0 : m.uiMPOS + 1);
    sprintf(AlignResult->caBufp, "%s\t%d\t%s\t%u\t%d\t%s\t%s\t%u\t%d\t%s\t%s\t%s\n",\
            m.QNAME, m.FLAG, m.RNAME, m.uiPOS + 1, m.MAPQ, m.CIGAR,\
            m.MRNM, uiMPOS, m.ISIZE, SEQ, QUAL, m.TAG);
    AlignResult->UpdateSize();
}

// TODO the quality score is not handled
inline void printAMappingInFastq(FileOutputBuffer* AlignResult, CMappingResult& m)
{
    char readInColor[FILENAME_MAX];
    char colorReadQ[FILENAME_MAX];
    // fake quality score
    if (m.QScores[0] == '\0') {
        if (m.isColorRead) {
            memset(colorReadQ, '!', sizeof(char) * m.uiReadLength);
        } else {
            memset(m.QScores, 'I', sizeof(char) * m.uiReadLength);
        }
    }
    // TODO: assign m.isColorRead when maaping
    // TODO: if it is color read, make color read output
    const char* SEQ;
    const char* QUAL;
    if (m.isColorRead) {
        // TODO: Move that so Mapping Result is undependent to that
        basesStr2ColorStr(m.caRead, readInColor);
        SEQ = readInColor;
        QUAL = colorReadQ;
    } else {
        SEQ = m.caRead;
        QUAL = m.QScores;
    }

    m.QScores[m.uiReadLength] = '\0';
    sprintf(AlignResult->caBufp, "@%s\n%s\n+\n%s\n", m.QNAME, SEQ, QUAL);
    AlignResult->UpdateSize();
}

// Assume both end are mapped to a same reference
inline bool printAPairedMappingInSam\
(FileOutputBuffer* AlignResult, CMappingResult &m1, CMappingResult &m2)
{
    const bool bMapped = true;
    setSamFlags(m1, m2, (int)(bMapped), (int)(bMapped), bMapped);
    m1.setPairedEndSamFields();
    m2.setPairedEndSamFields();
    m1.uiMPOS = m2.uiPOS;
    m2.uiMPOS = m1.uiPOS;
    strcpy(m2.QNAME, m1.QNAME); // sam format needs a same query name for a pair.
    printAMappingInSam(AlignResult, m1);
    printAMappingInSam(AlignResult, m2);
    return(true);
}

inline void printAMappingInPerM(FileOutputBuffer* AlignResult, CMappingResult& m, bool bPrintNM)
{
    bool bPrintNoOfMis = (bPrintNM || m.QScores[0] == '\0');
    int mis = (bPrintNoOfMis ? (int)m.uiDiff : m.mismatchScore);

    if (m.QScores[0] == '\0') {
        sprintf(AlignResult->caBufp, "%s\t%s\t%s\t%d\t%s\t%c\t%d\t%u\t%s\n",\
                m.QNAME, m.caRead, m.RNAME, m.uiPOS, m.caRef,\
                m.strand, mis, m.MultipleMappedNo,"*");
    } else {
        sprintf(AlignResult->caBufp, "%s\t%s\t%s\t%d\t%s\t%c\t%d\t%u\t%s\n",\
                m.QNAME, m.caRead, m.RNAME, m.uiPOS, m.caRef,\
                m.strand, mis, m.MultipleMappedNo, m.QScores);
    }
    AlignResult->UpdateSize();
}

inline string getCategory(char strand1, char strand2, int ISIZE, int disLB, int disUB)
{
    char category[4];
    category[3] = '\0';
    category[0] = (strand1 == strand2) ? 'A' : 'B';
    category[1] = (strand1 == '-') ? 'B' : 'A';
    if (ISIZE > disUB) {
        category[2] = 'B';
    } else if (ISIZE < disLB) {
        category[2] = 'C';
    } else {
        category[2] = 'A';
    }
    return(string(category));
}

inline bool hasQScores(CMappingResult &m)
{
    // TODO Has a better judgement
    return(m.QScores[0] != '\0');
}
//
inline bool printAPairedMappingInPerM\
(FileOutputBuffer* AlignResult, CMappingResult &m1, CMappingResult &m2, int mappedPairedNo, bool bPrintNM = false)
{
    bool bPrintNoOfMis = (bPrintNM || !hasQScores(m1) || !hasQScores(m2));
    int mis1 = (bPrintNoOfMis ? (int)m1.uiDiff : m1.mismatchScore);
    int mis2 = (bPrintNoOfMis ? (int)m2.uiDiff : m2.mismatchScore);
    int totalMis = mis1 + mis2;

    if(m1.caRef[0] == '\0' || m2.caRef[0] == '\0') {
        if(m1.QScores == '\0' || m2.QScores == '\0') {
            sprintf(AlignResult->caBufp, "%s\t%s\t%s\t%d\t%d\t%d\t%s\t%s\t%u\t%u\t%c\t%c\t%d\n",
                    m1.QNAME, m1.caRead, m2.caRead, mis1, mis2,
                    totalMis, m1.RNAME, m2.RNAME, m1.uiPOS, m2.uiPOS, m1.strand, m2.strand, mappedPairedNo);
        } else {
            sprintf(AlignResult->caBufp, "%s\t%s\t%s\t%s\t%s\t%d\t%d\t%d\t%s\t%s\t%u\t%u\t%c\t%c\t%d\n",
                    m1.QNAME, m1.caRead, m2.caRead, m1.QScores, m2.QScores, mis1, mis2,
                    totalMis, m1.RNAME, m2.RNAME, m1.uiPOS, m2.uiPOS, m1.strand, m2.strand, mappedPairedNo);
        }
    } else {
        if(m1.QScores == '\0' || m2.QScores == '\0') {
            sprintf(AlignResult->caBufp, "%s\t%s\t%s\t%d\t%d\t%d\t%s\t%s\t%u\t%u\t%c\t%c\t%d\t%s\t%s\n",
                    m1.QNAME, m1.caRead, m2.caRead, mis1, mis2,
                    totalMis, m1.RNAME, m2.RNAME, m1.uiPOS, m2.uiPOS, m1.strand, m2.strand, mappedPairedNo, m1.caRef, m2.caRef);
        } else {
            sprintf(AlignResult->caBufp, "%s\t%s\t%s\t%s\t%s\t%d\t%d\t%d\t%s\t%s\t%u\t%u\t%c\t%c\t%d\t%s\t%s\n",
                    m1.QNAME, m1.caRead, m2.caRead, m1.QScores, m2.QScores, mis1, mis2,
                    totalMis, m1.RNAME, m2.RNAME, m1.uiPOS, m2.uiPOS, m1.strand, m2.strand, mappedPairedNo, m1.caRef, m2.caRef);
        }
    }
    AlignResult->UpdateSize();
    return(true);
}

inline void printAMappingInGff(FileOutputBuffer* AlignResult, CMappingResult& m,\
                               int score, string& rTag)
{
    sprintf(AlignResult->caBufp, "%s\tsolid\tread\t%d\t%d\t%d\t%c\t.\tg=%s;q=%s;r=%s",
            m.QNAME, m.uiPOS + 1, m.uiPOS + m.uiReadLength, score, m.strand,\
            m.caRead, m.QScores, rTag.c_str());
    AlignResult->UpdateSize();
}
