#ifndef SHASTA_SHORT_BASE_SEQUENCE_HPP
#define SHASTA_SHORT_BASE_SEQUENCE_HPP

// shasta.
#include "Base.hpp"
#include "bitReversal.hpp"
#include "IntegerBySize.hpp"

// Standard library.
#include "array.hpp"
#include <concepts>
#include "iostream.hpp"
#include <limits>
#include "stdexcept.hpp"
#include "string.hpp"



namespace shasta {

    // A short sequence of bases.
    // Uses only two integers, so its capacity is limited
    // by the length of the integers used.
    // This class does not keep track of the number of bases
    // actually stored. All unused positions are left set at "A".
    template<class Int> requires std::unsigned_integral<Int> class ShortBaseSequence;
    using ShortBaseSequence8 = ShortBaseSequence<uint8_t>;
    using ShortBaseSequence16 = ShortBaseSequence<uint16_t>;
    using ShortBaseSequence32 = ShortBaseSequence<uint32_t>;
    using ShortBaseSequence64 = ShortBaseSequence<uint64_t>;
    template<class Int> inline ostream& operator<<(ostream&, const ShortBaseSequence<Int>&);

    void testShortBaseSequence();
}



// A short sequence of bases.
// Uses only two integers, so its capacity is limited
// by the length of the integers used.
// Position 0: the LSB bit of the bases (with base 0 corresponding to the MSB bit).
// Position 1: the MSB bit of the bases (with base 0 corresponding to the MSB bit).
// This class does not keep track of the number of bases
// actually stored. All unused positions are left set at "A".
template<class Int> requires std::unsigned_integral<Int> class shasta::ShortBaseSequence {
public:

    // The number of bases that can be represented equals the number of bits
    // in the Int type.
    static const size_t capacity = std::numeric_limits<Int>::digits;
    static const size_t capacityMinus1 = capacity - 1ULL;

    // The constructor fills the data with 0, which corresponds to all A's.
    ShortBaseSequence()
    {
        std::fill(data.begin(), data.end(), Int(0));
    }

    // Return the base at a given position.
    Base operator[](uint64_t i) const
    {
        const uint64_t bitIndex = capacityMinus1 - (i & capacityMinus1);
        const uint64_t bit0 = (data[0] >> bitIndex) & 1ULL;
        const uint64_t bit1 = (data[1] >> bitIndex) & 1ULL;
        const uint8_t value = uint8_t((bit1 << 1ULL) + bit0);
        return Base::fromInteger(value);
    }

    // Set the base at a given position.
    void set(uint64_t i, Base base) {
        const uint64_t bitIndex = capacityMinus1 - (i & capacityMinus1);
        const Int mask = Int(1ULL << bitIndex);
        const Int maskComplement = Int(~mask);

        const uint64_t bit0 = (base.value) & 1ULL;
        if(bit0 == 0) {
            data[0] &= maskComplement;
        } else {
            data[0] |= mask;
        }

        const uint64_t bit1 = (base.value >> 1ULL) & 1ULL;
        if(bit1 == 0) {
            data[1] &= maskComplement;
        } else {
            data[1] |= mask;
        }

    }

    // Return an integer consisting of the concatenation
    // of the base bits corresponding to the first n bases.
    using Int2 = UintBySize<2 * sizeof(Int)>::type;
    Int2 id(uint64_t n) const
    {
        const uint64_t shift = capacity - n;
        const Int2 lsb = data[0] >> shift;
        const Int2 msb = data[1] >> shift;
        return (msb << n) | lsb;
    }

    // Opposite of the above: construct the sequence given the id.
    ShortBaseSequence(Int2 id, uint64_t n)
    {
        const Int2 mask = (Int2(1) << n) - Int2(1);
        const uint64_t shift = capacity - n;
        data[0] = Int((id & mask) << shift);
        data[1] = Int(((id >> n) & mask) << shift);
    }

    // Return the reverse complement of the first n bases.
    ShortBaseSequence<Int> reverseComplementSlow(uint64_t n) const
    {
        ShortBaseSequence<Int> reverseComplementedSequence;
        for(size_t i=0; i<n; i++) {
            const Base b = (*this)[i].complement();
            reverseComplementedSequence.set(n-i-1, b);
        }
        return reverseComplementedSequence;
    }



    // Return the reverse complement of the first n bases.
    // Use bit reversal for speed. This avoids a loop over the n bases.
    ShortBaseSequence<Int> reverseComplement(uint64_t n) const
    {
        const Int shift = Int(capacity - n);
        const Int mask = Int(1ULL << n) - Int(1);
        ShortBaseSequence<Int> reverseComplementedSequence;
        reverseComplementedSequence.data[0] = Int(((~bitReversal(data[0])) & mask) << shift);
        reverseComplementedSequence.data[1] = Int(((~bitReversal(data[1])) & mask) << shift);

#if 0
        // Testing.
        SHASTA_ASSERT(reverseComplementedSequence == reverseComplementSlow(n));
        SHASTA_ASSERT(reverseComplementedSequence.reverseComplementSlow(n) == *this);
#endif

        return reverseComplementedSequence;
    }



    bool operator==(const ShortBaseSequence<Int>& that) const
    {
        return data == that.data;
    }

    bool operator<(const ShortBaseSequence<Int>& that) const
    {
        return data < that.data;
    }

    // Write the first n bases.
    ostream& write(ostream& s, uint64_t n) const
    {
        for(uint64_t i=0; i<n; i++) {
            s << (*this)[i];
        }
        return s;
    }

    void shiftLeft() {
        data[0] = Int(data[0] << 1);
        data[1] = Int(data[1] << 1);
    }

    // The data are left public to facilitate low level custom code.
    array<Int, 2> data;
};



template<class Int> inline std::ostream& shasta::operator<<(
    std::ostream& s,
    const shasta::ShortBaseSequence<Int>& sequence)
{
    for(size_t i=0; i<sequence.capacity; i++) {
        s << sequence[i];
    }
    return s;
}



#endif
