#ifndef _BLASR_ALIGNMENT_SET_TO_CMPH5_ADAPTER_IMPL_HPP_
#define _BLASR_ALIGNMENT_SET_TO_CMPH5_ADAPTER_IMPL_HPP_

template <typename T_CmpFile>
unsigned int AlignmentSetToCmpH5Adapter<T_CmpFile>::StoreMovieInfo(std::string movieName,
                                                                   T_CmpFile &cmpFile)
{

    std::map<std::string, unsigned int>::iterator mapIt;
    mapIt = knownMovies.find(movieName);
    if (mapIt != knownMovies.end()) {
        return mapIt->second;
    } else {
        unsigned int id = cmpFile.movieInfoGroup.AddMovie(movieName);
        knownMovies[movieName] = id;
        return id;
    }
}

template <typename T_CmpFile>
template <typename T_Reference>
void AlignmentSetToCmpH5Adapter<T_CmpFile>::StoreReferenceInfo(std::vector<T_Reference> &references,
                                                               T_CmpFile &cmpFile)
{
    for (size_t r = 0; r < references.size(); r++) {
        std::string sequenceName, md5;
        sequenceName = references[r].GetSequenceName();
        md5 = references[r].GetMD5();
        unsigned int length = references[r].GetLength();

        // Add this reference to /RefInfo.
        // Don't create /ref0000x and register it in /RefGroup at this point,
        // because this reference may not map to any alignments at all.
        cmpFile.AddRefInfo(sequenceName, length, md5);

        // Update refNameToRefInfoIndex
        if (refNameToRefInfoIndex.find(sequenceName) != refNameToRefInfoIndex.end()) {
            std::cout << "ERROR. Reference name " << sequenceName << " is not unique." << std::endl;
            std::exit(EXIT_FAILURE);
        }
        refNameToRefInfoIndex[sequenceName] = r;
    }
}

template <typename T_CmpFile>
unsigned int AlignmentSetToCmpH5Adapter<T_CmpFile>::StoreRefGroup(std::string refName,
                                                                  T_CmpFile &cmpFile)
{
    // Find out whether there is a refGroup associated with refName.
    std::map<std::string, RefGroupNameId>::iterator mapIt;
    mapIt = refNameToRefGroupNameandId.find(refName);
    if (mapIt != refNameToRefGroupNameandId.end()) {
        // An existing refGroup is associated with this refName.
        return mapIt->second.id;
    } else {
        // No refGroup is associated with refName, create one.
        int refInfoIndex = refNameToRefInfoIndex[refName];
        unsigned int refInfoId = refInfoIndex + 1;

        std::string refGroupName;
        unsigned int refGroupId = cmpFile.AddRefGroup(refName, refInfoId, refGroupName);

        // Update refNameToRefGroupNameandId.
        refNameToRefGroupNameandId[refName] = RefGroupNameId(refGroupName, refGroupId);
        return refGroupId;
    }
}

template <typename T_CmpFile>
unsigned int AlignmentSetToCmpH5Adapter<T_CmpFile>::StorePath(std::string &path, T_CmpFile &cmpFile)
{
    if (knownPaths.find(path) != knownPaths.end()) {
        return knownPaths[path];
    } else {
        unsigned int id = cmpFile.alnGroupGroup.AddPath(path);
        knownPaths[path] = id;
        return id;
    }
}

template <typename T_CmpFile>
void AlignmentSetToCmpH5Adapter<T_CmpFile>::RemoveGapsAtEndOfAlignment(
    AlignmentCandidate<> &alignment)
{
    int numEndDel = 0, numEndIns = 0;
    if (alignment.gaps.size() > 0) {
        int lastGap = alignment.gaps.size() - 1;
        for (size_t g = 0; g < alignment.gaps[lastGap].size(); g++) {
            if (alignment.gaps[lastGap][g].seq == Gap::Target) {
                numEndIns += alignment.gaps[lastGap][g].length;
            } else if (alignment.gaps[lastGap][g].seq == Gap::Query) {
                numEndDel += alignment.gaps[lastGap][g].length;
            }
        }
    }
    alignment.qAlignedSeqLength -= numEndIns;
    alignment.tAlignedSeqLength -= numEndDel;
}

