/*
 * File:   FastaParser.cpp
 * Author: yangxi
 *
 * Created on December 19, 2012, 11:24 AM
 */

#include "htio2/FastaIO.h"
#include "htio2/SimpleSeq.h"
#include "htio2/Error.h"
#include "htio2/FileHandle.h"
#include "htio2/FastqSeq.h"

using namespace std;

namespace htio2
{

FastaIO::FastaIO(const std::string& filename, IOMode mode, CompressFormat comp) : SeqIO(filename, mode, comp)
{
    init_buffer();
}

FastaIO::FastaIO(FileHandle* handle) : SeqIO(handle)
{
    init_buffer();
}

FastaIO::FastaIO(FILE* handle, bool auto_close) : SeqIO(handle, auto_close)
{
    init_buffer();
}

FastaIO::FastaIO(gzFile handle, bool auto_close) : SeqIO(handle, auto_close)
{
    init_buffer();
}

FastaIO::~FastaIO()
{
}

void FastaIO::seek(off_t offset, int whence)
{
    if (whence == SEEK_SET)
    {
        handle_offset = offset;
        handle->seek(offset, SEEK_SET);
    }
    else if (whence == SEEK_CUR)
    {
        handle->seek(handle_offset, SEEK_SET);
        handle->seek(offset, SEEK_CUR);
        handle_offset += offset;
    }
    else if (whence == SEEK_END)
    {
        handle->seek(0, SEEK_END);
        handle_offset = handle->tell() + offset;
        handle->seek(offset, SEEK_END);
    }
    else
        throw runtime_error("invalid seek whence");

    handle->read_line(buffer);
}

off_t FastaIO::tell() const
{
    return handle_offset;
}

bool FastaIO::next_seq(SimpleSeq& result)
{
    result.id.clear();
    result.desc.clear();
    result.seq.clear();

    //
    // parse header line
    // the header line should already be read to buffer
    //
    if (!buffer.length()) return false;

    // find the position of first space
    size_t i_id_end = buffer.find_first_of(" \t");

    if (i_id_end != string::npos)
    {
        // assign ID
        result.id.assign(buffer, 1, i_id_end - 1);

        // find the position of desc
        size_t i_desc_begin = buffer.find_first_not_of(" \t", i_id_end);
        if (i_desc_begin != string::npos)
        {
            result.desc.assign(buffer, i_desc_begin, string::npos);
        }
    }
    else
    {
        // no space, the whole line after ">" is ID
        result.id.assign(buffer, 1, string::npos);
    }

    //
    // get sequence
    //
    while (1)
    {
        // read a line
        handle_offset = handle->tell();
        if (!handle->read_line(buffer)) break;

        // break if we met next record
        if (buffer[0] == '>')
        {
            // ensure the position is at the start of header line
            break;
        }

        // store current line to sequence, removing spaces and tabs
        const size_t buffer_len = buffer.length();
        for (size_t i = 0; i < buffer_len; i++)
        {
            switch (buffer[i])
            {
            case ' ':
            case '\t':
                break;
            default:
                result.seq.push_back(buffer[i]);
            }
        }
    }

    return true;
}

bool FastaIO::next_seq(FastqSeq& result)
{
    bool re = this->next_seq(static_cast<SimpleSeq&> (result));
    result.quality.resize(result.seq.length(), 33 + 40);
    return re;
}

void FastaIO::write_seq(const SimpleSeq& seq)
{
    handle->write_line(string(">") + seq.id + " " + seq.desc);
    for (int i = 0; i < seq.seq.length(); i += 60)
        handle->write_line(seq.seq.substr(i, 60));
    handle_offset = handle->tell();
}

void FastaIO::write_seq(const FastqSeq& seq)
{
    write_seq(static_cast<const SimpleSeq&> (seq));
}

void FastaIO::init_buffer()
{
    while (1)
    {
        handle_offset = handle->tell();
        if (!handle->read_line(buffer)) break;

        if (buffer.length())
        {
            if (buffer[0] == '>')
                break;
            else
                throw InvalidFileContent(buffer, handle->tell());
        }
    }
}

} //namespace htio2
