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

#include "dme_controls/BaseAnimationSetEditor.h"
#include "tier1/KeyValues.h"
#include "tier2/fileutils.h"
#include "vgui_controls/Splitter.h"
#include "vgui_controls/Menu.h"
#include "vgui_controls/Label.h"
#include "vgui_controls/ToggleButton.h"
#include "vgui_controls/ComboBox.h"
#include "vgui_controls/FileOpenDialog.h"
#include "vgui_controls/MessageBox.h"
#include "vgui_controls/perforcefilelistframe.h"
#include "studio.h"
#include "dme_controls/BaseAnimSetAttributeSliderPanel.h"
#include "dme_controls/BaseAnimSetPresetFaderPanel.h"
#include "dme_controls/BaseAnimSetControlGroupPanel.h"
#include "dme_controls/dmecontrols_utils.h"
#include "dme_controls/dmepicker.h"

#include "sfmobjects/exportfacialanimation.h"

#include "movieobjects/dmechannel.h"
#include "movieobjects/dmeanimationset.h"
#include "movieobjects/dmeclip.h"
#include "movieobjects/dmeanimationlist.h"
#include "movieobjects/dmegamemodel.h"


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

using namespace vgui;

#define ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT 38
#define ANIMATION_SET_EDITOR_BUTTONTRAY_YPOS 12
#define ANIMATION_SET_BUTTON_INSET 0

struct AnimSetLayout_t
{
	CBaseAnimationSetEditor::EAnimSetLayout_t type;
	const char *shortname;
	const char *contextmenulabel;
};

static AnimSetLayout_t g_AnimSetLayout[] = 
{
	{	CBaseAnimationSetEditor::LAYOUT_SPLIT,			"split",		"#BxAnimSetSplitLayout" },
	{	CBaseAnimationSetEditor::LAYOUT_VERTICAL,		"vertical",		"#BxAnimSetVerticalLayout" },
	{	CBaseAnimationSetEditor::LAYOUT_HORIZONTAL,		"horizontal",	"#BxAnimSetHorizontalLayout" },
};

static const char *NameForLayout( CBaseAnimationSetEditor::EAnimSetLayout_t layout, bool menu )
{
	int c = ARRAYSIZE( g_AnimSetLayout );
	for ( int i = 0; i < c; ++i )
	{
		const AnimSetLayout_t& data = g_AnimSetLayout[ i ];
		if ( data.type == layout )
		{
			return menu ? data.contextmenulabel : data.shortname;
		}
	}
	Assert( 0 );
	return menu ? g_AnimSetLayout[ 0 ].contextmenulabel : g_AnimSetLayout[ 0 ].shortname;
}

static CBaseAnimationSetEditor::EAnimSetLayout_t LayoutForName( const char *name )
{
	int c = ARRAYSIZE( g_AnimSetLayout );
	for ( int i = 0; i < c; ++i )
	{
		const AnimSetLayout_t& data = g_AnimSetLayout[ i ];
		if ( !Q_stricmp( data.shortname, name ) )
		{
			return data.type;
		}
	}

	Assert( 0 );
	return CBaseAnimationSetEditor::LAYOUT_SPLIT;
}

CBaseAnimationSetEditor::CBaseAnimationSetEditor( vgui::Panel *parent, const char *className, bool bShowGroups ) :
	BaseClass( parent, className ),
	m_Layout( LAYOUT_SPLIT ),
	m_Images( false )
{
	const char *modes[] =
	{
		"AS_OFF",
		"AS_PREVIEW",
		"AS_RECORD",
		"AS_PLAYBACK",
	};

	const char *imagefiles[] =
	{
		"tools/ifm/icon_recordingmode_off",
		"tools/ifm/icon_recordingmode_preview",
		"tools/ifm/icon_recordingmode_record",
		"tools/ifm/icon_recordingmode_playback",
	};

	int i;
	for ( i = 0 ; i < NUM_AS_RECORDING_STATES; ++i )
	{
		m_pState[ i ] = new ToggleButton( this, modes[ i ], "" );
		m_pState[ i ]->SetContentAlignment( Label::a_center );
		m_pState[ i ]->AddActionSignalTarget( this );
		m_pState[ i ]->SetVisible( bShowGroups );
		m_pState[ i ]->SetKeyBoardInputEnabled( false );
	}

	m_pSelectionModeType = new ToggleButton( this, "AnimSetSelectionModeType", "" );
	m_pSelectionModeType->SetContentAlignment( Label::a_center );
	m_pSelectionModeType->AddActionSignalTarget( this );
	m_pSelectionModeType->SetSelected( false );
	m_pSelectionModeType->SetKeyBoardInputEnabled( false );

	m_pComboBox = new ComboBox( this, "AnimationSets", 10, false );

	//	m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_lock", false ) );
	//	m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_eyedropper", false ) );
	//	m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_selectionmodeactive", false ) );
	m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_selectionmodeattached", false ) );
	for ( i = 0; i < NUM_AS_RECORDING_STATES; ++i )
	{
		m_Images.AddImage( scheme()->GetImage( imagefiles[ i ], false ) );
	}

	int w, h;
	m_Images.GetImage( 1 )->GetContentSize( w, h );
	m_Images.GetImage( 1 )->SetSize( w, h );
	m_Images.GetImage( 2 )->GetContentSize( w, h );
	m_Images.GetImage( 2 )->SetSize( w, h );

	// SETUP_PANEL( this );

	PostMessage( GetVPanel(), new KeyValues( "OnChangeLayout", "value", m_Layout ) );
	PostMessage( GetVPanel(), new KeyValues( "PopulateAnimationSetsChoice" ) );

	m_pSelectionModeType->SetVisible( bShowGroups );
	m_pComboBox->SetVisible( bShowGroups );

	SetRecordingState( bShowGroups ? AS_PLAYBACK : AS_PREVIEW, true );

	m_hFileOpenStateMachine = new vgui::FileOpenStateMachine( this, this );
	m_hFileOpenStateMachine->AddActionSignalTarget( this );
}

