//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "movieobjects/dmechannel.h"
#include "movieobjects/dmelog.h"
#include "movieobjects/dmeclip.h"
#include "datamodel/dmelementfactoryhelper.h"
#include "datamodel/dmehandle.h"
#include "datamodel/dmattribute.h"
#include "tier0/vprof.h"
#include "tier1/KeyValues.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


//-----------------------------------------------------------------------------
// 
// CDmeChannelRecordingMgr
//
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
static CDmeChannelRecordingMgr s_ChannelRecordingMgr;
CDmeChannelRecordingMgr *g_pChannelRecordingMgr = &s_ChannelRecordingMgr;


//-----------------------------------------------------------------------------
// Constructor 
//-----------------------------------------------------------------------------
CDmeChannelRecordingMgr::CDmeChannelRecordingMgr()
{
	m_bActive = false;
	m_bSavedUndoState = false;
	m_bUseTimeSelection = false;
	m_nRevealType = PROCEDURAL_PRESET_NOT;
	m_pRevealTarget = NULL;
}


//-----------------------------------------------------------------------------
// Activates, deactivates layer recording.
//-----------------------------------------------------------------------------
void CDmeChannelRecordingMgr::StartLayerRecording( const char *pUndoRedoDesc, const DmeLog_TimeSelection_t *pTimeSelection )
{
	g_pDataModel->StartUndo( pUndoRedoDesc, pUndoRedoDesc );
	m_bSavedUndoState = g_pDataModel->IsUndoEnabled();
	g_pDataModel->SetUndoEnabled( false );

	Assert( !m_bActive );
	Assert( m_LayerChannels.Count() == 0 );
	m_LayerChannels.Purge();
	m_bActive = true;
	m_bUseTimeSelection = ( pTimeSelection != NULL );
	if ( pTimeSelection )
	{
		m_TimeSelection = *pTimeSelection;
	}
	else
	{
		// Slam to default value
		m_TimeSelection = DmeLog_TimeSelection_t();
	}
	m_TimeSelection.ResetTimeAdvancing();
}

void CDmeChannelRecordingMgr::FinishLayerRecording( float flThreshhold, bool bFlattenLayers /*=true*/ )
{
	Assert( m_bActive );

	RemoveAllChannelsFromRecordingLayer();
	m_bUseTimeSelection = false;
	m_TimeSelection.ResetTimeAdvancing();

	g_pDataModel->SetUndoEnabled( m_bSavedUndoState );
	if ( bFlattenLayers )
	{
		FlattenLayers( flThreshhold );
	}
	g_pDataModel->FinishUndo();

	m_bActive = false;
	m_LayerChannels.Purge();
	m_nRevealType = PROCEDURAL_PRESET_NOT;
	m_pRevealTarget = NULL;
	m_PasteTarget.RemoveAll();
}


//-----------------------------------------------------------------------------
// Adds a channel to the recording layer
//-----------------------------------------------------------------------------
void CDmeChannelRecordingMgr::AddChannelToRecordingLayer( CDmeChannel *pChannel, CDmeClip *pRoot, CDmeClip *pShot )
{
	Assert( pChannel->m_nRecordLayerIndex == -1 );

	CDmeLog *pLog = pChannel->GetLog();
	if ( !pLog )
		return;

	int nRecordLayerIndex = m_LayerChannels.AddToTail();
	LayerChannelInfo_t& info = m_LayerChannels[nRecordLayerIndex];
	info.m_Channel = pChannel;
	if ( pRoot )
	{
		if ( !pChannel->BuildClipStack( &info.m_ClipStack, pRoot, pShot ) )
		{
			m_LayerChannels.Remove( nRecordLayerIndex );
			return;
		}
	}

	pChannel->m_nRecordLayerIndex = nRecordLayerIndex;

	// This operation is undoable
	CEnableUndoScopeGuard guard;
	pLog->AddNewLayer();
	pChannel->SetMode( CM_RECORD );
}


