#include <pbdata/Types.h>
#include <pbdata/DNASequence.hpp>

#include <cassert>
#include <cstring>

DNALength DNASequence::size() { return length; }

void DNASequence::TakeOwnership(DNASequence &rhs)
{
    CheckBeforeCopyOrReference(rhs);
    // Free this DNASequence before take owner ship from rhs.
    DNASequence::Free();

    seq = rhs.seq;
    length = rhs.length;
    deleteOnExit = rhs.deleteOnExit;

    rhs.deleteOnExit = false;
}

void DNASequence::Append(const DNASequence &rhs, DNALength appendPos)
{
    assert(deleteOnExit);  // must have control over seq.
    //
    // Simply append rhs to this seuqence, unless appendPos is nonzero
    // in which case rhs is inserted at attendPos, overwriting this
    // sequence from appendPos to the end.
    //
    Nucleotide *newSeq;
    //
    // Handle the two cases (appendPos == 0 and appendPos > 0)
    // separately in order to handle memory deallocation correctly.
    //
    if (appendPos == 0) {
        DNALength newSeqLength = length + rhs.length;
        newSeq = ProtectedNew<Nucleotide>(newSeqLength);
        memcpy(newSeq, seq, length);
        memcpy(&newSeq[length], rhs.seq, rhs.length);

        if (length != 0) {
            delete[] seq;
        }
        seq = newSeq;
        length = newSeqLength;
    } else {
        if (appendPos + rhs.length < length) {
            memcpy(&seq[appendPos], rhs.seq, rhs.length);
            length = appendPos + rhs.length;
        } else {
            DNALength lengthCopy = length;
            length = appendPos;
            DNALength newSeqLength;
            newSeqLength = length + rhs.length;
            newSeq = ProtectedNew<Nucleotide>(newSeqLength);
            memcpy(newSeq, seq, length);
            memcpy(&newSeq[length], rhs.seq, rhs.length);
            if (deleteOnExit and lengthCopy != 0) {
                delete[] seq;
            }
            seq = newSeq;
            length = newSeqLength;
        }
    }
    deleteOnExit = true;
}

// Copy FROM rhs to this DNASequence.
DNASequence &DNASequence::Copy(const DNASequence &rhs, DNALength rhsPos, DNALength rhsLength)
{
    CheckBeforeCopyOrReference(rhs);
    // Free this DNASequence before copying from rhs
    DNASequence::Free();

    //
    // When initializing a vector of DNASequence's, the copy
    // constructor will initialze a list and call this
    // function with a zero-length DNASequence as the rhs to
    // initialize every element in the vector   The check
    // below will fail on zero-length sequences, so add a boundary
    // condition check before that to allow the copy-constructor to
    // work.
    //
    if (rhs.length == 0) {
        seq = NULL;
        length = 0;
        deleteOnExit = true;
        return *this;
    }

    //
    // Silently ignoring this case could lead to problems later on,
    // catastrophically assert here if the input is not valid.
    // In case rhsLength + rhsPos > ULONG_MAX (4294967295), check
    // both rhsLength and rhsPos, fix bug 21794
    //
    if (not(rhsLength <= rhs.length && rhsPos <= rhs.length + 1 &&
            rhsLength + rhsPos <= rhs.length + 2)) {
        std::cout << "ERROR.  The subsequence to copy is out of bounds." << std::endl
                  << "        Failed to copy a subsequence starting at " << rhsPos << std::endl
                  << "        with length " << rhsLength << " from a sequence of length "
                  << rhs.length << "." << std::endl;
        std::exit(EXIT_FAILURE);
    }

    if (rhsLength == 0) {
        rhsLength = rhs.length - rhsPos;
    }
    if (rhsLength == 0) {
        seq = NULL;
    } else {
        seq = ProtectedNew<Nucleotide>(rhsLength);
        memcpy(seq, &rhs.seq[rhsPos], rhsLength);
    }
    length = rhsLength;
    deleteOnExit = true;
    return *this;
}

DNASequence &DNASequence::Copy(const std::string &rhsSeq)
{
    // Free this DNASequence before copying from rhs
    DNASequence::Allocate(static_cast<DNALength>(rhsSeq.size()));
    memcpy(seq, rhsSeq.c_str(), length);
    return *this;
}

DNASequence &DNASequence::operator=(const std::string &rhsSeq) { return DNASequence::Copy(rhsSeq); }

