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

#include "SceneC.h"
#include "DataBlockI.h"
#include "EditableI.h"
#include "UndoC.h"
#include "LayerC.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 SceneChunksE {
	CHUNK_SCENE_BASE =			0x1000,
	CHUNK_SCENE_MUSICSTART =	0x1001,
	CHUNK_SCENE_LAYOUTCOLOR =	0x1002,
	CHUNK_SCENE_LAYER =			0x2000,
	CHUNK_SCENE_MARKER =		0x3000,
};

const uint32	SCENE_VERSION_1 = 1;
const uint32	SCENE_VERSION = 2;



SceneC::SceneC() :
	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_i32LayoutWidth( 640 ),
	m_i32LayoutHeight( 480 ),
	m_i32Time( 0 ),
	m_i32MusicStartTime( 0 ),
	m_rLayoutColor( 0, 0, 0, 1 ),
	m_pParent( 0 )
{
	// empty
}

SceneC::SceneC( 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_i32LayoutWidth( 640 ),
	m_i32LayoutHeight( 480 ),
	m_i32Time( 0 ),
	m_i32MusicStartTime( 0 ),
	m_rLayoutColor( 0, 0, 0, 1 ),
	m_pParent( 0 )
{
	// empty
}

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

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

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

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

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

void
SceneC::copy( EditableI* pEditable )
{
	uint32	i;
	SceneC*	pScene = (SceneC*)pEditable;

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

	// duplicate layers
	for( i = 0; i < pScene->m_rLayers.size(); i++ )
		m_rLayers.push_back( (LayerC*)pScene->m_rLayers[i]->duplicate() );

	m_sName = pScene->m_sName;
	m_i32BeatsPerMin = pScene->m_i32BeatsPerMin;
	m_i32EditAccuracy = pScene->m_i32EditAccuracy;
	m_i32QNotesPerBeat = pScene->m_i32QNotesPerBeat;
	m_i32BeatsPerMeasure = pScene->m_i32BeatsPerMeasure;
	m_rMarkers = pScene->m_rMarkers;
	m_i32Time = pScene->m_i32Time;
	m_i32MusicStartTime = pScene->m_i32MusicStartTime;
	m_rLayoutColor = pScene->m_rLayoutColor;
}

void
SceneC::restore( EditableI* pEditable )
{
	SceneC*	pScene = (SceneC*)pEditable;
	m_rLayers = pScene->m_rLayers;
	m_sName = pScene->m_sName;
	m_i32BeatsPerMin = pScene->m_i32BeatsPerMin;
	m_i32EditAccuracy = pScene->m_i32EditAccuracy;
	m_i32QNotesPerBeat = pScene->m_i32QNotesPerBeat;
	m_i32BeatsPerMeasure = pScene->m_i32BeatsPerMeasure;
	m_rMarkers = pScene->m_rMarkers;
	m_i32Time = pScene->m_i32Time;
	m_i32MusicStartTime = pScene->m_i32MusicStartTime;
	m_rLayoutColor = pScene->m_rLayoutColor;

	// Let the parent know that we were changed.
	if( !get_original() )
	{
		if( m_pParent )
			m_pParent->update_notify( this );
	}
}

PluginClass::SuperClassIdC
SceneC::get_base_class_id() const
{
	return BASECLASS_SCENE;
}

uint32
SceneC::update_notify( EditableI* pCaller )
{
	if( m_pParent )
	{
		// Simplify the notify, and let the parent just know that something changed in the scene.
		return m_pParent->update_notify( this );
	}
	return 0;
}

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

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

PajaTypes::uint32
SceneC::get_layer_count()
{
	return m_rLayers.size();
}

LayerC*
SceneC::get_layer( PajaTypes::uint32 ui32Index )
{
	assert( ui32Index >= 0 && ui32Index < m_rLayers.size() );
	return m_rLayers[ui32Index];
}

LayerC*
SceneC::add_layer()
{
	LayerC*	pLayer = (LayerC*)LayerC::create_new( this );
	if( get_undo() )
		get_undo()->add_discardable_data( pLayer, DATA_CREATED );
	m_rLayers.insert( m_rLayers.begin(), pLayer );
	return pLayer;
}

void
SceneC::del_layer( PajaTypes::uint32 ui32Index )
{
	assert( ui32Index >= 0 && ui32Index < m_rLayers.size() );
	LayerC*	pLayer = m_rLayers[ui32Index];
	if( get_undo() )
		get_undo()->add_discardable_data( pLayer, DATA_REMOVED );
	m_rLayers.erase( m_rLayers.begin() + ui32Index );
}

void
SceneC::move_layer_before( uint32 ui32Index, uint32 ui32IndexBefore )
{
	if( ui32Index == ui32IndexBefore )
		return;

	if( ui32Index < m_rLayers.size() ) {
		LayerC*	pLayer = m_rLayers[ui32Index];
		m_rLayers.erase( m_rLayers.begin() + ui32Index );

		int32	i32IndexBefore = (int32)ui32IndexBefore;

		if( i32IndexBefore > (int32)ui32Index )
			i32IndexBefore--;

		if( i32IndexBefore < 0 )
			i32IndexBefore = 0;

		if( i32IndexBefore >= (int32)m_rLayers.size() )
			m_rLayers.push_back( pLayer );
		else
			m_rLayers.insert( m_rLayers.begin() + i32IndexBefore, pLayer );
	}
}

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

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

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

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

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

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

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

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

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

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

int32
SceneC::get_music_start_time() const
{
	return m_i32MusicStartTime;
}

void
SceneC::set_music_start_time( int32 i32Time )
{
	m_i32MusicStartTime = i32Time;
}


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

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


int32
SceneC::get_layout_width()
{
	return m_i32LayoutWidth;
}

void
SceneC::set_layout_width( int32 i32Width )
{
	m_i32LayoutWidth = i32Width;
}

int32
SceneC::get_layout_height()
{
	return m_i32LayoutHeight;
}

void
SceneC::set_layout_height( int32 i32Height )
{
	m_i32LayoutHeight = i32Height;
}

const ColorC&
SceneC::get_layout_color() const
{
	return m_rLayoutColor;
}

void
SceneC::set_layout_color( const ColorC& rCol )
{
	m_rLayoutColor = rCol;
}

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

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

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

int32
SceneC::get_marker_color( uint32 ui32Index ) const
{
	if( ui32Index < m_rMarkers.size() )
		return m_rMarkers[ui32Index].m_i32Color;
	return 0;
}

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

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

void
SceneC::set_marker_color( uint32 ui32Index, int32 i32Color )
{
	if( ui32Index < m_rMarkers.size() )
		m_rMarkers[ui32Index].m_i32Color = i32Color;
}

void
SceneC::add_marker( int32 i32Time, const char* szName, int32 i32Color )
{
	// 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_i32Color = i32Color;
	rMarker.m_sName = szName;
	m_rMarkers.push_back( rMarker );
}

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


int
SceneC::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
SceneC::sort_markers()
{
	if( m_rMarkers.size() )
		qsort( &m_rMarkers[0], m_rMarkers.size(), sizeof( MarkerS ), compare_func );
}


void
SceneC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	// init effects
	for( uint32 i = 0; i < get_layer_count(); i++ ) {
		LayerC*	pLayer = get_layer( i );
		if( !pLayer )
			continue;
		for( int32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( !pEffect )
				continue;
			pEffect->initialize( ui32Reason, pInterface );
		}
	}
}