//-----------------------------------------------------------------------------
// Removes all channels from the recording layer
//-----------------------------------------------------------------------------
void CDmeChannelRecordingMgr::RemoveAllChannelsFromRecordingLayer( )
{
	int c = m_LayerChannels.Count();
	for ( int i = 0 ; i < c; ++i )
	{
		CDmeChannel *pChannel = m_LayerChannels[ i ].m_Channel.Get();
		if ( !pChannel )
			continue;

		CDmeLog *pLog = pChannel->GetLog();
		if ( pLog && IsUsingTimeSelection() )
		{
			// Computes local times for the time selection
			DmeLog_TimeSelection_t timeSelection;
			GetLocalTimeSelection( timeSelection, pChannel->m_nRecordLayerIndex );
			pLog->FinishTimeSelection( pChannel->GetCurrentTime(), timeSelection );
		}
		pChannel->m_nRecordLayerIndex = -1;
		pChannel->SetMode( CM_PLAY );
	}
}


//-----------------------------------------------------------------------------
// Flattens recorded layers into the base layer
//-----------------------------------------------------------------------------
void CDmeChannelRecordingMgr::FlattenLayers( float flThreshhold )
{
	int nFlags = 0;
	if ( IsUsingDetachedTimeSelection() && IsTimeAdvancing() )
	{
		nFlags |= CDmeLog::FLATTEN_NODISCONTINUITY_FIXUP;
	}

	int c = m_LayerChannels.Count();
	for ( int i = 0 ; i < c; ++i )
	{
		CDmeChannel *pChannel = m_LayerChannels[ i ].m_Channel.Get();
		if ( !pChannel )
			continue;

		CDmeLog *pLog = pChannel->GetLog();
		Assert( pLog );
		if ( !pLog )
			continue;

		pLog->FlattenLayers( flThreshhold, nFlags );
	}
}


//-----------------------------------------------------------------------------
// Used to iterate over all channels currently being recorded
//-----------------------------------------------------------------------------
int CDmeChannelRecordingMgr::GetLayerRecordingChannelCount()
{
	return m_LayerChannels.Count();
}

CDmeChannel* CDmeChannelRecordingMgr::GetLayerRecordingChannel( int nIndex )
{
	return m_LayerChannels[nIndex].m_Channel.Get();
}


//-----------------------------------------------------------------------------
// Computes time selection info in log time for a particular recorded channel
//-----------------------------------------------------------------------------
void CDmeChannelRecordingMgr::GetLocalTimeSelection( DmeLog_TimeSelection_t& selection, int nIndex )
{
	Assert( m_bUseTimeSelection );
	LayerChannelInfo_t& info = m_LayerChannels[nIndex];
	selection = m_TimeSelection;
	for ( int i = 0; i < TS_TIME_COUNT; ++i )
	{
		selection.m_nTimes[i] = CDmeClip::ToChildMediaTime( info.m_ClipStack, selection.m_nTimes[i], false );
	}
	selection.m_pPresetValue = info.m_pPresetValue;
}


//-----------------------------------------------------------------------------
// Methods which control various aspects of recording
//-----------------------------------------------------------------------------
void CDmeChannelRecordingMgr::UpdateTimeAdvancing( bool bPaused, DmeTime_t tCurTime )
{
	Assert( m_bActive && m_bUseTimeSelection );
	if ( !bPaused && !m_TimeSelection.IsTimeAdvancing() )
	{
		m_TimeSelection.StartTimeAdvancing();

		// blow away logs after curtime
		int nCount = m_LayerChannels.Count();
		for ( int i = 0; i < nCount; ++i )
		{
			LayerChannelInfo_t& info = m_LayerChannels[i];
			DmeTime_t t = CDmeClip::ToChildMediaTime( info.m_ClipStack, tCurTime, false );
			info.m_Channel->GetLog()->RemoveKeys( t, DMETIME_MAXTIME );
		}
	}
}

void CDmeChannelRecordingMgr::UpdateRecordingTimeSelectionTimes( const DmeLog_TimeSelection_t& timeSelection )
{
	Assert( m_bActive );
	for ( int i = 0; i < TS_TIME_COUNT; ++i )
	{
		m_TimeSelection.m_nTimes[i] = timeSelection.m_nTimes[i];
	}
	m_TimeSelection.m_nResampleInterval = timeSelection.m_nResampleInterval;
}