CBaseAnimationSetEditor::~CBaseAnimationSetEditor()
{
}

void CBaseAnimationSetEditor::CreateToolsSubPanels()
{
	m_hControlGroup = new CBaseAnimSetControlGroupPanel( (Panel *)NULL, "AnimSetControlGroup", this );
	m_hPresetFader = new CBaseAnimSetPresetFaderPanel( (Panel *)NULL, "AnimSetPresetFader", this );
	m_hAttributeSlider = new CBaseAnimSetAttributeSliderPanel( (Panel *)NULL, "AnimSetAttributeSliderPanel", this );
}

void CBaseAnimationSetEditor::OnButtonToggled( KeyValues *params )
{
	Panel *ptr = reinterpret_cast< Panel * >( params->GetPtr( "panel" ) );
	/*

	if ( ptr == m_pSelectionModeType )
	{
	// FIXME, could do this with MESSAGE_FUNC_PARAMS and look up "panel" ptr and compare to figure out which button was manipulated...
	g_pMovieMaker->SetTimeSelectionModeType( !m_pSelectionModeType->IsSelected() ? CIFMTool::MODE_DETACHED : CIFMTool::MODE_ATTACHED );
	}
	else
	*/
	{
		for ( int i = 0; i < NUM_AS_RECORDING_STATES; ++i )
		{
			if ( ptr == m_pState[ i ] )
			{
				SetRecordingState( (RecordingState_t)i, true );
				break;
			}
		}
	}
}