void DNASequence::ShallowCopy(const DNASequence &rhs)
{
    seq = rhs.seq;
    length = rhs.length;
    deleteOnExit = false;
}

int DNASequence::GetStorageSize() const { return (length * sizeof(Nucleotide)); }

DNASequence &DNASequence::operator=(const DNASequence &rhs)
{
    DNASequence::Copy(rhs);
    return *this;
}

//
// synonym for printseq
//
void DNASequence::Print(std::ostream &out, int lineLength) const { PrintSeq(out, lineLength); }

std::string DNASequence::ToString(const int lineLength) const
{
    std::string line;
    if (lineLength == 0) {
        line.assign((char *)seq, length);
    } else {
        assert(lineLength > 0);
        const char sep = '\n';
        for (size_t pos = 0; pos < length; pos++) {
            line.push_back(seq[pos]);
            if (pos != length - 1 and (pos + 1) % lineLength == 0) {
                line.push_back(sep);
            }
        }
    }
    return line;
}

DNASequence &DNASequence::ReverseComplementSelf(void)
{
    if (deleteOnExit) {
        for (DNALength i = 0; i < length / 2 + length % 2; i++) {
            char c = seq[i];
            seq[i] = ReverseComplementNuc[seq[length - i - 1]];
            seq[length - i - 1] = ReverseComplementNuc[static_cast<int>(c)];
        }
    } else {
        const DNALength l = length;
        Nucleotide *newSeq = ProtectedNew<Nucleotide>(l);
        for (DNALength i = 0; i < l; i++) {
            newSeq[i] = ReverseComplementNuc[seq[length - i - 1]];
        }
        seq = newSeq;
        length = l;
        newSeq = nullptr;
        deleteOnExit = true;
    }
    return *this;
}

void DNASequence::PrintSeq(std::ostream &out, int lineLength) const
{
    if (lineLength == 0) {
        std::string line;
        line.assign((char *)seq, length);
        out << line;
    } else {
        //
        // Make sure this isn't
        assert(lineLength > 0);
        DNALength curPos = 0;
        int curLineLength = lineLength;
        while (curPos < length) {
            if (curPos + curLineLength > length) {
                curLineLength = length - curPos;
            }
            std::string line;
            line.assign((char *)&seq[curPos], curLineLength);
            out << line << std::endl;
            curPos += curLineLength;
        }
    }
}

void DNASequence::Allocate(DNALength plength)
{
    DNASequence::Free();
    seq = ProtectedNew<Nucleotide>(plength);
    length = plength;
    deleteOnExit = true;
}

void DNASequence::ReferenceSubstring(const DNASequence &rhs, DNALength pos, DNALength substrLength)
{
    CheckBeforeCopyOrReference(rhs);

    // Free this DNASequence before referencing rhs.
    DNASequence::Free();

    //
    // This makes a reference therefore it should not be deleted.
    //
    assert(pos <= rhs.length && substrLength <= rhs.length);
    if (substrLength == 0) {
        substrLength = rhs.length - pos;
    }
    assert(pos + substrLength <= rhs.length);
    seq = &rhs.seq[pos];
    length = substrLength;
    deleteOnExit = false;
}

DNALength DNASequence::MakeRCCoordinate(DNALength forPos) { return length - forPos - 1; }

// Create a reverse complement DNAsequence of this DNASequence and assign to rc.
void DNASequence::MakeRC(DNASequence &rc, DNALength pos, DNALength rcLength)
{
    if (rcLength == 0) {
        rcLength = length - pos;
    }

    ((DNASequence &)rc).Allocate(rcLength);
    DNALength i;
    for (i = 0; i < rcLength; i++) {
        rc.seq[rcLength - i - 1] = ReverseComplementNuc[seq[i + pos]];
    }
    rc.length = rcLength;
    rc.deleteOnExit = true;
}

void DNASequence::ToTwoBit()
{
    DNALength i;
    for (i = 0; i < length; i++) {
        seq[i] = TwoBit[seq[i]];
    }
    bitsPerNuc = 2;
}

void DNASequence::ToFourBit()
{
    DNALength i;
    if (bitsPerNuc != 4)
        for (i = 0; i < length; i++) {
            seq[i] = FourBit[seq[i]];
        }
    bitsPerNuc = 4;
}

void DNASequence::ConvertThreeBitToAscii()
{
    DNALength i;
    for (i = 0; i < length; i++) {
        seq[i] = ThreeBitToAscii[seq[i]];
    }
}