void CDmeChannelRecordingMgr::SetIntensityOnAllLayers( float flIntensity )
{
	m_TimeSelection.m_flIntensity = flIntensity;
}

void CDmeChannelRecordingMgr::SetRecordingMode( RecordingMode_t mode )
{
	m_TimeSelection.SetRecordingMode( mode );
}

void CDmeChannelRecordingMgr::SetPresetValue( CDmeChannel* pChannel, CDmAttribute *pPresetValue )
{
	Assert( pChannel->m_nRecordLayerIndex != -1 );
	m_LayerChannels[ pChannel->m_nRecordLayerIndex ].m_pPresetValue = pPresetValue;
}


//-----------------------------------------------------------------------------
// Methods to query aspects of recording
//-----------------------------------------------------------------------------
bool CDmeChannelRecordingMgr::IsUsingDetachedTimeSelection() const
{
	Assert( m_bActive );
	return !m_TimeSelection.m_bAttachedMode;
}

bool CDmeChannelRecordingMgr::IsTimeAdvancing() const
{
	Assert( m_bActive );
	return m_TimeSelection.IsTimeAdvancing();
}

bool CDmeChannelRecordingMgr::IsUsingTimeSelection() const
{
	return m_bUseTimeSelection;
}

bool CDmeChannelRecordingMgr::ShouldRecordUsingTimeSelection() const
{
	return m_bUseTimeSelection && m_bActive;
}

void CDmeChannelRecordingMgr::SetProceduralTarget( int nProceduralMode, const CDmAttribute *pTarget )
{
	m_nRevealType = nProceduralMode;
	m_pRevealTarget = pTarget;
	m_PasteTarget.RemoveAll();
}

void CDmeChannelRecordingMgr::SetProceduralTarget( int nProceduralMode, const CUtlVector< KeyValues * >& list )
{
	m_nRevealType = nProceduralMode;
	m_pRevealTarget = NULL;
	m_PasteTarget.RemoveAll();
	for ( int i = 0; i < list.Count(); ++i )
	{
		m_PasteTarget.AddToTail( list[ i ] );
	}
}

int CDmeChannelRecordingMgr::GetProceduralType() const
{
	return m_nRevealType;
}

const CDmAttribute *CDmeChannelRecordingMgr::GetProceduralTarget() const
{
	Assert( m_pRevealTarget );
	return m_pRevealTarget;
}

const CUtlVector< KeyValues * > &CDmeChannelRecordingMgr::GetPasteTarget() const
{
	return m_PasteTarget;
}

//-----------------------------------------------------------------------------
// Expose this class to the scene database 
//-----------------------------------------------------------------------------
IMPLEMENT_ELEMENT_FACTORY( DmeChannel, CDmeChannel );


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CDmeChannel::OnConstruction()
{
	m_nRecordLayerIndex		= -1;
	m_nNextCurveType		= CURVE_DEFAULT;
	m_tCurrentTime			= DMETIME_INVALID;
	m_tPreviousTime			= DMETIME_INVALID;
	m_timeOutsideTimeframe	= DMETIME_INVALID;

	m_fromElement	.Init( this, "fromElement",		FATTRIB_HAS_CALLBACK | FATTRIB_NEVERCOPY );
	m_fromAttribute	.Init( this, "fromAttribute",	FATTRIB_TOPOLOGICAL | FATTRIB_HAS_CALLBACK );
	m_fromIndex		.Init( this, "fromIndex",		FATTRIB_TOPOLOGICAL );
	m_toElement		.Init( this, "toElement",		FATTRIB_HAS_CALLBACK | FATTRIB_NEVERCOPY );
	m_toAttribute	.Init( this, "toAttribute",		FATTRIB_TOPOLOGICAL | FATTRIB_HAS_CALLBACK );
	m_toIndex		.Init( this, "toIndex",			FATTRIB_TOPOLOGICAL );
	m_mode			.InitAndSet( this, "mode", (int)CM_PASS );
	m_log			.Init( this, "log" );
	m_FromAttributeHandle	= DMATTRIBUTE_HANDLE_INVALID;
	m_ToAttributeHandle		= DMATTRIBUTE_HANDLE_INVALID;
}