void CBaseAnimationSetEditor::ChangeLayout( EAnimSetLayout_t newLayout )
{
	int i;

	m_Layout = newLayout;

	// Make sure these don't get blown away...
	m_hControlGroup->SetParent( (Panel *)NULL );
	m_hPresetFader->SetParent( (Panel *)NULL );
	m_hAttributeSlider->SetParent( (Panel *)NULL );

	delete m_Splitter.Get();
	m_Splitter = NULL;

	CUtlVector< Panel * > list;
	list.AddToTail( m_hControlGroup.Get() );
	list.AddToTail( m_hPresetFader.Get() );
	list.AddToTail( m_hAttributeSlider.Get() );

	Splitter *sub = NULL;

	switch ( m_Layout )
	{
	default:
	case LAYOUT_SPLIT:
		{
			m_Splitter = new Splitter( this, "AnimSetEditorMainSplitter", SPLITTER_MODE_VERTICAL, 1 );
			m_Splitter->SetAutoResize
				( 
				Panel::PIN_TOPLEFT, 
				Panel::AUTORESIZE_DOWNANDRIGHT,
				0, ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT,
				0, 0
				);
			m_Splitter->SetBounds( 0, ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT, GetWide(), GetTall() - ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT );
			m_Splitter->SetSplitterColor( Color(32, 32, 32, 255) );

			// m_Splitter->EnableBorders( false );

			m_hControlGroup->SetParent( m_Splitter->GetChild( 0 ) );
			m_hControlGroup->SetAutoResize
				( 
				Panel::PIN_TOPLEFT, 
				Panel::AUTORESIZE_DOWNANDRIGHT,
				0, 0,
				0, 0
				);

			sub = new Splitter( m_Splitter->GetChild( 1 ), "AnimSetEditorSubSplitter", SPLITTER_MODE_HORIZONTAL, 1 );
			sub->SetAutoResize
				( 
				Panel::PIN_TOPLEFT, 
				Panel::AUTORESIZE_DOWNANDRIGHT,
				0, 0,
				0, 0
				);

			m_hPresetFader->SetParent( sub->GetChild( 0 ) );
			m_hPresetFader->SetAutoResize
				( 
				Panel::PIN_TOPLEFT, 
				Panel::AUTORESIZE_DOWNANDRIGHT,
				0, 0,
				0, 0
				);
			m_hAttributeSlider->SetParent( sub->GetChild( 1 ) );
			m_hAttributeSlider->SetAutoResize
				( 
				Panel::PIN_TOPLEFT, 
				Panel::AUTORESIZE_DOWNANDRIGHT,
				0, 0,
				0, 0
				);
		}
		break;
	case LAYOUT_VERTICAL:
		{
			m_Splitter = new Splitter( this, "AnimSetEditorMainSplitter", SPLITTER_MODE_VERTICAL, 2 );
			m_Splitter->SetSplitterColor( Color(32, 32, 32, 255) );
			m_Splitter->SetAutoResize
				( 
				Panel::PIN_TOPLEFT, 
				Panel::AUTORESIZE_DOWNANDRIGHT,
				0, ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT,
				0, 0
				);
			m_Splitter->SetBounds( 0, ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT, GetWide(), GetTall() - ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT );

			for ( i = 0; i < list.Count(); ++i )
			{
				list[ i ]->SetParent( m_Splitter->GetChild( i ) );
				list[ i ]->SetSize( m_Splitter->GetChild( i )->GetWide(), m_Splitter->GetChild( i )->GetTall() );
				list[ i ]->SetAutoResize
					( 
					Panel::PIN_TOPLEFT, 
					Panel::AUTORESIZE_DOWNANDRIGHT,
					0, 0,
					0, 0
					);
			}

			m_Splitter->EvenlyRespaceSplitters();
		}
		break;
	case LAYOUT_HORIZONTAL:
		{
			m_Splitter = new Splitter( this, "AnimSetEditorMainSplitter", SPLITTER_MODE_HORIZONTAL, 2 );
			m_Splitter->SetSplitterColor( Color(32, 32, 32, 255) );
			m_Splitter->SetAutoResize
				( 
				Panel::PIN_TOPLEFT, 
				Panel::AUTORESIZE_DOWNANDRIGHT,
				0, ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT,
				0, 0
				);

			m_Splitter->SetBounds( 0, ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT, GetWide(), GetTall() - ANIMATION_SET_EDITOR_BUTTONTRAY_HEIGHT );

			for ( i = 0; i < list.Count(); ++i )
			{
				list[ i ]->SetParent( m_Splitter->GetChild( i ) );
				list[ i ]->SetSize( m_Splitter->GetChild( i )->GetWide(), m_Splitter->GetChild( i )->GetTall() );
				list[ i ]->SetAutoResize
					( 
					Panel::PIN_TOPLEFT, 
					Panel::AUTORESIZE_DOWNANDRIGHT,
					0, 0,
					0, 0
					);
			}

			m_Splitter->EvenlyRespaceSplitters();
		}
		break;
	}

	if ( sub )
	{
		sub->OnSizeChanged( sub->GetWide(), sub->GetTall() );
		sub->EvenlyRespaceSplitters();
	}
}

void CBaseAnimationSetEditor::PerformLayout()
{
	BaseClass::PerformLayout();

	int w, h;
	GetSize( w, h );

	int ypos = ANIMATION_SET_EDITOR_BUTTONTRAY_YPOS;

	int xpos = w - 25;
	m_pSelectionModeType->SetBounds( xpos, ypos, 20, 20 );
	for ( int i = NUM_AS_RECORDING_STATES - 1; i >= 0 ; --i  )
	{
		xpos -= 23;
		m_pState[ i ]->SetBounds( xpos, ypos, 20, 20 );
	}

	m_pComboBox->SetBounds( 10, ypos, xpos - 10- 5, 20 );
}

void CBaseAnimationSetEditor::OnChangeLayout( int value )
{
	ChangeLayout( ( EAnimSetLayout_t )value );
}


//-----------------------------------------------------------------------------
// Finds a channel in the animation set to overwrite with import data
//-----------------------------------------------------------------------------
CDmeChannel* CBaseAnimationSetEditor::FindImportChannel( CDmeChannel *pChannel, CDmeChannelsClip *pChannelsClip )
{
	CDmElement *pTargetElement = pChannel->GetToElement();
	if ( !pTargetElement )
		return NULL;

	CDmAttribute *pTargetAttribute = pChannel->GetToAttribute();
	if ( !pTargetAttribute )
		return NULL;

	const char *pTarget = pTargetAttribute->GetName();
	const char *pTargetName = pTargetElement->GetName();
	CDmeLog *pTargetLog = pChannel->GetLog();

	int nCount = pChannelsClip->m_Channels.Count();
	for ( int j = 0; j < nCount; ++j )
	{
		CDmeChannel *pImportChannel = pChannelsClip->m_Channels[j];
		if ( !pImportChannel )
			continue;

		CDmeLog *pImportLog = pImportChannel->GetLog();
		if ( !pImportLog )
			continue;

		if ( pTargetLog && ( pImportLog->GetType() != pTargetLog->GetType() ) )
			continue;

		if ( !pImportChannel->GetToAttribute() )
			continue;

		const char *pImportTarget = pImportChannel->GetToAttribute()->GetName();

		// Attribute to write into has to match exactly
		if ( Q_stricmp( pTarget, pImportTarget ) )
			continue;

		CDmElement *pImportTargetElement = pImportChannel->GetToElement();
		const char *pImportName = pImportTargetElement->GetName();
		
		// Element name has to match exactly or be of the form *(channel name)*
		if ( !Q_stricmp( pTargetName, pImportName ) )
			return pImportChannel;

		char pTemp[512];
		const char *pParen = strrchr( pTargetName, '(' );
		if ( !pParen )
			continue;
		Q_strncpy( pTemp, pParen+1, sizeof(pTemp) );
		char *pParen2 = strchr( pTemp, ')' );
		if ( !pParen2 )
			continue;
		*pParen2 = 0;
		if ( !Q_stricmp( pImportName, pTemp ) )
			return pImportChannel;
	}
	return NULL;
}


