#include "midi_processor.h"

bool midi_processor::is_standard_midi( std::vector<uint8_t> const& p_file )
{
    if ( p_file.size() < 18 ) return false;
    if ( p_file[ 0 ] != 'M' || p_file[ 1 ] != 'T' || p_file[ 2 ] != 'h' || p_file[ 3 ] != 'd') return false;
    if ( p_file[ 4 ] != 0 || p_file[ 5 ] != 0 || p_file[ 6 ] != 0 || p_file[ 7 ] != 6 ) return false;
    if ( p_file[ 10 ] == 0 && p_file[ 11 ] == 0 ) return false; // no tracks
    if ( p_file[ 12 ] == 0 && p_file[ 13 ] == 0 ) return false; // dtx == 0, will cause division by zero on tempo calculations
    return true;
}

bool midi_processor::process_standard_midi_count( std::vector<uint8_t> const& p_file, size_t & track_count )
{
    track_count = 0;

    if ( p_file[ 0 ] != 'M' || p_file[ 1 ] != 'T' || p_file[ 2 ] != 'h' || p_file[ 3 ] != 'd' ) return false;
    if ( p_file[ 4 ] != 0 || p_file[ 5 ] != 0 || p_file[ 6 ] != 0 || p_file[ 7 ] != 6 ) return false; /*throw exception_io_data("Bad MIDI header size");*/

    std::vector<uint8_t>::const_iterator it = p_file.begin() + 8;

    uint16_t form = ( it[0] << 8 ) | it[1];
    if ( form > 2 ) return false;

    uint16_t track_count_16 = ( it[2] << 8 ) | it[3];

    if ( form == 2 ) track_count = track_count_16;
    else track_count = 1;

    return true;
}

void midi_processor::process_standard_midi_track( std::vector<uint8_t>::const_iterator & it, std::vector<uint8_t>::const_iterator end, midi_container & p_out, bool is_gmf )
{
    bool needs_end_marker = false;

    midi_track track;
    unsigned current_timestamp = 0;
    unsigned last_event_timestamp = 0;
    unsigned char last_event_code = 0xFF;

    unsigned last_sysex_length = 0;
    unsigned last_sysex_timestamp = 0;

    std::vector<uint8_t> buffer;
    buffer.resize( 3 );

    for (;;)
    {
        if ( !needs_end_marker && it == end ) break;
        int delta = decode_delta( it, end );
        if ( !needs_end_marker && it == end ) break;

        if ( delta < 0 )
        {
            /*"MIDI processor encountered negative delta: " << delta << "; flipping sign.";*/
            delta = -delta;
        }
        
        if ( delta > 1000000 )
            break;

        current_timestamp += delta;
        if ( it == end ) break;
        unsigned char event_code = *it++;
        unsigned data_bytes_read = 0;
        if ( event_code < 0x80 )
        {
            if ( last_event_code == 0xFF ) break; /*throw exception_io_data("First MIDI track event short encoded");*/
            buffer[ data_bytes_read++ ] = event_code;
            event_code = last_event_code;
        }
        if ( event_code < 0xF0 )
        {
            if ( last_sysex_length )
            {
                track.add_event( midi_event( last_event_timestamp = last_sysex_timestamp, midi_event::extended, 0, &buffer[0], last_sysex_length ) );
                last_sysex_length = 0;
            }

            last_event_code = event_code;
            if ( is_gmf && ( event_code & 0xF0 ) == 0xE0 ) continue;
            if ( data_bytes_read < 1 )
            {
                if ( it == end ) break;
                buffer[ 0 ] = *it++;
                ++data_bytes_read;
            }
            bool command_valid = true;
            switch ( event_code & 0xF0 )
            {
            case 0xC0:
            case 0xD0:
                break;
            default:
                if ( it == end )
                {
                    command_valid = false;
                    break;
                }
                buffer[ data_bytes_read ] = *it++;
                ++data_bytes_read;
            }
            if ( !command_valid ) break;
            track.add_event( midi_event( last_event_timestamp = current_timestamp, (midi_event::event_type)(( event_code >> 4 ) - 8), event_code & 0x0F, &buffer[0], data_bytes_read ) );
        }
        else if ( event_code == 0xF0 )
        {
            if ( last_sysex_length )
            {
                track.add_event( midi_event( last_event_timestamp = last_sysex_timestamp, midi_event::extended, 0, &buffer[0], last_sysex_length ) );
                last_sysex_length = 0;
            }

            if ( it == end ) break;
            int data_count = decode_delta( it, end );
            if ( data_count < 0 ) break; /*throw exception_io_data( "Invalid System Exclusive message" );*/
            if ( end - it < data_count ) break;
            buffer.resize( data_count + 1 );
            buffer[ 0 ] = 0xF0;
            std::copy( it, it + data_count, buffer.begin() + 1 );
            it += data_count;
            last_sysex_length = data_count + 1;
            last_sysex_timestamp = current_timestamp;
        }
        else if ( event_code == 0xF7 ) // SysEx continuation
        {
            if ( !last_sysex_length ) break;
            if ( it == end ) break;
            int data_count = decode_delta( it, end );
            if ( data_count < 0 ) break;
            if ( end - it < data_count ) break;
            buffer.resize( last_sysex_length + data_count );
            std::copy( it, it + data_count, buffer.begin() + last_sysex_length );
            it += data_count;
            last_sysex_length += data_count;
        }
        else if ( event_code == 0xFF )
        {
            if ( last_sysex_length )
            {
                track.add_event( midi_event( last_event_timestamp = last_sysex_timestamp, midi_event::extended, 0, &buffer[0], last_sysex_length ) );
                last_sysex_length = 0;
            }

            if ( it == end ) break;
            unsigned char meta_type = *it++;
            if ( it == end ) break;
            int data_count = decode_delta( it, end );
            if ( data_count < 0 ) break; /*throw exception_io_data( "Invalid meta message" );*/
            if ( end - it < data_count ) break;
            buffer.resize( data_count + 2 );
            buffer[ 0 ] = 0xFF;
            buffer[ 1 ] = meta_type;
            std::copy( it, it + data_count, buffer.begin() + 2 );
            it += data_count;
            if ( meta_type == 0x2F )
                current_timestamp = last_event_timestamp;
            else
                last_event_timestamp = current_timestamp;
            track.add_event( midi_event( current_timestamp, midi_event::extended, 0, &buffer[0], data_count + 2 ) );

            if ( meta_type == 0x2F )
            {
                needs_end_marker = true;
                break;
            }
        }
        else if ( event_code >= 0xF8 && event_code <= 0xFE )
        {
            /* Sequencer specific events, single byte */
            buffer[ 0 ] = event_code;
            track.add_event( midi_event( last_event_timestamp = current_timestamp, midi_event::extended, 0, &buffer[0], 1 ) );
        }
        else break; /*throw exception_io_data("Unhandled MIDI status code");*/
    }

    if ( !needs_end_marker )
    {
        buffer[ 0 ] = 0xFF;
        buffer[ 1 ] = 0x2F;
        track.add_event( midi_event( last_event_timestamp, midi_event::extended, 0, &buffer[0], 2 ) );
    }

    p_out.add_track( track );
}

