/*
 * nvbio
 * Copyright (c) 2011-2014, NVIDIA CORPORATION. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * Neither the name of the NVIDIA CORPORATION nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

namespace nvbio {

// constructor
//
inline
FASTA_inc_reader::FASTA_inc_reader(const char* filename, const uint32 buffer_size)
    : m_buffer( buffer_size ), m_buffer_size( 0 ), m_buffer_pos( 0 )
{
    m_file = gzopen( filename, "r" );
    if (m_file)
        gzbuffer( m_file, buffer_size );
}
// destructor
//
inline
FASTA_inc_reader::~FASTA_inc_reader()
{
    if (m_file)
        gzclose( m_file );
}

// read a batch of bp reads
//
// \tparam Writer          an output handler class, which must
//                         implement the following interface:
//
// \code
// struct Writer
// {
//     // called before starting to parse a new read
//     void begin_read();
//
//     // called upon completion of a single read
//     void end_read();
//
//     // provide the next character of the read id
//     void id(const char c);
//
//     // provide the next base of the read
//     void read(const char c);
// }
// \endcode
//
template <typename Writer>
uint32 FASTA_inc_reader::read(const uint32 n_reads, Writer& writer)
{
    uint32 n = 0;
    uint8 c;
    bool  read_sequence = false;

    writer.begin_read();

    while ((c = get()) != 255)
    {
        // start of a new sequence?
        if (c == '>')
        {
            // end the previous one
            if (read_sequence)
            {
                writer.end_read();
                writer.begin_read();

                if (n == n_reads)
                {
                    m_buffer_pos--;
                    return n;
                }
            }

            n++;
            
            // read the id
            for (c = get(); c != ' ' && c != '\n'; c = get())
                writer.id( c );
            writer.id( '\0' );

            // read the rest of the line
            while (c != '\n') { c = get(); }

            read_sequence = true;
        }

        // save non-trivial characters into the read
        if (read_sequence && c != '\n' && c != ' ')
            writer.read( c );
    }
    // output the last sequence we've been reading
    writer.end_read();

    return n;
}

// get the next character, or 255 if EOF
//
inline
uint8 FASTA_inc_reader::get()
{
    if (m_buffer_pos >= m_buffer_size)
    {
        m_buffer_size = uint32( gzread( m_file, &m_buffer[0], (unsigned int)m_buffer.size() ) );
        m_buffer_pos  = 0;
    }
    return (m_buffer_pos < m_buffer_size) ? m_buffer[ m_buffer_pos++ ] : 255u;
};

// constructor
//
inline
FASTA_reader::FASTA_reader(const char* filename, const uint32 buffer_size)
    : m_buffer( buffer_size ), m_buffer_size( 0 ), m_buffer_pos( 0 )
{
    m_file = gzopen( filename, "r" );
    if (m_file)
        gzbuffer( m_file, buffer_size );
}

// destructor
//
inline
FASTA_reader::~FASTA_reader()
{
    if (m_file)
        gzclose( m_file );
}

// rewind the file
//
inline
void FASTA_reader::rewind()
{
    if (m_file)
        gzrewind( m_file );

    m_buffer_size = 0;
    m_buffer_pos  = 0;
}

// read a batch of bp reads
//
// \tparam Writer          an output handler class, which must
//                         implement the following interface:
//
// \code
// struct Writer
// {
//     // called whenever a new read has been parsed
//     void push_back(
//         const char*     id,
//         const uint32    read_length,
//         const uint8*    read);
// }
// \endcode
//
template <typename Writer>
uint32 FASTA_reader::read(const uint32 n_reads, Writer& writer)
{
    uint32 n = 0;
    uint8 c;
    bool  read_sequence = false;

    while ((c = get()) != 255)
    {
        // start of a new sequence?
        if (c == '>')
        {
            // output the previous one
            if (read_sequence)
            {
                writer.push_back(
                    &m_id[0],
                    uint32( m_read.size() ),
                    &m_read[0] );

                if (n == n_reads)
                    return n;
            }

            n++;
            
            // read the id
            m_id.erase( m_id.begin(), m_id.end() );
            for (c = get(); c != ' ' && c != '\n'; c = get())
                m_id.push_back( char(c) );

            m_id.push_back('\0');

            // read the rest of the line
            while (c != '\n') { c = get(); }

            // reset the read
            m_read.erase( m_read.begin(), m_read.end() );

            read_sequence = true;
        }

        // save non-trivial characters into the read
        if (read_sequence && c != '\n' && c != ' ')
            m_read.push_back( c );
    }
    // output the last sequence we've been reading
    if (read_sequence)
    {
        writer.push_back(
            &m_id[0],
            uint32( m_read.size() ),
            &m_read[0] );
    }
    return n;
}

// get the next character, or 255 if EOF
//
inline
uint8 FASTA_reader::get()
{
    if (m_buffer_pos >= m_buffer_size)
    {
        m_buffer_size = uint32( gzread( m_file, &m_buffer[0], (unsigned int)m_buffer.size() ) );
        m_buffer_pos  = 0;
    }
    return (m_buffer_pos < m_buffer_size) ? m_buffer[ m_buffer_pos++ ] : 255u;
}

} // namespace nvbio