template <typename T_CmpFile>
void AlignmentSetToCmpH5Adapter<T_CmpFile>::StoreAlignmentCandidate(AlignmentCandidate<> &alignment,
                                                                    int alnSegment,
                                                                    T_CmpFile &cmpFile,
                                                                    int moleculeNumber,
                                                                    bool copyQVs)
{
    //
    // Find out where the movie is going to get stored.
    //
    std::string movieName;
    int holeNumber = 0;
    bool nameParsedProperly;

    nameParsedProperly = ParsePBIReadName(alignment.qName, movieName, holeNumber);
    if (!nameParsedProperly) {
        std::cout << "ERROR. Attempting to store a read with name " << alignment.qName
                  << " that does not " << std::endl
                  << "appear to be a PacBio read." << std::endl;
        std::exit(EXIT_FAILURE);
    }

    unsigned int movieId = StoreMovieInfo(movieName, cmpFile);

    // Check whether the reference is in /RefInfo.
    std::map<std::string, int>::iterator mapIt;
    mapIt = refNameToRefInfoIndex.find(alignment.tName);
    if (mapIt == refNameToRefInfoIndex.end()) {
        std::cout << "ERROR. The reference name " << alignment.tName
                  << " was not found in the list of references." << std::endl;
        std::cout << "Perhaps a different reference file was aligned to than " << std::endl
                  << "what was provided for SAM conversion. " << std::endl;
        std::exit(EXIT_FAILURE);
    }

    // Store refGroup
    unsigned int refGroupId = StoreRefGroup(alignment.tName, cmpFile);
    std::string refGroupName = refNameToRefGroupNameandId[alignment.tName].name;
    assert(refGroupId == refNameToRefGroupNameandId[alignment.tName].id);

    if (cmpFile.refGroupIdToArrayIndex.find(refGroupId) == cmpFile.refGroupIdToArrayIndex.end()) {
        std::cout << "ERROR. The reference ID is not indexed. "
                  << "This is an internal inconsistency." << std::endl;
        std::exit(EXIT_FAILURE);
    }

    size_t refGroupIndex = cmpFile.refGroupIdToArrayIndex[refGroupId];
    assert(refGroupIndex + 1 == refGroupId);

    std::string path = "/" + refGroupName + "/" + movieName;
    unsigned int pathId = StorePath(path, cmpFile);

    std::vector<unsigned int> alnIndex;
    alnIndex.resize(22);

    RemoveGapsAtEndOfAlignment(alignment);

    /*
    * Store the alignment string
    */
    std::vector<unsigned char> byteAlignment;
    AlignmentToByteAlignment(alignment, alignment.qAlignedSeq, alignment.tAlignedSeq,
                             byteAlignment);

    unsigned int offsetBegin, offsetEnd;
    cmpFile.StoreAlnArray(byteAlignment, alignment.tName, movieName, offsetBegin, offsetEnd);
    // Copy QVs into cmp.h5
    if (copyQVs) {
        std::vector<std::string> optionalQVs;
        alignment.CopyQVs(&optionalQVs);
        for (size_t qv_i = 0; qv_i < optionalQVs.size(); qv_i++) {
            std::string *qvName = &alignment.optionalQVNames[qv_i];
            std::string *qvString = &optionalQVs[qv_i];

            // If the qvString is empty, then the alignment is missing the quality
            // value
            if (qvString->size() == 0) {
                continue;
            }

            unsigned int qvOffsetBegin, qvOffsetEnd;
            if (qvName->compare(qvName->size() - 3, 3, "Tag") == 0) {
                std::vector<char> qvVector;
                QVsToCmpH5QVs(*qvString, byteAlignment, true, &qvVector);
                cmpFile.StoreTags(qvVector, alignment.tName, *qvName, movieName, &qvOffsetBegin,
                                  &qvOffsetEnd);
            } else {
                std::vector<UChar> qvVector;
                QVsToCmpH5QVs(*qvString, byteAlignment, false, &qvVector);
                cmpFile.StoreQVs(qvVector, alignment.tName, *qvName, movieName, &qvOffsetBegin,
                                 &qvOffsetEnd);
            }
            assert(qvOffsetBegin == offsetBegin);
            assert(qvOffsetEnd == offsetEnd);
        }
    }

    numAlignments++;

    DistanceMatrixScoreFunction<DNASequence, DNASequence> distScoreFn;
    //distScoreFn does not matter since the score is not stored.
    ComputeAlignmentStats(alignment, alignment.qAlignedSeq.seq, alignment.tAlignedSeq.seq,
                          distScoreFn);

    /*
    The current AlnIndex column names:
    (0): "AlnID", "AlnGroupID", "MovieID", "RefGroupID", "tStart",
    (5): "tEnd", "RCRefStrand", "HoleNumber", "SetNumber",
    (9): "StrobeNumber", "MoleculeID", "rStart", "rEnd", "MapQV", "nM",
    (15): "nMM", "nIns", "nDel", "Offset_begin", "Offset_end",
    (20): "nBackRead", "nReadOverlap"
  */
    if (moleculeNumber == -1) {
        moleculeNumber = numZMWsPerMovieSpringField * (movieId - 1) + holeNumber;
    }
    alnIndex[0] = numAlignments;                                           // AlnId
    alnIndex[1] = pathId;                                                  // AlnGroupID
    alnIndex[2] = movieId;                                                 // MovieID
    alnIndex[3] = refGroupId;                                              // RefGroupID
    alnIndex[4] = alignment.tAlignedSeqPos;                                // tStart
    alnIndex[5] = alignment.tAlignedSeqPos + alignment.tAlignedSeqLength;  // tEnd
    alnIndex[6] = alignment.tStrand;                                       // RCRefStrand
    alnIndex[7] = holeNumber;
    alnIndex[8] = 0;           // SET NUMBER -- parse later!!!!
    alnIndex[9] = alnSegment;  // strobenumber
    alnIndex[10] = moleculeNumber;
    alnIndex[11] = alignment.qAlignedSeqPos;
    alnIndex[12] = alignment.qAlignedSeqPos + alignment.qAlignedSeqLength;
    alnIndex[13] = alignment.mapQV;
    alnIndex[14] = alignment.nMatch;
    alnIndex[15] = alignment.nMismatch;
    alnIndex[16] = alignment.nIns;
    alnIndex[17] = alignment.nDel;
    alnIndex[18] = offsetBegin;
    alnIndex[19] = offsetEnd;
    alnIndex[20] = 0;
    alnIndex[21] = 0;
    cmpFile.alnInfoGroup.WriteAlnIndex(alnIndex);
}

template <typename T_CmpFile>
void AlignmentSetToCmpH5Adapter<T_CmpFile>::StoreAlignmentCandidateList(
    std::vector<AlignmentCandidate<> > &alignments, T_CmpFile &cmpFile, int moleculeNumber,
    bool copyQVs)
{
    for (size_t a = 0; a < alignments.size(); a++) {
        StoreAlignmentCandidate(alignments[a], a, cmpFile, moleculeNumber, copyQVs);
    }
}

#endif
