//
// OutputChannelC.cpp: implementation of the OutputChannelC class.
//
//////////////////////////////////////////////////////////////////////

#include "OutputChannelC.h"
#include "DataBlockI.h"
#include "EditableI.h"
#include "UndoC.h"
#include "OutputTrackC.h"
#include "PajaTypes.h"
#include "FileIO.h"
#include <vector>
#include <string>
#include <assert.h>
#include <stdlib.h>		//qsort


using namespace PajaTypes;
using namespace Edit;
using namespace Composition;
using namespace FileIO;
using namespace PluginClass;
using namespace PajaSystem;

enum OutputChannelChunksE {
	CHUNK_OUTPUTCH_BASE =		0x1000,
	CHUNK_OUTPUTCH_TRACK =		0x2000,
	CHUNK_OUTPUTCH_MARKER =		0x3000,
};

const uint32	OUTPUTCH_VERSION = 1;



OutputChannelC::OutputChannelC() :
	m_i32BeatsPerMin( 120 ),
	m_i32EditAccuracy( 2 ),
	m_i32QNotesPerBeat( 4 ),
	m_i32BeatsPerMeasure( 4 ),
	m_i32Duration( 10 * (120 * 4 * 256 / 60)  ),		// 10 sec (120 BPM * 4 beats/measure * quaternote size
	m_i32Width( 640 ),
	m_i32Height( 480 ),
	m_i32Time( 0 ),
	m_i32Flags( 0 ),
	m_pGBuffer( 0 )
{
	// empty
}

OutputChannelC::OutputChannelC( int32 i32Flags ) :
	m_i32BeatsPerMin( 120 ),
	m_i32EditAccuracy( 2 ),
	m_i32QNotesPerBeat( 4 ),
	m_i32BeatsPerMeasure( 4 ),
	m_i32Duration( 10 * (120 * 4 * 256 / 60)  ),		// 10 sec (120 BPM * 4 beats/measure * quaternote size
	m_i32Width( 640 ),
	m_i32Height( 480 ),
	m_i32Time( 0 ),
	m_i32Flags( i32Flags ),
	m_pGBuffer( 0 )
{
	// empty
}


OutputChannelC::OutputChannelC( EditableI* pOriginal ) :
	EditableI( pOriginal ),
	m_i32BeatsPerMin( 120 ),
	m_i32EditAccuracy( 8 ),
	m_i32QNotesPerBeat( 4 ),
	m_i32BeatsPerMeasure( 4 ),
	m_i32Duration( 10 * (120 * 4 * 256 / 60)  ),		// 10 sec
	m_i32Width( 640 ),
	m_i32Height( 480 ),
	m_i32Time( 0 ),
	m_i32Flags( 0 ),
	m_pGBuffer( 0 )
{
	// empty
}

OutputChannelC::~OutputChannelC()
{
	// if this is a clone, return
	if( get_original() )
		return;

	for( uint32 i = 0; i < m_rTracks.size(); i++ )
		m_rTracks[i]->release();
}

OutputChannelC*
OutputChannelC::create_new( int32 i32Flags )
{
	return new OutputChannelC( i32Flags );
}

DataBlockI*
OutputChannelC::create()
{
	return new OutputChannelC;
}

DataBlockI*
OutputChannelC::create( EditableI* pOriginal )
{
	return new OutputChannelC( pOriginal );
}

void
OutputChannelC::copy( EditableI* pEditable )
{
	uint32	i;
	OutputChannelC*	pOutCh = (OutputChannelC*)pEditable;

	// remove layers
	for( i = 0; i < m_rTracks.size(); i++ )
		m_rTracks[i]->release();
	m_rTracks.clear();

	// duplicate layers
	for( i = 0; i < pOutCh->m_rTracks.size(); i++ )
		m_rTracks.push_back( (OutputTrackC*)pOutCh->m_rTracks[i]->duplicate() );

	m_sName = pOutCh->m_sName;
	m_i32BeatsPerMin = pOutCh->m_i32BeatsPerMin;
	m_i32EditAccuracy = pOutCh->m_i32EditAccuracy;
	m_i32QNotesPerBeat = pOutCh->m_i32QNotesPerBeat;
	m_i32BeatsPerMeasure = pOutCh->m_i32BeatsPerMeasure;
	m_rMarkers = pOutCh->m_rMarkers;
	m_i32Flags = pOutCh->m_i32Flags;
}