//-----------------------------------------------------------------------------
// Transforms an imported channel, if necessary
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::TransformImportedChannel( CDmeChannel *pChannel )
{
	CDmElement *pTarget = pChannel->GetToElement();
	const static UtlSymId_t symBones = g_pDataModel->GetSymbol( "bones" );
	CDmeGameModel *pGameModel = FindReferringElement<CDmeGameModel>( pTarget, symBones );
	if ( !pGameModel )
		return;

	int nBoneIndex = pGameModel->FindBone( CastElement< CDmeTransform >( pTarget ) );
	if ( nBoneIndex < 0 )
		return;

	// If we've got logs that have been imported, we need to compute our bounds.
	pGameModel->m_bComputeBounds = true;

	DmAttributeType_t logType = pChannel->GetLog()->GetDataType();
	int nLayerCount = pChannel->GetLog()->GetNumLayers();

	bool bHasPreTransform = false;
	bool bHasPostTransform = false;

	matrix3x4_t preTransform, postTransform;
	if ( pGameModel->GetSrcBoneTransforms( &preTransform, &postTransform, nBoneIndex ) )
	{
		bHasPreTransform = true;
		bHasPostTransform = true;
	}

	if ( pGameModel->IsRootTransform( nBoneIndex ) )
	{
		// NOTE: Root transforms require a pre-multiply of log data

		// Deal with the 'up axis' rotation
		matrix3x4_t rootTransform;
		RadianEuler angles( M_PI / 2.0f, 0.0f, M_PI / 2.0f );
		if ( bHasPreTransform )
		{
			AngleMatrix( angles, rootTransform );
			ConcatTransforms( rootTransform, preTransform, preTransform );
		}
		else
		{
			AngleMatrix( angles, preTransform );
		}
		bHasPreTransform = true;
	}

	if ( !bHasPreTransform && !bHasPostTransform )
		return;

	for ( int i = 0; i < nLayerCount; ++i )
	{
		if ( logType == AT_VECTOR3 )
		{
			CDmeVector3LogLayer *pPositionLog = CastElement< CDmeVector3LogLayer >( pChannel->GetLog()->GetLayer( i ) );
			if ( bHasPreTransform )
			{
				RotatePositionLog( pPositionLog, preTransform );
			}

#ifdef _DEBUG
			// At the moment, we don't support anything but prerotation.
			// This would be tricky because we'd need to read the quat logs
			// to figure out how to translate in local space.
			if ( bHasPostTransform )
			{
				Assert( fabs( postTransform[0][3] ) < 1e-3 && fabs( postTransform[1][3] ) < 1e-3 && fabs( postTransform[2][3] ) < 1e-3 );
			}
#endif
		}
		else
		{
			CDmeQuaternionLogLayer *pOrientationLog = CastElement< CDmeQuaternionLogLayer >( pChannel->GetLog()->GetLayer( i ) );
			if ( bHasPreTransform )
			{
				RotateOrientationLog( pOrientationLog, preTransform, true );
			}
			if ( bHasPostTransform )
			{
				RotateOrientationLog( pOrientationLog, postTransform, false );
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Expands channels clip time to encompass log
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::FixupChannelsClipTime( CDmeChannelsClip *pChannelsClip, CDmeLog *pLog )
{
	// Expand the channels clip to include the entire channel
	DmeTime_t st = pLog->GetBeginTime();
	DmeTime_t et = pLog->GetEndTime();
	st = pChannelsClip->FromChildMediaTime( st, false );
	et = pChannelsClip->FromChildMediaTime( et, false );
	if ( et < pChannelsClip->GetEndTime() )
	{
		et = pChannelsClip->GetEndTime();
	}
	if ( st < pChannelsClip->GetStartTime() )
	{
		DmeTime_t tDelta = pChannelsClip->GetStartTime() - st;
		DmeTime_t tOffset = pChannelsClip->GetTimeOffset();
		pChannelsClip->SetStartTime( st );
		pChannelsClip->SetTimeOffset( tOffset - tDelta );
	}
	else
	{
		st = pChannelsClip->GetStartTime();
	}
	DmeTime_t duration = et - st;
	if ( duration > pChannelsClip->GetDuration() )
	{
		pChannelsClip->SetDuration( duration );
	}
}


//-----------------------------------------------------------------------------
// Expands channels clip time to encompass log
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::FixupChannelsClipTime( CDmeChannel *pChannel, CDmeLog *pLog )
{
	CUtlVector< CDmeChannelsClip* > clips;
	FindAncestorsReferencingElement( pChannel, clips );
	int nCount = clips.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		FixupChannelsClipTime( clips[i], pLog );
	}
}


//-----------------------------------------------------------------------------
// Imports a specific channels clip into the animation set
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::OnImportConfirmed( KeyValues *pParams )
{
	KeyValues *pImportParams = pParams->FindKey( "context" );
	CDmeChannelsClip *pChannelsClip = GetElementKeyValue< CDmeChannelsClip >( pImportParams, "channelsClip" );
	if ( pParams->GetInt( "operationPerformed" ) == 0 )
	{
		CDisableUndoScopeGuard sg;
		g_pDataModel->RemoveFileId( pChannelsClip->GetFileId() );
		return;
	}

	bool bVisibleOnly = pImportParams->GetInt( "visibleOnly" ) != 0;

	CUtlVector< LogPreview_t > controls;
	int nCount = bVisibleOnly ? BuildVisibleControlList( controls ) : BuildFullControlList( controls );

	CUndoScopeGuard guard( "Import Animation" );
	for ( int i = 0; i < nCount; ++i )
	{
		for ( int k = 0; k < LOG_PREVIEW_MAX_CHANNEL_COUNT; ++k )
		{
			CDmeChannel *pChannel = controls[i].m_hChannels[k];
			if ( !pChannel )
				continue;

			CDmeChannel *pImportChannel = FindImportChannel( pChannel, pChannelsClip );
			if ( !pImportChannel )
				continue;

			// Switch the log over
			CDmeLog *pImportLog = pImportChannel->GetLog();
			pChannel->SetLog( pImportLog );
			pImportChannel->SetLog( NULL );
			pImportLog->SetFileId( pChannel->GetFileId(), TD_DEEP );

			TransformImportedChannel( pChannel );

			// Expand the channels clip to include the entire channel
			FixupChannelsClipTime( pChannel, pChannel->GetLog() );
		}
	}
	guard.Release();

	// Cleanup the file
	CDisableUndoScopeGuard sg;
	g_pDataModel->RemoveFileId( pChannelsClip->GetFileId() );
}


//-----------------------------------------------------------------------------
// Imports a specific channels clip into the animation set
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::ImportAnimation( CDmeChannelsClip *pChannelsClip, bool bVisibleOnly )
{
	CUtlVector< LogPreview_t > controls;
	int nCount = bVisibleOnly ? BuildVisibleControlList( controls ) : BuildFullControlList( controls );

	COperationFileListFrame *pStatusFrame = new COperationFileListFrame( this, 
		"Import the Following Channels?", "Target Control", false );
	pStatusFrame->SetCloseButtonVisible( false );
	pStatusFrame->SetOperationColumnHeaderText( "Source Channel" );

	int nSrcCount = pChannelsClip->m_Channels.Count();
	CDmeChannel** ppFoundChannels = (CDmeChannel**)_alloca( nSrcCount * sizeof(CDmeChannel*) );
	int nFoundCount = 0;

	for ( int i = 0; i < nCount; ++i )
	{
		for ( int k = 0; k < LOG_PREVIEW_MAX_CHANNEL_COUNT; ++k )
		{
			CDmeChannel *pChannel = controls[i].m_hChannels[k];
			if ( !pChannel || pChannel->GetToElement() == NULL )
				continue;

			char pChannelInfo[512];
			Q_snprintf( pChannelInfo, sizeof(pChannelInfo), "\"%s\" : %s", 
				pChannel->GetToElement()->GetName(), pChannel->GetToAttribute()->GetName() );

			CDmeChannel *pImportChannel = FindImportChannel( pChannel, pChannelsClip );
			if ( !pImportChannel )
			{
				pStatusFrame->AddOperation( "No source channel", pChannelInfo, Color( 255, 0, 0, 255 ) ); 
				continue;
			}

			ppFoundChannels[nFoundCount++] = pImportChannel;

			char pImportInfo[512];
			Q_snprintf( pImportInfo, sizeof(pImportInfo), "\"%s\" : %s", 
				pImportChannel->GetToElement()->GetName(), pImportChannel->GetToAttribute()->GetName() );
			pStatusFrame->AddOperation( pImportInfo, pChannelInfo, Color( 0, 255, 0, 255 ) );
		}
	}

	for ( int i = 0; i < nSrcCount; ++i )
	{
		CDmeChannel *pMissingChannel  = pChannelsClip->m_Channels[i];

		int j;
		for ( j = 0; j < nFoundCount; ++j )
		{
			if ( ppFoundChannels[j] == pMissingChannel )
				break;
		}

		if ( j != nFoundCount )
			continue;

		char pImportInfo[512];
		Q_snprintf( pImportInfo, sizeof(pImportInfo), "\"%s\" : %s", 
			pMissingChannel->GetToElement()->GetName(), pMissingChannel->GetToAttribute()->GetName() );
		pStatusFrame->AddOperation( pImportInfo, "No destination control", Color( 255, 255, 0, 255 ) );
	}

	KeyValues *pContext = new KeyValues( "context" );
	SetElementKeyValue( pContext, "channelsClip", pChannelsClip );
	pContext->SetInt( "visibleOnly", bVisibleOnly );
	pStatusFrame->DoModal( pContext, "ImportConfirmed" );
}


//-----------------------------------------------------------------------------
// Called by CDmePickerFrame in SelectImportAnimation
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::OnImportAnimationSelected( KeyValues *pParams )
{
	KeyValues *pContextKeyValues = pParams->FindKey( "context" );
	CDmeChannelsClip *pChannelsClip = GetElementKeyValue< CDmeChannelsClip >( pParams, "dme" );
	if ( pChannelsClip )
	{
		bool bVisibleOnly = pContextKeyValues->GetInt( "visibleOnly" ) != 0;
		ImportAnimation( pChannelsClip, bVisibleOnly );
	}
	else
	{
		OnImportAnimationCancelled( pParams );
	}
}


//-----------------------------------------------------------------------------
// Called by CDmePickerFrame in SelectImportAnimation
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::OnImportAnimationCancelled( KeyValues *pParams )
{
	KeyValues *pContextKeyValues = pParams->FindKey( "context" );
	CDmElement *pAnimationList = GetElementKeyValue<CDmElement>( pContextKeyValues, "animationList" );
		
	// Cleanup the file
	if ( pAnimationList )
	{
		CDisableUndoScopeGuard sg;
		g_pDataModel->RemoveFileId( pAnimationList->GetFileId() );
	}
}


//-----------------------------------------------------------------------------
// Selects an animation to import
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::SelectImportAnimation( CDmeAnimationList *pAnimationList, bool bVisibleOnly )
{
	KeyValues *pContextKeyValues = new KeyValues( "context" );
	SetElementKeyValue( pContextKeyValues, "animationList", pAnimationList );
	pContextKeyValues->SetInt( "visibleOnly", bVisibleOnly );

	int nCount = pAnimationList->GetAnimationCount();
	CUtlVector< DmePickerInfo_t > choices( 0, nCount );
	for ( int i = 0; i < nCount; ++i )
	{
		CDmeChannelsClip *pAnimation = pAnimationList->GetAnimation( i );
		if ( !pAnimation )
			continue;

		int j = choices.AddToTail();
		DmePickerInfo_t& info = choices[j];
		info.m_hElement = pAnimation->GetHandle();
		info.m_pChoiceString = pAnimation->GetName();
	}

	CDmePickerFrame *pAnimationPicker = new CDmePickerFrame( this, "Select Animation To Import" );
	pAnimationPicker->DoModal( choices, pContextKeyValues );
}


//-----------------------------------------------------------------------------
// Called by the FileOpenDialog in OnImportAnimation
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::OnFileSelected( KeyValues *kv )
{
	KeyValues *pContextKeyValues = kv->FindKey( "ImportAnimation" );
	if ( !pContextKeyValues )
		return;

	bool bVisibleOnly = pContextKeyValues->GetInt( "visibleOnly" );
	if ( bVisibleOnly )
	{
		CUtlVector< LogPreview_t > controls;
		int nCount = BuildVisibleControlList( controls );
		if ( nCount == 0 )
		{
			vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Error Importing Animations\n", 
				"Cannot import because there are no visible controls!\n", GetParent() );
			pMessageBox->DoModal( );
			return;
		}
	}

	const char *pFileName = kv->GetString( "fullpath", NULL );
	if ( !pFileName )
		return;

	CDmElement *pRoot;
	CDisableUndoScopeGuard guard;
	DmFileId_t fileId = g_pDataModel->RestoreFromFile( pFileName, NULL, NULL, &pRoot, CR_FORCE_COPY );
	guard.Release();

	if ( fileId == DMFILEID_INVALID )
		return;

	CDmeChannelsClip *pChannelsClip = CastElement< CDmeChannelsClip >( pRoot );
	if ( pChannelsClip )
	{
		ImportAnimation( pChannelsClip, bVisibleOnly );
		return;
	}

	CDmeAnimationList* pAnimationList = CastElement< CDmeAnimationList >( pRoot );
	if ( !pAnimationList )
	{
		pAnimationList = pRoot->GetValueElement< CDmeAnimationList >( "animationList" );
	}

	if ( !pAnimationList || pAnimationList->GetAnimationCount() == 0 )
	{
		char pBuf[1024];
		Q_snprintf( pBuf, sizeof(pBuf), "File \"%s\" contains no animations!\n", pFileName ); 
		vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Error Importing Animations\n", pBuf, GetParent() );
		pMessageBox->DoModal( );

		CDisableUndoScopeGuard sg;
		g_pDataModel->RemoveFileId( pRoot->GetFileId() );
		sg.Release();
		return;
	}

	if ( pAnimationList->GetAnimationCount() == 1 )
	{
		ImportAnimation( pAnimationList->GetAnimation( 0 ), bVisibleOnly );
	}
	else
	{
		SelectImportAnimation( pAnimationList, bVisibleOnly );
	}
}


//-----------------------------------------------------------------------------
// Called by the context menu to import animation files
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::OnImportAnimation( KeyValues *pParams )
{
	// Compute starting directory
	CDmeGameModel *pGameModel = m_AnimSet->GetValueElement< CDmeGameModel >( "gameModel" );

	char pStartingDir[ MAX_PATH ];
	if ( !pGameModel )
	{
		GetModContentSubdirectory( "models", pStartingDir, sizeof(pStartingDir) );
	}
	else
	{
		char pModelName[ MAX_PATH ];
		studiohdr_t *pStudioHdr = pGameModel->GetStudioHdr();
		Q_StripExtension( pStudioHdr->pszName(), pModelName, sizeof(pModelName) );

		char pRelativePath[ MAX_PATH ];
		Q_snprintf( pRelativePath, sizeof(pRelativePath), "models/%s/animations/dmx", pModelName );
		GetModContentSubdirectory( pRelativePath, pStartingDir, sizeof(pStartingDir) );
		if ( !g_pFullFileSystem->IsDirectory( pStartingDir ) )
		{
			Q_snprintf( pRelativePath, sizeof(pRelativePath), "models/%s", pModelName );
			GetModContentSubdirectory( pRelativePath, pStartingDir, sizeof(pStartingDir) );
			if ( !g_pFullFileSystem->IsDirectory( pStartingDir ) )
			{
				GetModContentSubdirectory( "models", pStartingDir, sizeof(pStartingDir) );
			}
		}
	}

	KeyValues *pContextKeyValues = new KeyValues( "ImportAnimation", "visibleOnly", pParams->GetInt( "visibleOnly" ) );
	FileOpenDialog *pDialog = new FileOpenDialog( this, "Select Animation File Name", true, pContextKeyValues );
	pDialog->SetStartDirectoryContext( "animation_set_import_animation", pStartingDir );
	pDialog->AddFilter( "*.*", "All Files (*.*)", false );
	pDialog->AddFilter( "*.dmx", "Animation file (*.dmx)", true );
	pDialog->SetDeleteSelfOnClose( true );
	pDialog->AddActionSignalTarget( this );
	pDialog->DoModal( );
}


//-----------------------------------------------------------------------------
// Main entry point for exporting facial animation
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::SetupFileOpenDialog( vgui::FileOpenDialog *pDialog, 
	bool bOpenFile, const char *pFileFormat, KeyValues *pContextKeyValues )
{
	// Compute starting directory
	char pStartingDir[ MAX_PATH ];
	GetModSubdirectory( "scenes", pStartingDir, sizeof(pStartingDir) );

	Assert( !bOpenFile );
	pDialog->SetTitle( "Save Facial Animation As", true );

	Assert( !V_strcmp( pFileFormat, "facial_animation" ) );
	pDialog->SetStartDirectoryContext( "facial_animation_export", pStartingDir );
	pDialog->AddFilter( "*.*", "All Files (*.*)", false );
	pDialog->AddFilter( "*.dmx", "Facial animation file (*.dmx)", true, pFileFormat );
}

bool CBaseAnimationSetEditor::OnReadFileFromDisk( const char *pFileName, const char *pFileFormat, KeyValues *pContextKeyValues )
{
	Assert( 0 );
	return false;
}


//-----------------------------------------------------------------------------
// Called when it's time to write the file
//-----------------------------------------------------------------------------
bool CBaseAnimationSetEditor::OnWriteFileToDisk( const char *pFileName, const char *pFileFormat, KeyValues *pContextKeyValues )
{
	// Recompute relative paths for each source now that we know the file name
	// NOTE: This also updates the name of the fileID in the datamodel system
	CDisableUndoScopeGuard guard;
	bool bOk = ExportFacialAnimation( pFileName, GetRootClip(), GetAnimationSetClip(), m_AnimSet ); 
	return bOk;
}


//-----------------------------------------------------------------------------
// Main entry point for exporting facial animation
//-----------------------------------------------------------------------------
void CBaseAnimationSetEditor::OnExportFacialAnimation()
{
	KeyValues *pContextKeyValues = new KeyValues( "ExportFacialAnimation" );
	m_hFileOpenStateMachine->SaveFile( pContextKeyValues, NULL, "facial_animation", FOSM_SHOW_PERFORCE_DIALOGS );
}


void CBaseAnimationSetEditor::OnOpenContextMenu( KeyValues *params )
{
	if ( m_hContextMenu.Get() )
	{
		delete m_hContextMenu.Get();
		m_hContextMenu = NULL;
	}

	m_hContextMenu = new Menu( this, "ActionMenu" );

	int c = ARRAYSIZE( g_AnimSetLayout );
	for ( int i = 0; i < c; ++i )
	{
		const AnimSetLayout_t& data = g_AnimSetLayout[ i ];

		m_hContextMenu->AddMenuItem( data.contextmenulabel, new KeyValues( "OnChangeLayout", "value", (int)data.type ), this );
	}

	if ( m_AnimSet.Get() )
	{
		m_hContextMenu->AddSeparator( );
		m_hContextMenu->AddMenuItem( "#ImportAnimation", new KeyValues( "ImportAnimation" ), this );
		m_hContextMenu->AddMenuItem( "#ReattachToModel", new KeyValues( "ReattachToModel" ), this );
		m_hContextMenu->AddMenuItem( "#ExportFacialAnimation", new KeyValues( "ExportFacialAnimation" ), this );
	}

	Panel *rpanel = reinterpret_cast< Panel * >( params->GetPtr( "contextlabel" ) );
	if ( rpanel )
	{
		// force the menu to compute required width/height
		m_hContextMenu->PerformLayout();
		m_hContextMenu->PositionRelativeToPanel( rpanel, Menu::DOWN, 0, true );
	}
	else
	{
		Menu::PlaceContextMenu( this, m_hContextMenu.Get() );
	}
}

void CBaseAnimationSetEditor::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	// Have to manually apply settings here if they aren't attached in hierarchy
	if ( m_hControlGroup->GetParent() != this )
	{
		m_hControlGroup->ApplySchemeSettings( pScheme );
	}
	if ( m_hPresetFader->GetParent() != this )
	{
		m_hPresetFader->ApplySchemeSettings( pScheme );
	}
	if ( m_hAttributeSlider->GetParent() != this )
	{
		m_hAttributeSlider->ApplySchemeSettings( pScheme );
	}

	m_pSelectionModeType->ClearImages();
	m_pSelectionModeType->AddImage( m_Images.GetImage( 1 ), 0 );

	for ( int i = 0; i < NUM_AS_RECORDING_STATES; ++i )
	{
		m_pState[ i ]->ClearImages();
		m_pState[ i ]->AddImage( m_Images.GetImage( i + 2 ), 0 );
	}

	m_pComboBox->SetFont( pScheme->GetFont( "DefaultBold" ) );
}

CBaseAnimSetControlGroupPanel *CBaseAnimationSetEditor::GetControlGroup()
{
	return m_hControlGroup.Get();
}

CBaseAnimSetPresetFaderPanel *CBaseAnimationSetEditor::GetPresetFader()
{
	return m_hPresetFader.Get();
}

CBaseAnimSetAttributeSliderPanel *CBaseAnimationSetEditor::GetAttributeSlider()
{
	return m_hAttributeSlider.Get();
}



void CBaseAnimationSetEditor::ChangeAnimationSet( CDmeAnimationSet *newAnimSet )
{
	m_AnimSet = newAnimSet;

	if ( newAnimSet )
	{
		CUndoScopeGuard guard( "Auto-create Procedural Presets" );
		newAnimSet->EnsureProceduralPresets();
	}

	// send to sub controls
	m_hControlGroup->ChangeAnimationSet( newAnimSet );
	m_hPresetFader->ChangeAnimationSet( newAnimSet );
	m_hAttributeSlider->ChangeAnimationSet( newAnimSet );
}

void CBaseAnimationSetEditor::OnDataChanged()
{
}

void CBaseAnimationSetEditor::OnTextChanged()
{
	KeyValues *kv = m_pComboBox->GetActiveItemUserData();
	if ( !kv )
		return;

	CDmeAnimationSet *set = GetElementKeyValue< CDmeAnimationSet >( kv, "handle" );
	if ( set )
	{
		ChangeAnimationSet( set );
	}
}

void CBaseAnimationSetEditor::SetRecordingState( RecordingState_t state, bool /*updateSettings*/ )
{
	m_RecordingState = state;

	// Reset buttons as needed
	for ( int i = 0; i < NUM_AS_RECORDING_STATES; ++i )
	{
		if ( (RecordingState_t)i == state )
		{
			m_pState[ i ]->SetSelected( true );
			m_pState[ i ]->ForceDepressed( true );
		}
		else
		{
			m_pState[ i ]->SetSelected( false );
			m_pState[ i ]->ForceDepressed( false );
		}
	}
}

RecordingState_t CBaseAnimationSetEditor::GetRecordingState() const
{
	return m_RecordingState;
}

CDmeAnimationSet *CBaseAnimationSetEditor::GetAnimationSet()
{
	return m_AnimSet;
}

int CBaseAnimationSetEditor::BuildVisibleControlList( CUtlVector< LogPreview_t >& list )
{
	return m_hAttributeSlider->BuildVisibleControlList( list );
}

int CBaseAnimationSetEditor::BuildFullControlList( CUtlVector< LogPreview_t >& list )
{
	return m_hAttributeSlider->BuildFullControlList( list );
}

void CBaseAnimationSetEditor::RecomputePreview()
{
	m_hAttributeSlider->RecomputePreview();
}