void CDmeChannel::OnDestruction()
{
}

int CDmeChannel::GetFromArrayIndex() const
{
	return m_fromIndex;
}

int CDmeChannel::GetToArrayIndex() const
{
	return m_toIndex;
}

void CDmeChannel::Play()
{
	CDmAttribute *pToAttr = GetToAttribute();

	if ( pToAttr == NULL )
		return;

	CDmeLog *pLog = GetLog();
	if ( !pLog )
		return;

	DmeTime_t time = GetCurrentTime();

	DmeTime_t t0 = pLog->GetBeginTime();
	DmeTime_t tn = pLog->GetEndTime();

	PlayMode_t pmode = PM_HOLD;
	switch ( pmode )
	{
	case PM_HOLD:
		time = clamp( time, t0, tn );
		break;

	case PM_LOOP:
		if ( tn == t0 )
		{
			time = t0;
		}
		else
		{
			time -= t0;
			time = time % ( tn - t0 );
			time += t0;
		}
		break;
	}

	// We might not want to do it this way, but this makes empty logs not get in the way if there is a valid pFromAttr
#if 1
	if ( pLog->IsEmpty() && !pLog->HasDefaultValue() &&
		GetFromAttribute() != NULL )
	{
		Pass();
		return;
	}
#endif

	pLog->GetValue( time, pToAttr, m_toIndex.Get() );
}

void CDmeChannel::Pass()
{
	CDmAttribute *pFromAttr = GetFromAttribute();
	CDmAttribute *pToAttr = GetToAttribute();
	if ( !pFromAttr || !pToAttr )
		return;

	if ( pFromAttr == pToAttr )
		return;

	DmAttributeType_t type = pFromAttr->GetType();
	const void *pValue = NULL;
	if ( IsArrayType( type ) )
	{
		CDmrGenericArray array( pFromAttr );
		pValue = array.GetUntyped( m_fromIndex.Get() );
		type = ArrayTypeToValueType( type );
	}
	else
	{
		pValue = pFromAttr->GetValueUntyped();
	}

	if ( IsArrayType( pToAttr->GetType() ) )
	{
		CDmrGenericArray array( pToAttr );
		array.Set( m_toIndex.Get(), type, pValue );
	}
	else
	{
		pToAttr->SetValue( type, pValue );
	}
}

//-----------------------------------------------------------------------------
// IsDirty - ie needs to operate
//-----------------------------------------------------------------------------
bool CDmeChannel::IsDirty()
{
	if ( BaseClass::IsDirty() )
		return true;

	switch( GetMode() )
	{
	case CM_PLAY:
		return true;

	case CM_RECORD:
		if ( m_nRecordLayerIndex != -1 )
			return true;

		// NOTE: Fall through!
	case CM_PASS:
		{
			CDmAttribute *pFromAttr = GetFromAttribute();
			if ( pFromAttr && pFromAttr->IsFlagSet( FATTRIB_OPERATOR_DIRTY ) )
				return true;
		}
		break;

	default:
		break;
	}
	return false;
}


void CDmeChannel::Operate()
{
	VPROF( "CDmeChannel::Operate" );

	switch ( GetMode() )
	{
	case CM_OFF:
		return;

	case CM_PLAY:
		Play();
		return;

	case CM_RECORD:
		Record();
		return;

	case CM_PASS:
		Pass();
		return;
	}
}

void CDmeChannel::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs )
{
	ChannelMode_t mode = GetMode();
	if ( mode == CM_OFF || mode == CM_PLAY )
		return; // off and play ignore inputs

	CDmAttribute *pAttr = GetFromAttribute();
	if ( pAttr != NULL )
	{
		attrs.AddToTail( pAttr );
	}
}