void
OutputChannelC::restore( EditableI* pEditable )
{
	OutputChannelC*	pOutCh = (OutputChannelC*)pEditable;
	m_rTracks = pOutCh->m_rTracks;
	m_sName = pOutCh->m_sName;
	m_i32BeatsPerMin = pOutCh->m_i32BeatsPerMin;
	m_i32EditAccuracy = pOutCh->m_i32EditAccuracy;
	m_i32QNotesPerBeat = pOutCh->m_i32QNotesPerBeat;
	m_i32BeatsPerMeasure = pOutCh->m_i32BeatsPerMeasure;
	m_rMarkers = pOutCh->m_rMarkers;
	m_i32Flags = pOutCh->m_i32Flags;
}

void
OutputChannelC::set_name( const char* szName )
{
	m_sName = szName;
}

const char*
OutputChannelC::get_name() const
{
	return m_sName.c_str();
}

void
OutputChannelC::set_graphicsbuffer( GraphicsBufferI* pBuffer )
{
	m_pGBuffer = pBuffer;
}

GraphicsBufferI*
OutputChannelC::get_graphicsbuffer()
{
	return m_pGBuffer;
}


PajaTypes::uint32
OutputChannelC::get_track_count()
{
	return m_rTracks.size();
}

OutputTrackC*
OutputChannelC::get_track( PajaTypes::uint32 ui32Index )
{
	assert( ui32Index >= 0 && ui32Index < m_rTracks.size() );
	return m_rTracks[ui32Index];
}

OutputTrackC*
OutputChannelC::add_track()
{
	OutputTrackC*	pTrack = (OutputTrackC*)OutputTrackC::create_new( this );
	if( get_undo() )
		get_undo()->add_discardable_data( pTrack, DATA_CREATED );
	m_rTracks.insert( m_rTracks.begin(), pTrack );
	return pTrack;
}

void
OutputChannelC::del_track( PajaTypes::uint32 ui32Index )
{
	assert( ui32Index >= 0 && ui32Index < m_rTracks.size() );
	OutputTrackC*	pTrack = m_rTracks[ui32Index];
	if( get_undo() )
		get_undo()->add_discardable_data( pTrack, DATA_REMOVED );
	m_rTracks.erase( m_rTracks.begin() + ui32Index );
}

int32
OutputChannelC::get_beats_per_min()
{
	return m_i32BeatsPerMin;
}

void
OutputChannelC::set_beats_per_min( int32 i32BeatsPerMin )
{
	m_i32BeatsPerMin = i32BeatsPerMin;
}

int32
OutputChannelC::get_edit_accuracy()
{
	return m_i32EditAccuracy;
}

void
OutputChannelC::set_edit_accuracy( int32 i32Accuracy )
{
	m_i32EditAccuracy = i32Accuracy;
}

int32
OutputChannelC::get_beats_per_measure()
{
	return m_i32BeatsPerMeasure;
}

void
OutputChannelC::set_beats_per_measure( int32 i32Beats )
{
	m_i32BeatsPerMeasure = i32Beats;
}

int32
OutputChannelC::get_qnotes_per_beat()
{
	return m_i32QNotesPerBeat;
}

void
OutputChannelC::set_qnotes_per_beat( int32 i32QNotes )
{
	m_i32QNotesPerBeat = i32QNotes;
}

int32
OutputChannelC::get_time() const
{
	return m_i32Time;
}

void
OutputChannelC::set_time( int32 i32Time )
{
	m_i32Time = i32Time;
}


int32
OutputChannelC::get_duration()
{
	return m_i32Duration;
}

