#include "htio2/QualityUtil.h"
#include "htio2/Error.h"
#include "htio2/FastqIO.h"
#include "htio2/FastqSeq.h"

#include <map>

using namespace std;

namespace htio2
{

QualityEncode guess_quality_encode(char qual_ascii_min, char qual_ascii_max)
{
    if (qual_ascii_min > qual_ascii_max)
        return ENCODE_UNKNOWN;

    if (64 <= qual_ascii_min && qual_ascii_max <= 126)
    {
        // 0 to 62, 64 based
        return ENCODE_ILLUMINA;
    }
    else if (59 <= qual_ascii_min && qual_ascii_max <= 126)
    {
        // -5 to 62, 64 based
        return ENCODE_SOLEXA;
    }
    else if (33 <= qual_ascii_min && qual_ascii_max <= 74)
    {
        // 0 to 41, 33 based
        return ENCODE_CASAVA_1_8;
    }
    else if (33 <= qual_ascii_min && qual_ascii_max <= 126)
    {
        // 0 to 93, 33 based
        return ENCODE_SANGER;
    }
    else
    {
        return ENCODE_UNKNOWN;
    }
}

QualityEncode guess_quality_encode(const std::string& quality)
{
    if (!quality.length()) return ENCODE_UNKNOWN;

    char min = quality[0];
    char max = quality[0];

    for (size_t i = 1; i < quality.length(); i++)
    {
        const char curr = quality[i];
        if (curr < min) min = curr;
        if (max < curr) max = curr;
    }

    return guess_quality_encode(min, max);
}

QualityEncode guess_quality_encode(FastqIO& stream, size_t num_to_guess)
{
    FastqSeq seq;

    char min = 127;
    char max = 0;

    for (size_t i = 0; i < num_to_guess; i++)
    {
        if (!stream.next_seq(seq)) break;
        for (size_t i_qual = 0; i_qual < seq.quality.length(); i_qual++)
        {
            const char qual = seq.quality[i_qual];
            if (qual < min) min = qual;
            if (max < qual) max = qual;
        }
    }

    return guess_quality_encode(min, max);
}

void get_quality_range(QualityEncode encode, int16_t& qual_from, int16_t& qual_to)
{
    if (encode == ENCODE_SANGER)
    {
        qual_from = 0;
        qual_to = 93;
    }
    else if (encode == ENCODE_SOLEXA)
    {
        qual_from = -5;
        qual_to = 62;
    }
    else if (encode == ENCODE_ILLUMINA)
    {
        qual_from = 0;
        qual_to = 62;
    }
    else if (encode == ENCODE_CASAVA_1_8)
    {
        qual_from = 0;
        qual_to = 41;
    }
    else
    {
        throw InvalidQualityEncode(encode);
    }
}

void decode_quality(const std::string& input, std::vector<int16_t>& output, QualityEncode encode)
{
    int16_t offset = get_quality_offset(encode);
    if (offset == -1) throw InvalidQualityEncode(encode);

    const size_t sz = input.size();
    output.resize(sz);
    for (size_t i = 0; i < sz; i++)
        output[i] = input[i] - offset;
}

void encode_quality(const std::vector<int16_t>& input, std::string& output, QualityEncode encode)
{
    int16_t offset = get_quality_offset(encode);
    if (offset == -1) throw InvalidQualityEncode(encode);

    const size_t sz = input.size();
    output.resize(sz);
    for (size_t i = 0; i < sz; i++)
        output[i] = input[i] + offset;
}

void recode_quality(const std::string& input, std::string& output, QualityEncode encode_input, QualityEncode encode_output)
{
    size_t len = input.length();
    vector<int16_t> qual_in(len);
    vector<int16_t> qual_out(len);

    decode_quality(input, qual_in, encode_input);

    for (size_t i = 0; i < len; i++)
    {
        float p = quality_to_probability(qual_in[i], encode_input);
        qual_out[i] = probability_to_quality(p, encode_output);
    }

    encode_quality(qual_out, output, encode_output);
}

} // namespace htio2