void CDmeChannel::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs )
{
	ChannelMode_t mode = GetMode();
	if ( mode == CM_OFF )
		return; // off ignores inputs

	if ( mode == CM_RECORD || mode == CM_PASS )
	{
		if ( GetFromAttribute() == GetToAttribute() )
			return; // record/pass from and to the same attribute doesn't write anything
	}

	CDmAttribute *pAttr = GetToAttribute();
	if ( pAttr != NULL )
	{
		attrs.AddToTail( pAttr );
	}
}


//-----------------------------------------------------------------------------
// accessors
//-----------------------------------------------------------------------------
CDmElement *CDmeChannel::GetFromElement() const
{
	return m_fromElement;
}

CDmElement *CDmeChannel::GetToElement() const
{
	return m_toElement;
}

void CDmeChannel::SetLog( CDmeLog *pLog )
{
	m_log = pLog;
}

CDmeLog *CDmeChannel::CreateLog( DmAttributeType_t type )
{
	CDmeLog *log = CDmeLog::CreateLog( type, GetFileId() );
	m_log.Set( log );
	return log;
}

// HACK:  This is an evil hack since the element and attribute change sequentially, but they really need to change in lockstep or else you're looking
//  up an attribute from some other element or vice versa.


void CDmeChannel::SetInput( CDmElement* pElement, const char* pAttribute, int index )
{
	m_fromElement.Set( pElement );
	m_fromAttribute.Set( pAttribute );
	m_fromIndex.Set( index );
	SetupFromAttribute();
}

void CDmeChannel::SetOutput( CDmElement* pElement, const char* pAttribute, int index )
{
	m_toElement.Set( pElement );
	m_toAttribute.Set( pAttribute );
	m_toIndex.Set( index );
	SetupToAttribute();
}

void CDmeChannel::SetInput( CDmAttribute *pAttribute, int index )
{
	if ( pAttribute )
	{
		SetInput( pAttribute->GetOwner(), pAttribute->GetName(), index );
	}
	else
	{
		SetInput( NULL, "", index );
	}
}

void CDmeChannel::SetOutput( CDmAttribute *pAttribute, int index )
{
	if ( pAttribute )
	{
		SetOutput( pAttribute->GetOwner(), pAttribute->GetName(), index );
	}
	else
	{
		SetOutput( NULL, "", index );
	}
}


ChannelMode_t CDmeChannel::GetMode()
{
	return static_cast< ChannelMode_t >( m_mode.Get() );
}

void CDmeChannel::SetMode( ChannelMode_t mode )
{
	if ( mode != m_mode )
	{
		m_mode.Set( static_cast< int >( mode ) );
		m_tPreviousTime = DMETIME_INVALID;
	}
}

void CDmeChannel::ClearLog()
{
	GetLog()->ClearKeys();
}

CDmeLog *CDmeChannel::GetLog()
{
	if ( !m_log.GetElement() && ( m_FromAttributeHandle == DMATTRIBUTE_HANDLE_INVALID ) )
	{
		// NOTE: This will generate a new log based on the from attribute
		SetupFromAttribute();
	}
	return m_log.GetElement();
}


//-----------------------------------------------------------------------------
// Used to cache off handles to attributes
//-----------------------------------------------------------------------------
CDmAttribute *CDmeChannel::SetupFromAttribute()
{
	m_FromAttributeHandle = DMATTRIBUTE_HANDLE_INVALID;

	CDmElement *pObject = m_fromElement.GetElement();
	const char *pName = m_fromAttribute.Get();
	if ( pObject == NULL || pName == NULL || !pName[0] )
		return NULL;

	CDmAttribute *pAttr = pObject->GetAttribute( pName );
	if ( !pAttr )
		return NULL;

	m_FromAttributeHandle = pAttr->GetHandle();

	DmAttributeType_t fromType = pAttr->GetType();
	if ( IsArrayType( fromType ) )
	{
		fromType = ArrayTypeToValueType( fromType );
	}

	CDmeLog *pLog = m_log.GetElement();
	if ( pLog == NULL )
	{
		CreateLog( fromType );
		return pAttr;
	}

	DmAttributeType_t logType = pLog->GetDataType();
	if ( IsArrayType( logType ) )
	{
		logType = ArrayTypeToValueType( logType );
	}

	if ( logType != fromType )
	{
		// NOTE: This will release the current log
		CreateLog( fromType );
	}

	return pAttr;
}