void
OutputChannelC::set_duration( int32 i32Duration )
{
	m_i32Duration = i32Duration;
}


int32
OutputChannelC::get_width()
{
	return m_i32Width;
}

void
OutputChannelC::set_width( int32 i32Width )
{
	m_i32Width = i32Width;
}

int32
OutputChannelC::get_height()
{
	return m_i32Height;
}

void
OutputChannelC::set_height( int32 i32Height )
{
	m_i32Height = i32Height;
}


uint32
OutputChannelC::get_marker_count() const
{
	return m_rMarkers.size();
}

int32
OutputChannelC::get_marker_time( uint32 ui32Index ) const
{
	if( ui32Index < m_rMarkers.size() )
		return m_rMarkers[ui32Index].m_i32Time;
	return 0;
}

const char*
OutputChannelC::get_marker_name( uint32 ui32Index ) const
{
	if( ui32Index < m_rMarkers.size() )
		return m_rMarkers[ui32Index].m_sName.c_str();
	return 0;
}

void
OutputChannelC::set_marker_time( uint32 ui32Index, int32 i32Time )
{
	if( ui32Index < m_rMarkers.size() )
		m_rMarkers[ui32Index].m_i32Time = i32Time;
}

void
OutputChannelC::set_marker_name( uint32 ui32Index, const char* szName )
{
	if( ui32Index < m_rMarkers.size() )
		m_rMarkers[ui32Index].m_sName = szName;
}

void
OutputChannelC::add_marker( int32 i32Time, const char* szName )
{
	// if there's a marker in the same point in time, delete the old.
	for( int32 i = m_rMarkers.size() - 1; i >= 0; i-- ) {
		if( m_rMarkers[i].m_i32Time == i32Time )
			m_rMarkers.erase( m_rMarkers.begin() + i );
	}

	// add new marker
	MarkerS	rMarker;
	rMarker.m_i32Time = i32Time;
	rMarker.m_sName = szName;
	m_rMarkers.push_back( rMarker );
}

void
OutputChannelC::del_marker( uint32 ui32Index )
{
	if( ui32Index < m_rMarkers.size() )
		m_rMarkers.erase( m_rMarkers.begin() + ui32Index );
}


int
OutputChannelC::compare_func( const void* vpParam1, const void* vpParam2 )
{
	MarkerS*	pMarker1 = (MarkerS*)vpParam1;
	MarkerS*	pMarker2 = (MarkerS*)vpParam2;

	if( pMarker1->m_i32Time < pMarker2->m_i32Time )
		return -1;

	return 1;
}

void
OutputChannelC::sort_markers()
{
	qsort( m_rMarkers.begin(), m_rMarkers.size(), sizeof( MarkerS ), compare_func );
}


void
OutputChannelC::set_flags( int32 i32Flags )
{
	m_i32Flags = i32Flags;
}

void
OutputChannelC::add_flags( int32 i32Flags )
{
	m_i32Flags |= i32Flags;
}

void
OutputChannelC::del_flags( int32 i32Flags )
{
	m_i32Flags &= ~i32Flags;
}

void
OutputChannelC::toggle_flags( int32 i32Flags )
{
	m_i32Flags ^= i32Flags;
}

int32
OutputChannelC::get_flags()
{
	return m_i32Flags;
}