bool midi_processor::process_standard_midi( std::vector<uint8_t> const& p_file, midi_container & p_out )
{
    if ( p_file[ 0 ] != 'M' || p_file[ 1 ] != 'T' || p_file[ 2 ] != 'h' || p_file[ 3 ] != 'd' ) return false;
    if ( p_file[ 4 ] != 0 || p_file[ 5 ] != 0 || p_file[ 6 ] != 0 || p_file[ 7 ] != 6 ) return false; /*throw exception_io_data("Bad MIDI header size");*/

    std::vector<uint8_t>::const_iterator it = p_file.begin() + 8;
    std::vector<uint8_t>::const_iterator end = p_file.end();

    uint16_t form = ( it[0] << 8 ) | it[1];
    if ( form > 2 ) return false;

    uint16_t track_count_16 = ( it[2] << 8 ) | it[3];
    uint16_t dtx = ( it[4] << 8 ) | it[5];

    if ( !track_count_16 || !dtx )
        return false;

    it += 6;

    std::size_t track_count = track_count_16;

    p_out.initialize( form, dtx );

    for ( std::size_t i = 0; i < track_count; ++i )
    {
        if ( end - it < 8 ) break;
        while ( it[0] != 'M' || it[1] != 'T' || it[2] != 'r' || it[3] != 'k' ) {
            uint32_t chunk_size = ( it[4] << 24 ) | ( it[5] << 16 ) | ( it[6] << 8 ) | it[7];
            if ( (unsigned long)(end - it) < 8 + chunk_size )
                break;
            it += 8 + chunk_size;
            if ( end - it < 8 ) break;
        }
        
        if ( end - it < 8 ) break;

        uint32_t track_size = ( it[4] << 24 ) | ( it[5] << 16 ) | ( it[6] << 8 ) | it[7];

        it += 8;

        intptr_t track_data_offset = it - p_file.begin();

        if ( (unsigned long)(end - it) < track_size )
            track_size = (uint32_t)(end - it);

        process_standard_midi_track( it, it + track_size, p_out, false );

        track_data_offset += track_size;
        size_t messup_offset = it - p_file.begin();
        if ( messup_offset != track_data_offset )
        {
            /* Assume messed up track, attempt to skip it. */
            if ( end - p_file.begin() >= track_data_offset )
                it = p_file.begin() + track_data_offset;
            else
                break;
        }
    }

    return !!p_out.get_track_count();
}