CDmAttribute *CDmeChannel::SetupToAttribute()
{
	m_ToAttributeHandle = DMATTRIBUTE_HANDLE_INVALID;

	CDmElement *pObject = m_toElement.GetElement();
	const char *pName = m_toAttribute.Get();
	if ( pObject == NULL || pName == NULL || !pName[0] )
		return NULL;

	CDmAttribute *pAttr = pObject->GetAttribute( pName );
	if ( !pAttr )
		return NULL;

	m_ToAttributeHandle = pAttr->GetHandle();
	return pAttr;
}


//-----------------------------------------------------------------------------
// This function gets called whenever an attribute changes
//-----------------------------------------------------------------------------
void CDmeChannel::OnAttributeChanged( CDmAttribute *pAttribute )
{
	if ( ( pAttribute == m_fromElement  .GetAttribute() ) ||
		( pAttribute == m_fromAttribute.GetAttribute() ) )
	{
		// NOTE: This will force a recache of the attribute handle
		m_FromAttributeHandle = DMATTRIBUTE_HANDLE_INVALID;
		return;
	}

	if ( ( pAttribute == m_toElement  .GetAttribute() ) ||
		( pAttribute == m_toAttribute.GetAttribute() ) )
	{
		m_ToAttributeHandle = DMATTRIBUTE_HANDLE_INVALID;
		return;
	}

	BaseClass::OnAttributeChanged( pAttribute );
}


DmeTime_t CDmeChannel::GetCurrentTime() const
{
	return m_tCurrentTime;
}

//-----------------------------------------------------------------------------
// Simple version. Only works if multiple active channels clips
// do not reference the same channels
//-----------------------------------------------------------------------------
void CDmeChannel::SetCurrentTime( DmeTime_t time )
{
	m_tPreviousTime = m_tCurrentTime;
	m_tCurrentTime = time;
	m_timeOutsideTimeframe = DMETIME_ZERO;
}

//-----------------------------------------------------------------------------
// SetCurrentTime sets the current time on the clip,
// choosing the time closest to (and after) a timeframe if multiple sets in a frame
//-----------------------------------------------------------------------------
void CDmeChannel::SetCurrentTime( DmeTime_t time, DmeTime_t start, DmeTime_t end )
{ 
	m_tPreviousTime = m_tCurrentTime;

	DmeTime_t dt( 0 );
	if ( time < start )
	{
		dt = time - start;
		time = start;
	}
	else if ( time >= end )
	{
		dt = time - end;
		time = end;
	}
	DmeTime_t totf = m_timeOutsideTimeframe;

	const DmeTime_t t0( 0 );
	if ( ( dt < t0 && totf < t0 && dt < totf ) ||	// both prior to clip, old totf closer
		( dt < t0 && totf >= t0 ) ||				// new dt prior to clip, old totf in or after
		( dt >= t0 && totf >= t0 && dt > totf ) )	// both after clip, old totf closer
		return; // if old todt is a better match, don't update channel time

	m_tCurrentTime = time;
	m_timeOutsideTimeframe = dt;
}

//-----------------------------------------------------------------------------
// ClearTimeMetric marks m_timeOutsideTimeframe invalid (-inf is the worst possible match)
//-----------------------------------------------------------------------------
void CDmeChannel::ClearTimeMetric()
{
	m_timeOutsideTimeframe = DmeTime_t::MinTime();
}

void CDmeChannel::SetChannelToPlayToSelf( const char *outputAttributeName, float defaultValue, bool force /*= false*/ )
{
	if ( !HasAttribute( outputAttributeName ) )
	{
		AddAttribute( outputAttributeName, AT_FLOAT );
	}

	CDmeTypedLog< bool > *log = static_cast< CDmeTypedLog< bool >* >( GetLog() );
	// Usually we won't put it into playback if it's empty, we'll just read the default value continously
	if ( force || ( log && !log->IsEmpty() && !log->HasDefaultValue() ) )
	{
		SetMode( CM_PLAY );
		SetOutput( this, outputAttributeName );
	}
	SetValue( outputAttributeName, defaultValue );
}