uint32
OutputChannelC::save( SaveC* pSave )
{
	uint32	ui32Error = IO_OK;
	uint32	i;

	//
	// save scene base information
	//

	// make sure this string fits into the buffer when it's loaded back
	std::string	sStr = m_sName;
	if( sStr.size() > 255 )
		sStr.resize( 255 );

	pSave->begin_chunk( CHUNK_OUTPUTCH_BASE, OUTPUTCH_VERSION );
		// name
		ui32Error = pSave->write_str( sStr.c_str() );
		// layout width
		ui32Error = pSave->write( &m_i32Width, sizeof( m_i32Width ) );
		//  height
		ui32Error = pSave->write( &m_i32Height, sizeof( m_i32Height ) );
		// duration
		ui32Error = pSave->write( &m_i32Duration, sizeof( m_i32Duration ) );
		// BPM
		ui32Error = pSave->write( &m_i32BeatsPerMin, sizeof( m_i32BeatsPerMin ) );
		// quarter-notes per beat
		ui32Error = pSave->write( &m_i32QNotesPerBeat, sizeof( m_i32QNotesPerBeat ) );
		// beats per measure
		ui32Error = pSave->write( &m_i32BeatsPerMeasure, sizeof( m_i32BeatsPerMeasure ) );
		// editing accuracy
		ui32Error = pSave->write( &m_i32EditAccuracy, sizeof( m_i32EditAccuracy ) );
		// flags
		ui32Error = pSave->write( &m_i32Flags, sizeof( m_i32Flags ) );
	pSave->end_chunk();

	//
	// save track
	//
	for( i = 0; i < m_rTracks.size(); i++ ) {
		pSave->begin_chunk( CHUNK_OUTPUTCH_TRACK, OUTPUTCH_VERSION );
			m_rTracks[i]->save( pSave );
		pSave->end_chunk();
	}

	//
	// Save markers
	//
	for( i = 0; i < m_rMarkers.size(); i++ ) {
		std::string	sName = m_rMarkers[i].m_sName;
		if( sName.size() > 255 )
			sName.resize( 255 );
		pSave->begin_chunk( CHUNK_OUTPUTCH_MARKER, OUTPUTCH_VERSION );
			ui32Error = pSave->write_str( sName.c_str() );
			ui32Error = pSave->write( &m_rMarkers[i].m_i32Time, sizeof( int32 ) );
		pSave->end_chunk();
	}

	return ui32Error;
}

uint32
OutputChannelC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;
	char	szStr[256];

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_OUTPUTCH_BASE:
			{
				if( pLoad->get_chunk_version() == OUTPUTCH_VERSION ) {
					// name
					ui32Error = pLoad->read_str( szStr );
					m_sName = szStr;
					// layout width
					ui32Error = pLoad->read( &m_i32Width, sizeof( m_i32Width ) );
					//  height
					ui32Error = pLoad->read( &m_i32Height, sizeof( m_i32Height ) );
					// duration
					ui32Error = pLoad->read( &m_i32Duration, sizeof( m_i32Duration ) );
					// BPM
					ui32Error = pLoad->read( &m_i32BeatsPerMin, sizeof( m_i32BeatsPerMin ) );
					// quarter-notes per beat
					ui32Error = pLoad->read( &m_i32QNotesPerBeat, sizeof( m_i32QNotesPerBeat ) );
					// beats per measure
					ui32Error = pLoad->read( &m_i32BeatsPerMeasure, sizeof( m_i32BeatsPerMeasure ) );
					// editing accuracy
					ui32Error = pLoad->read( &m_i32EditAccuracy, sizeof( m_i32EditAccuracy ) );
					// flags
					ui32Error = pLoad->read( &m_i32Flags, sizeof( m_i32Flags ) );
				}
			}
			break;

		case CHUNK_OUTPUTCH_TRACK:
			{
				if( pLoad->get_chunk_version() == OUTPUTCH_VERSION ) {
					OutputTrackC*	pTrack = OutputTrackC::create_new( this );
					pTrack->load( pLoad );
					m_rTracks.push_back( pTrack );
				}
			}
			break;

		case CHUNK_OUTPUTCH_MARKER:
			{
				if( pLoad->get_chunk_version() == OUTPUTCH_VERSION ) {
					int32	i32Time;
					ui32Error = pLoad->read_str( szStr );
					ui32Error = pLoad->read( &i32Time, sizeof( i32Time ) );

					add_marker( i32Time, szStr );
				}
			}
			break;

		default:
			assert( 0 );
			break;
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END )
			return ui32Error;
	}

	if( ui32Error != IO_OK && ui32Error != IO_END )
		return ui32Error;

	sort_markers();

	return IO_OK;
}