void
SceneC::set_parent( EditableI* pParent )
{
	m_pParent = pParent;
}

EditableI*
SceneC::get_parent()
{
	return m_pParent;
}

void
SceneC::reset_references()
{
	// update effect references
	for( uint32 i = 0; i < get_layer_count(); i++ ) {
		LayerC*	pLayer = get_layer( i );
		if( !pLayer )
			continue;
		for( int32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( pEffect )
				pEffect->reset_references();
		}
	}
}

void
SceneC::update_references()
{
	// update effect references
	for( uint32 i = 0; i < get_layer_count(); i++ ) {
		LayerC*	pLayer = get_layer( i );
		if( !pLayer )
			continue;
		for( int32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( !pEffect )
				continue;
			for( int32 k = 0; k < pEffect->get_gizmo_count(); k++ ) {
				GizmoI*	pGizmo = pEffect->get_gizmo( k );
				if( !pGizmo )
					continue;
				for( int32 p = 0; p < pGizmo->get_parameter_count(); p++ ) {
					ParamI*	pParam = pGizmo->get_parameter( p );
					if( !pParam )
						continue;
					if( pParam->get_type() == PARAM_TYPE_FILE ) {
						ParamFileC*	pFileParam = (ParamFileC*)pParam;
						pFileParam->update_file_references();
					}
					else if( pParam->get_type() == PARAM_TYPE_LINK ) {
						ParamLinkC*	pLinkParam = (ParamLinkC*)pParam;
						pLinkParam->update_link_references();
					}
				}
			}
		}
	}
}


