
#include "PajaTypes.h"
#include "TimeSegmentC.h"
#include "DataBlockI.h"
#include "EditableI.h"
#include "KeyC.h"
#include "UndoC.h"
#include "FileIO.h"
#include <stdlib.h>	// qsort
#include <assert.h>
#include <vector>


using namespace PajaTypes;
using namespace Composition;
using namespace Edit;
using namespace FileIO;


const uint32		TIMESEGMENT_VERSION_1 = 1;
const uint32		TIMESEGMENT_VERSION = 2;

enum TimeSegmentChunksE {
	CHUNK_TIMESEGMENT_BASE = 0x1000,
	CHUNK_TIMESEGMENT_KEY = 0x2000,
};



TimeSegmentC::TimeSegmentC( EditableI* pOriginal ) :
	EditableI( pOriginal ),
	m_i32SegmentStart( 0 )
{
	// empty
}

TimeSegmentC::TimeSegmentC() :
	m_i32SegmentStart( 0 )
{
	// empty
}

TimeSegmentC::TimeSegmentC( PajaTypes::int32 i32SegmentStart, PajaTypes::int32 i32SegmentEnd ) :
	m_i32SegmentStart( i32SegmentStart )
{
	// add two initial keys
	add_key_at_time( i32SegmentStart );
	add_key_at_time( i32SegmentEnd );
}

TimeSegmentC::~TimeSegmentC()
{
	// If this is clone, bail out.
	if( get_original() )
		return;

	// delete keys
	for( uint32 i = 0; i < m_rTrimKeys.size(); i++ )
		m_rTrimKeys[i]->release();
}


TimeSegmentC*
TimeSegmentC::create_new()
{
	return new TimeSegmentC();
}

TimeSegmentC*
TimeSegmentC::create_new( PajaTypes::int32 i32SegmentStart, PajaTypes::int32 i32SegmentEnd )
{
	return new TimeSegmentC( i32SegmentStart, i32SegmentEnd );
}

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

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

void
TimeSegmentC::restore( EditableI* pEditable )
{
	TimeSegmentC*	pSeg = (TimeSegmentC*)pEditable;
	m_i32SegmentStart = pSeg->m_i32SegmentStart;
	m_rTrimKeys = pSeg->m_rTrimKeys;
}

PluginClass::SuperClassIdC
TimeSegmentC::get_base_class_id() const
{
	return BASECLASS_TIMESEGMENT;
}

void
TimeSegmentC::copy( EditableI* pEditable )
{
	TimeSegmentC*	pSeg = (TimeSegmentC*)pEditable;
	uint32			i;

	m_i32SegmentStart = pSeg->m_i32SegmentStart;

	// remove old keys
	for( i = 0; i < m_rTrimKeys.size(); i++ )
		m_rTrimKeys[i]->release();
	m_rTrimKeys.clear();

	// add new keys
	for( i = 0; i < pSeg->m_rTrimKeys.size(); i++ )
		m_rTrimKeys.push_back( (KeyC*)pSeg->m_rTrimKeys[i]->duplicate() );
}


// the segment
PajaTypes::int32
TimeSegmentC::get_segment_start()
{
	return m_i32SegmentStart;
}

void
TimeSegmentC::set_segment_start( PajaTypes::int32 i32Start )
{
	m_i32SegmentStart = i32Start;
}

// time trim keys
PajaTypes::int32
TimeSegmentC::get_key_count()
{
	return m_rTrimKeys.size();
}

KeyC*
TimeSegmentC::get_key( PajaTypes::int32 i32Index )
{
	assert( i32Index >= 0 && i32Index < (int32)m_rTrimKeys.size() );
	return m_rTrimKeys[i32Index];
}

void
TimeSegmentC::del_key( PajaTypes::int32 i32Index )
{
	assert( i32Index >= 0 && i32Index < (int32)m_rTrimKeys.size() );
	KeyC*	pKey = m_rTrimKeys[i32Index];
	m_rTrimKeys.erase( m_rTrimKeys.begin() + i32Index );
	if( get_undo() )
		get_undo()->add_discardable_data( pKey, DATA_REMOVED );
}

