//     HTIO - a high-throughput sequencing reads input/output library
//
//     This program is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
//
//     This program is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
//
//     You should have received a copy of the GNU General Public License
//     along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "htio2/HeaderUtil.h"
#include "htio2/Cast.h"
#include "htio2/FastqIO.h"
#include "htio2/SimpleSeq.h"
#include "htio2/FastqSeq.h"
#include "htio2/Error.h"

#include "JUCE-3.0.8/JuceHeader.h"

using namespace std;
using namespace htio2::juce;

namespace htio2
{

void guess_header_format(const std::string& header,
                         HeaderFormat& format,
                         bool& with_ncbi_additive)
{
//    printf("header: %s\n", header.c_str());
    string header_use;
    if (header[0] == '@') header_use.assign(header, 1, string::npos);
    else header_use = header;

    StringArray fields;
    fields.addTokens(String(header_use), " \t");

    // check the existance of NCBI additive part
    if (fields.size() >= 2 &&
            fields.strings.getFirst().startsWith("SRR") &&
            fields.strings.getLast().startsWith("length="))
    {
//        printf("  with NCBI\n");
        with_ncbi_additive = true;
        fields.remove(0);
        fields.remove(fields.size()-1);
    }
    else
    {
        with_ncbi_additive = false;
    }

    // check header format
    if (fields.size() >= 1)
    {
//        printf("  check %s\n", fields[0].toRawUTF8());
        StringArray tokens_f1;
        tokens_f1.addTokens(fields[0], ":", "");
//        printf("  %d tokens\n", tokens_f1.size());

        int lane = -1;
        int tile = -1;
        int x = -1;
        int y = -1;

        if (tokens_f1.size() == 5 &&
                from_string<int>(tokens_f1[1].toStdString(), lane) &&
                from_string<int>(tokens_f1[2].toStdString(), tile) &&
                from_string<int>(tokens_f1[3].toStdString(), x) &&
                from_string<int>(tokens_f1[4].toStdString(), y))
        {
            format = HEADER_PRI_1_8;
        }
        else if (tokens_f1.size() == 7 &&
                 from_string<int>(tokens_f1[3].toStdString(), lane) &&
                 from_string<int>(tokens_f1[4].toStdString(), tile) &&
                 from_string<int>(tokens_f1[5].toStdString(), x) &&
                 from_string<int>(tokens_f1[6].toStdString(), y))
        {
            format = HEADER_1_8;
        }
        else
        {
            format = HEADER_UNKNOWN;
        }
    }
    else
    {
        format = HEADER_UNKNOWN;
    }
}

size_t guess_header_format(FastqIO& stream, size_t num_to_guess, HeaderFormat& format, bool& with_ncbi_additive)
{
    FastqSeq seq;
    HeaderFormat tmp_format = HEADER_UNKNOWN;
    bool tmp_ncbi_flag = false;

    size_t num_seq = 0;
    off_t orig_offset = stream.tell();

    for (; num_seq < num_to_guess; num_seq++)
    {
        if (!stream.next_seq(seq)) break;
        HeaderFormat curr_format;
        bool curr_ncbi_flag;
        guess_header_format(seq.id + " " + seq.desc, curr_format, curr_ncbi_flag);

        if (tmp_format == HEADER_UNKNOWN)
        {
            tmp_format = curr_format;
            tmp_ncbi_flag = curr_ncbi_flag;
        }
        else
        {
            if (tmp_format != curr_format || tmp_ncbi_flag != curr_ncbi_flag)
            {
                stream.seek(orig_offset, 0);
                throw runtime_error("FASTQ content has mixed header format");
            }
        }
    }

    stream.seek(orig_offset, 0);
    format = tmp_format;
    with_ncbi_additive = tmp_ncbi_flag;
    return num_seq;
}

HeaderParser::HeaderParser(HeaderFormat h_format, bool with_ncbi_additive)
: format(h_format)
, with_ncbi_additive(with_ncbi_additive)
{
    if (h_format == HEADER_1_8)
        lane_colon_pos = 3;
    else if (h_format == HEADER_PRI_1_8)
        lane_colon_pos = 1;
    else
        lane_colon_pos = -1;
}

void HeaderParser::parse(const string& id, const string& desc)
{
    if (lane_colon_pos == -1)
    {
        lane = 0;
        tile = 0;
        barcode.clear();
        return;
    }

    string id_tidy;
    string desc_tidy;
    if (with_ncbi_additive)
    {
        size_t prefix_end = id.find_first_of(" \t");
        size_t suffix_begin = desc.find_last_of(" \t");
        if (prefix_end == string::npos || suffix_begin == string::npos)
            throw runtime_error("failed to locate NCBI SRA additive in header: " + id + " " + desc);

        size_t tidy_begin = id.find_first_not_of(" \t", prefix_end + 1);
        size_t tidy_end = desc.find_last_not_of(" \t", suffix_begin - 1);

        id_tidy.assign(id, tidy_begin, string::npos);
        desc_tidy.assign(desc, 0, tidy_end + 1);
    }
    else
    {
        id_tidy = id;
        desc_tidy = desc;
    }

    //
    // parse lane and tile
    //
    int32_t lane_i_begin = -1;
    int32_t num_colon = 0;

    for (size_t i = 0; i < id_tidy.size(); i++)
    {
        if (id_tidy[i] == ':') num_colon++;

        if (num_colon == lane_colon_pos)
        {
            lane_i_begin = i + 1;
            break;
        }
    }

    if (sscanf(id_tidy.c_str() + lane_i_begin, "%d:%d:", &lane, &tile) != 2)
        throw runtime_error("failed to parse lane and tile in header: " + id_tidy);

    //
    // parse barcode
    //
    if (format == HEADER_1_8)
    {
        size_t pos = desc_tidy.find_last_of(':');
        barcode.assign(desc_tidy, pos + 1, string::npos);
    }
    else if (format == HEADER_PRI_1_8)
    {
        barcode = "";
    }
    else
    {
        barcode = "";
    }
}

} // namespace htio2