void CDmeChannel::SetNextKeyCurveType( int nCurveType )
{
	m_nNextCurveType = nCurveType;
}

CDmeLogLayer *FindLayerInSnapshot( const CDmrElementArray<CDmElement>& snapshotArray, CDmeLog *origLog )
{
	if ( !snapshotArray.IsValid() )
		return NULL;

	int c = snapshotArray.Count();
	for ( int i = 0; i < c; ++i )
	{
		CDmeLogLayer *layer = CastElement< CDmeLogLayer >( snapshotArray[ i ] );
		if ( !layer )
			continue;

		CDmeLog *pLog = layer->GetValueElement< CDmeLog >( "origLog" );
		if ( !pLog )
		{
			Assert( 0 );
			continue;
		}

		if ( pLog == origLog )
			return layer;
	}

	return NULL;
}

KeyValues *FindLayerInPasteData( const CUtlVector< KeyValues * > &list, CDmeLog *log )
{
	int c = list.Count();
	for ( int i = 0; i  < c; ++i )
	{
		CDisableUndoScopeGuard noundo;

		KeyValues *kv = list[ i ];
		Assert( kv );

		if ( Q_stricmp( kv->GetName(), "ControlLayers" ) )
			continue;

		LayerSelectionData_t *data = reinterpret_cast< LayerSelectionData_t * >( kv->GetPtr( "LayerData" ) );
		if ( !data )
			continue;

		CDmeChannel *ch = data->m_hChannel;
		if ( !ch )
			continue;

		CDmeLog *chLog = ch->GetLog();
		if ( chLog == log )
			return kv;
	}

	return NULL;
}

static int FindSpanningLayerAndSetIntensity( DmeLog_TimeSelection_t &ts, LayerSelectionData_t *data )
{
	Assert( data->m_vecData.Count() >= 2 );

	float frac = ts.m_flIntensity;
	int i = 0;
	for ( ; i < data->m_vecData.Count() - 1; ++i )
	{
		LayerSelectionData_t::DataLayer_t *current = &data->m_vecData[ i ];
		LayerSelectionData_t::DataLayer_t *next = &data->m_vecData[ i + 1 ];

		if ( frac >= current->m_flStartFraction && frac <= next->m_flStartFraction )
		{
			frac = RemapVal( frac, current->m_flStartFraction, next->m_flStartFraction, 0.0f, 1.0f );
			ts.m_flIntensity = frac;
			break;
		}
	}

	return i;
}