void
TimeSegmentC::set_key_time( PajaTypes::int32 i32Index, PajaTypes::int32 i32Time )
{
	assert( i32Index >= 0 && i32Index < (int32)m_rTrimKeys.size() );

	// move to local timespace
	i32Time -= m_i32SegmentStart;

	// set time
	UndoC*	pOldUndo = m_rTrimKeys[i32Index]->begin_editing( get_undo() );
	m_rTrimKeys[i32Index]->set_time( i32Time );
	m_rTrimKeys[i32Index]->end_editing( pOldUndo );

	sort_keys();
}

PajaTypes::int32
TimeSegmentC::get_key_time( PajaTypes::int32 i32Index )
{
	assert( i32Index >= 0 && i32Index < (int32)m_rTrimKeys.size() );

	// return time in global time space
	return m_rTrimKeys[i32Index]->get_time() + m_i32SegmentStart;
}

KeyC*
TimeSegmentC::get_key_at_time( PajaTypes::int32 i32Time )
{
	i32Time -= m_i32SegmentStart;
	for( uint32 i = 0; i < m_rTrimKeys.size(); i++ )
		if( m_rTrimKeys[i]->get_time() == i32Time )
			return m_rTrimKeys[i];
	return 0;
}

KeyC*
TimeSegmentC::add_key_at_time( PajaTypes::int32 i32Time )
{
	i32Time -= m_i32SegmentStart;

	KeyC*	pKey = KeyC::create_new();
	pKey->set_time( i32Time );
	m_rTrimKeys.push_back( pKey );
	sort_keys();

	if( get_undo() )
		get_undo()->add_discardable_data( pKey, DATA_CREATED );

	return pKey;
}

bool
TimeSegmentC::is_visible( PajaTypes::int32 i32Time )
{
	i32Time -= m_i32SegmentStart;

	// test boxes
	for( uint32 i = 0; (i + 1) < m_rTrimKeys.size(); i += 2 ) {
		int32	i32TimeStart, i32TimeEnd;
		i32TimeStart = m_rTrimKeys[i]->get_time();
		i32TimeEnd = m_rTrimKeys[i + 1]->get_time();

		if( i32Time >= i32TimeStart && i32Time < i32TimeEnd )
			return true;
	}

	return false;
}

static
int
compare_func( const void* vpParam1, const void* vpParam2 )
{
	KeyC*	pKey1 = *(KeyC**)vpParam1;
	KeyC*	pKey2 = *(KeyC**)vpParam2;

	if( pKey1->get_time() < pKey2->get_time() )
		return -1;
	return 1;
}

void
TimeSegmentC::sort_keys()
{
	if( m_rTrimKeys.size() )
		qsort( &m_rTrimKeys[0], m_rTrimKeys.size(), sizeof( KeyC* ), compare_func );
}


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

	pSave->begin_chunk( CHUNK_TIMESEGMENT_BASE, TIMESEGMENT_VERSION );
		// segment start
		ui32Error = pSave->write( &m_i32SegmentStart, sizeof( m_i32SegmentStart ) );
	pSave->end_chunk();

	// save keys
	for( uint32 i = 0; i < m_rTrimKeys.size(); i++ ) {
		pSave->begin_chunk( CHUNK_TIMESEGMENT_KEY, TIMESEGMENT_VERSION );
		ui32Error = m_rTrimKeys[i]->save( pSave );
		pSave->end_chunk();
	}

	return ui32Error;
}

uint32
TimeSegmentC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_TIMESEGMENT_BASE:
			{
				if( pLoad->get_chunk_version() == TIMESEGMENT_VERSION_1 ) {
					// segment start
					ui32Error = pLoad->read( &m_i32SegmentStart, sizeof( m_i32SegmentStart ) );
					// segment end  OBSOLETE!!
					uint32	ui32Tmp;
					ui32Error = pLoad->read( &ui32Tmp, sizeof( ui32Tmp ) );
				}
				else if( pLoad->get_chunk_version() == TIMESEGMENT_VERSION ) {
					// segment start
					ui32Error = pLoad->read( &m_i32SegmentStart, sizeof( m_i32SegmentStart ) );
				}
			}
			break;
		case CHUNK_TIMESEGMENT_KEY:
			{
				if( pLoad->get_chunk_version() == TIMESEGMENT_VERSION_1 || pLoad->get_chunk_version() == TIMESEGMENT_VERSION ) {
					KeyC*	pKey = KeyC::create_new();
					ui32Error = pKey->load( pLoad );
					m_rTrimKeys.push_back( pKey );
				}
			}
			break;
		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

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

	sort_keys();

	return IO_OK;
}