void DNASequence::ToAscii()
{
    DNALength i;
    if (bitsPerNuc != 8) {
        for (i = 0; i < length; i++) {
            seq[i] = FourBitToAscii[seq[i]];
        }
        bitsPerNuc = 8;
    }
}

// Copy rhs[start:start+plength] to this DNASequence.
void DNASequence::Assign(DNASequence &ref, DNALength start, DNALength plength)
{
    CheckBeforeCopyOrReference(ref);
    // Free this DNASequence before assigning anything
    DNASequence::Free();

    if (plength) {
        length = plength;
        seq = ProtectedNew<Nucleotide>(length);
        memcpy(seq, &ref.seq[start], length);
    } else if (start) {
        length = ref.length - start;
        seq = ProtectedNew<Nucleotide>(length);
        memcpy(seq, &ref.seq[start], length);
    } else {
        ((DNASequence *)this)->Copy(ref);
    }
    deleteOnExit = true;
}

void DNASequence::ToLower()
{
    DNALength i;
    for (i = 0; i < length; i++) {
        seq[i] = AllToLower[seq[i]];
    }
}

void DNASequence::ToUpper()
{
    DNALength i;
    for (i = 0; i < length; i++) {
        seq[i] = AllToUpper[seq[i]];
    }
}

void DNASequence::Concatenate(const Nucleotide *moreSeq, DNALength moreSeqLength)
{
    DNALength prevLength = length;
    length += moreSeqLength;
    Nucleotide *prev = seq;
    seq = ProtectedNew<Nucleotide>(length);
    if (prev != NULL) {
        memcpy(seq, prev, prevLength);
        delete[] prev;
    }
    memcpy((Nucleotide *)&seq[prevLength], moreSeq, moreSeqLength);
    deleteOnExit = true;
}

std::string DNASequence::GetTitle() const { return std::string(""); }

void DNASequence::Concatenate(const Nucleotide *moreSeq)
{
    DNALength moreSeqLength = strlen((char *)moreSeq);
    Concatenate(moreSeq, moreSeqLength);
}

void DNASequence::Concatenate(DNASequence &seq) { Concatenate(seq.seq, seq.length); }

int DNASequence::Compare(DNALength pos, DNASequence &rhs, DNALength rhsPos, DNALength length)
{
    return memcmp(&seq[pos], &rhs.seq[rhsPos], length);
}

int DNASequence::LessThanEqual(DNALength pos, DNASequence &rhs, DNALength rhsPos, DNALength length)
{
    int res = Compare(pos, rhs, rhsPos, length);
    if (res <= 0)
        return 1;
    else
        return 0;
}

int DNASequence::Equals(DNASequence &rhs, DNALength rhsPos, DNALength length, DNALength pos)
{
    int res = Compare(pos, rhs, rhsPos, length);
    return res == 0;
}

int DNASequence::LessThan(DNALength pos, DNASequence &rhs, DNALength rhsPos, DNALength length)
{
    int res = Compare(pos, rhs, rhsPos, length);
    return (res < 0);
}

void DNASequence::CleanupASCII()
{
    DNALength i;
    for (i = 0; i < length; i++) {
        if (ThreeBit[seq[i]] == 255) {
            seq[i] = 'N';
        }
    }
}

Nucleotide DNASequence::GetNuc(DNALength i) const { return seq[i]; }

DNALength DNASequence::GetRepeatContent() const
{
    DNALength i;
    DNALength nRepeat = 0;
    for (i = 0; i < length; i++) {
        if (tolower(seq[i]) == seq[i]) {
            nRepeat++;
        }
    }
    return nRepeat;
}

void DNASequence::CleanupOnFree() { deleteOnExit = true; }

void DNASequence::Free()
{
    if (deleteOnExit == true) {
        // if has control, delete seq
        // Otherwise, seq in memory is controlled by another object,
        // and will be deleted later.
        if (seq != NULL) {
            delete[] seq;
        }
    }
    // Reset seq, length and deleteOnExit
    seq = NULL;
    length = 0;
    deleteOnExit = false;
}

void DNASequence::Resize(DNALength newLength)
{
    DNASequence::Free();
    seq = ProtectedNew<Nucleotide>(newLength);
    length = newLength;
    deleteOnExit = true;
}

DNALength DNASequence::GetSeqStorage() const { return length; }

#ifdef USE_PBBAM
DNASequence &DNASequence::Copy(const PacBio::BAM::BamRecord &record)
{
    return DNASequence::Copy(record.Sequence());
}
#endif