void CDmeChannel::Record()
{
	VPROF( "CDmeChannel::Record" );

	CDmAttribute *pFromAttr = GetFromAttribute();
	if ( pFromAttr == NULL )
		return; // or clear out the log?

	CDmeLog *pLog = GetLog();
	DmeTime_t time = GetCurrentTime();
	if ( m_tPreviousTime == DMETIME_INVALID )
	{
		m_tPreviousTime = time;
	}

	if ( g_pChannelRecordingMgr->ShouldRecordUsingTimeSelection() )
	{
		Assert( m_nRecordLayerIndex != -1 );

		// Computes local times for the time selection
		DmeLog_TimeSelection_t timeSelection;
		g_pChannelRecordingMgr->GetLocalTimeSelection( timeSelection, m_nRecordLayerIndex );

		int nType = g_pChannelRecordingMgr->GetProceduralType();
		switch ( nType )
		{
		default:
		case PROCEDURAL_PRESET_NOT:
			{
				pLog->StampKeyAtHead( time, m_tPreviousTime, timeSelection, pFromAttr, m_fromIndex.Get() );
			}
			break;
		case PROCEDURAL_PRESET_REVEAL:
			{
				// Find the matching layer in the "target" data array
				const CDmrElementArray<CDmElement> snapshotArray = const_cast< CDmAttribute * >( g_pChannelRecordingMgr->GetProceduralTarget() );
				CDmeLogLayer *snapshotLayer = FindLayerInSnapshot( snapshotArray, pLog );
				if ( snapshotLayer )
				{
					Assert( pLog );
					pLog->RevealUsingTimeSelection( timeSelection, snapshotLayer );
				}
			}
			break;
		case PROCEDURAL_PRESET_JITTER:
		case PROCEDURAL_PRESET_SMOOTH:
		case PROCEDURAL_PRESET_SHARPEN:
		case PROCEDURAL_PRESET_SOFTEN:
		case PROCEDURAL_PRESET_STAGGER:
		case PROCEDURAL_PRESET_PASTE:
			{
				const CUtlVector< KeyValues * > &pasteTarget = g_pChannelRecordingMgr->GetPasteTarget();
				KeyValues *layer = FindLayerInPasteData( pasteTarget, pLog );
				if ( layer )
				{
					LayerSelectionData_t *data = reinterpret_cast< LayerSelectionData_t * >( layer->GetPtr( "LayerData" ) );
					Assert( data );

					int iSourceLayer = FindSpanningLayerAndSetIntensity( timeSelection, data );

					CDmeLogLayer *sourceLayer = data->m_vecData[ iSourceLayer ].m_hData.Get();
					CDmeLogLayer *targetLayer = data->m_vecData[ iSourceLayer + 1 ].m_hData.Get();
					if ( sourceLayer && sourceLayer->GetKeyCount() > 0 &&
						 targetLayer && targetLayer->GetKeyCount() > 0 &&
						 sourceLayer->GetKeyCount() == targetLayer->GetKeyCount() )
					{
						Assert( pLog->GetNumLayers() >= 2 );
						CDmeLogLayer *outputLayer = pLog->GetLayer( pLog->GetTopmostLayer() );
						if ( nType == PROCEDURAL_PRESET_STAGGER )
						{
							pLog->BlendTimesUsingTimeSelection( sourceLayer, targetLayer, outputLayer, timeSelection, data->m_tStartOffset );
						}
						else
						{
							pLog->BlendLayersUsingTimeSelection( sourceLayer, targetLayer, outputLayer, timeSelection, false, data->m_tStartOffset );
						}
					}
				}
			}
			break;
		}
	}
	else
	{
		if ( m_tPreviousTime != time )
		{
			pLog->SetDuplicateKeyAtTime( m_tPreviousTime );
		}
		pLog->SetKey( time, pFromAttr, m_fromIndex.Get(), m_nNextCurveType );
		m_nNextCurveType = CURVE_DEFAULT;
	}

	// Output the data that's in the log
	Play();
}


//-----------------------------------------------------------------------------
// Builds a clip stack that passes through root + shot
// Returns true if it succeeded
//-----------------------------------------------------------------------------
bool CDmeChannel::BuildClipStack( DmeClipStack_t *pClipStack, CDmeClip *pRoot, CDmeClip *pShot )
{
	DmAttributeReferenceIterator_t it;
	for ( it = g_pDataModel->FirstAttributeReferencingElement( GetHandle() );
		it != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID;
		it = g_pDataModel->NextAttributeReferencingElement( it ) )
	{
		CDmAttribute *pAttribute = g_pDataModel->GetAttribute( it );
		CDmElement *pElement = pAttribute->GetOwner();
		CDmeChannelsClip *pChannelsClip = CastElement< CDmeChannelsClip >( pElement );
		if ( !pChannelsClip )
			continue;

		if ( pChannelsClip->BuildClipStack( pClipStack, pRoot, pShot ) )
			return true;
	}
	return false;
}


//-----------------------------------------------------------------------------
// Finds the owner clip for a channel which passes through the root
//-----------------------------------------------------------------------------
CDmeClip* CDmeChannel::FindOwnerClipForChannel( CDmeClip *pRoot )
{
	DmeClipStack_t stack;
	if ( BuildClipStack( &stack, pRoot, pRoot ) )
		return stack[ stack.Count() - 1 ];
	return NULL;
}