uint32
SceneC::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_SCENE_BASE, SCENE_VERSION );
		// name
		ui32Error = pSave->write_str( sStr.c_str() );
		// layout width
		ui32Error = pSave->write( &m_i32LayoutWidth, sizeof( m_i32LayoutWidth ) );
		// layout height
		ui32Error = pSave->write( &m_i32LayoutHeight, sizeof( m_i32LayoutHeight ) );
		// 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 ) );
	pSave->end_chunk();

	// music start time
	pSave->begin_chunk( CHUNK_SCENE_MUSICSTART, SCENE_VERSION );
		ui32Error = pSave->write( &m_i32MusicStartTime, sizeof( m_i32MusicStartTime ) );
	pSave->end_chunk();

	// layout color
	pSave->begin_chunk( CHUNK_SCENE_LAYOUTCOLOR, SCENE_VERSION );
		float32	f32Col[4];
		f32Col[0] = m_rLayoutColor[0];
		f32Col[1] = m_rLayoutColor[1];
		f32Col[2] = m_rLayoutColor[2];
		f32Col[3] = m_rLayoutColor[3];
		ui32Error = pSave->write( f32Col, sizeof( f32Col ) );
	pSave->end_chunk();

	//
	// save layers
	//
	for( i = 0; i < m_rLayers.size(); i++ ) {
		pSave->begin_chunk( CHUNK_SCENE_LAYER, SCENE_VERSION );
			m_rLayers[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_SCENE_MARKER, SCENE_VERSION );
			ui32Error = pSave->write_str( sName.c_str() );
			ui32Error = pSave->write( &m_rMarkers[i].m_i32Time, sizeof( int32 ) );
			ui32Error = pSave->write( &m_rMarkers[i].m_i32Color, sizeof( int32 ) );
		pSave->end_chunk();
	}

	return ui32Error;
}

uint32
SceneC::load( LoadC* pLoad )
{
	return load( pLoad, false );
}

uint32
SceneC::load( LoadC* pLoad, bool bMerge )
{
	uint32	ui32Error = IO_OK;
	char	szStr[256];

	FactoryC*	pFactory = pLoad->get_factory();

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_SCENE_BASE:
			{
				if( !bMerge ) {
					if( pLoad->get_chunk_version() <= SCENE_VERSION ) {
						// name
						ui32Error = pLoad->read_str( szStr );
						m_sName = szStr;
						// layout width
						ui32Error = pLoad->read( &m_i32LayoutWidth, sizeof( m_i32LayoutWidth ) );
						// layout height
						ui32Error = pLoad->read( &m_i32LayoutHeight, sizeof( m_i32LayoutHeight ) );
						// 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 ) );
					}
				}
			}
			break;

		case CHUNK_SCENE_MUSICSTART:
			{
				if( pLoad->get_chunk_version() <= SCENE_VERSION ) {
					// music start
					ui32Error = pLoad->read( &m_i32MusicStartTime, sizeof( m_i32MusicStartTime ) );
				}
			}
			break;

		case CHUNK_SCENE_LAYOUTCOLOR:
			{
				if( pLoad->get_chunk_version() <= SCENE_VERSION ) {
					float32	f32Col[4];
					ui32Error = pLoad->read( f32Col, sizeof( f32Col ) );
					m_rLayoutColor[0] = f32Col[0];
					m_rLayoutColor[1] = f32Col[1];
					m_rLayoutColor[2] = f32Col[2];
					m_rLayoutColor[3] = f32Col[3];
				}
			}
			break;


		case CHUNK_SCENE_LAYER:
			{
				if( pLoad->get_chunk_version() <= SCENE_VERSION ) {
					LayerC*	pLayer = LayerC::create_new( this );
					m_rLayers.push_back( pLayer );
					ui32Error = pLayer->load( pLoad );
				}
			}
			break;

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

					add_marker( i32Time, szStr, 0 );
				}
				else if( pLoad->get_chunk_version() == SCENE_VERSION ) {
					int32	i32Time, i32Color;
					ui32Error = pLoad->read_str( szStr );
					ui32Error = pLoad->read( &i32Time, sizeof( i32Time ) );
					ui32Error = pLoad->read( &i32Color, sizeof( i32Color ) );

					add_marker( i32Time, szStr, i32Color );
				}
			}
			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;
}
