//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "hlfaceposer.h"
#include <stdio.h>
#include "TimelineItem.h"
#include "choreowidgetdrawhelper.h"
#include "mathlib/mathlib.h"
#include "expressions.h"
#include "StudioModel.h"
#include "expclass.h"
#include "mathlib/mathlib.h"
#include "ExpressionTool.h"
#include "choreoevent.h"
#include "choreoscene.h"
#include "choreoactor.h"
#include "choreochannel.h"
#include "ChoreoView.h"
#include "ControlPanel.h"
#include "faceposer_models.h"
#include "MatSysWin.h"
#include "choreoviewcolors.h"
#include "ifaceposersound.h"
#include "curveeditorhelpers.h"

extern double realtime;

#define DOUBLE_CLICK_TIME 0.2

#define GROW_HANDLE_WIDTH	66
#define GROW_HANDLE_HEIGHT	8
#define GROW_HANDLE_INSETPIXELS 8

#define TIMELINEITEM_DEFAULT_HEIGHT 100

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
TimelineItem::TimelineItem( mxWindow *workspace )
{
	m_pHelper = new CCurveEditorHelper< TimelineItem >( this );

	m_pWorkspace = workspace;

	m_nDragging = DRAGTYPE_NONE;
	m_nLastX = 0;
	m_nLastY = 0;
	m_nStartX = 0;
	m_nStartY = 0;

	SetExpressionInfo( NULL, 0 );

	m_nNumSelected = 0;

	m_nEditType = 0;

	SetCollapsed( false );
	SetActive( false );

	m_nUndoSetup = 0;
	m_rcBounds.top = 0;
	m_rcBounds.bottom = 0;
	m_rcBounds.left = 0;
	m_rcBounds.right = 0;
	m_bVisible = false;
	m_flLastClickTime = -1;

	m_nCurrentHeight = TIMELINEITEM_DEFAULT_HEIGHT;
}

TimelineItem::~TimelineItem( void )
{
	delete m_pHelper;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::ResetHeight()
{
	m_nCurrentHeight = TIMELINEITEM_DEFAULT_HEIGHT;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : mx - 
// Output : float
//-----------------------------------------------------------------------------
float TimelineItem::GetTimeForMouse( int mx, bool clip /*= false*/ )
{
	float start, end;
	g_pExpressionTool->GetStartAndEndTime( start, end );

	if ( clip )
	{
		if ( mx < m_rcBounds.left )
		{
			return start;
		}
		else if ( mx >= m_rcBounds.right )
		{
			return end;
		}
	}

	float frac = (float)( mx - m_rcBounds.left ) / (float)( m_rcBounds.right - m_rcBounds.left );
	float t = start + frac * ( end - start );
	return t;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : t - 
// Output : int
//-----------------------------------------------------------------------------
int TimelineItem::GetMouseForTime( float t, bool *clipped /*= NULL*/ )
{
	float start, end;
	g_pExpressionTool->GetStartAndEndTime( start, end );

	float frac = ( t - start ) / ( end - start );

	if ( frac < 0.0 || frac > 1.0 )
	{
		if ( clipped )
		{
			*clipped = true;
		}
	}

	int mx = m_rcBounds.left + ( int ) ( frac * (float)( m_rcBounds.right - m_rcBounds.left ) );

	return mx;
}

int TimelineItem::NumSamples()
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return 0;

	// Aggregate both types of tracks together
	return track->GetNumSamples( 0 ) + track->GetNumSamples( 1 );
	// return track->GetNumSamples( m_nEditType );
}

CExpressionSample *TimelineItem::GetSample( int idx )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return NULL;

	if ( idx >= track->GetNumSamples( 0 ) )
	{
		// Rebase and look at left/right track instead
		idx -= track->GetNumSamples( 0 );
		return track->GetSample( idx, 1 );
	}
	return track->GetSample( idx, 0 );
}

CExpressionSample *TimelineItem::GetSampleUnderMouse( int mx, int my, float tolerance /*= FP_TL_SELECTION_TOLERANCE*/ )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return NULL;

	CChoreoEvent *e = track->GetEvent();
	if ( !e )
		return NULL;

	float closest_dist = 9999999.f;
	CExpressionSample *bestsample = NULL;

	// Add a sample point
	int height = m_rcBounds.bottom - m_rcBounds.top;

	mx += m_rcBounds.left;

	for ( int i = 0; i < track->GetNumSamples( m_nEditType ); i++ )
	{
		CExpressionSample *sample = track->GetSample( i, m_nEditType );

		bool clipped = false;
		int px = GetMouseForTime( sample->time, &clipped );		
		int py = height * ( 1.0f - sample->value ); 

		int dx = px - mx;
		int dy = py - my;

		float dist = sqrt( (float)(dx * dx + dy * dy) );
		if ( dist < closest_dist )
		{
			bestsample = sample;
			closest_dist = dist;
		}
	}

	// Not close to any of them!!!
	if ( ( tolerance != 0.0f ) && 
		( closest_dist > tolerance ) )
		return NULL;

	return bestsample;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::DeselectAll( void )
{
	g_pExpressionTool->DeselectAll();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::SelectAll( void )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	for ( int t = 0; t < 2; t++ )
	{
		for ( int i = 0; i <  track->GetNumSamples( t ); i++ )
		{
			CExpressionSample *sample = track->GetSample( i, t );
			sample->selected = true;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::Delete( void )
{
	g_pExpressionTool->DeleteSelectedSamples();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : sample - 
//-----------------------------------------------------------------------------
void TimelineItem::AddSample( CExpressionSample const& sample )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	PreDataChanged( "Add sample point" );

	track->AddSample( sample.time, sample.value, m_nEditType );
	track->Resort( m_nEditType );

	SetActive( true );

	PostDataChanged( "Add sample point" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int TimelineItem::CountSelected( void )
{
	m_nNumSelected = m_pHelper->CountSelected( false );
	return m_nNumSelected;
}

void TimelineItem::SetMousePositionForEvent( mxEvent *event )
{
	POINT pt;
	GetCursorPos( &pt );
	ScreenToClient( (HWND)m_pWorkspace->getHandle(), &pt );

	pt.x -= m_rcBounds.left;
	pt.y -= m_rcBounds.top;

	event->x = pt.x;
	event->y = pt.y;
}

int	TimelineItem::handleEvent( mxEvent *event )
{
	int iret = 0;

	// Give helper a shot at the event
	if ( m_pHelper->HelperHandleEvent( event ) )
	{
		return 1;
	}

	switch ( event->event )
	{
	case mxEvent::KeyDown:
		{
			switch ( event->key )
			{
			default:
				iret = g_pChoreoView->HandleZoomKey( g_pExpressionTool, event->key );
				break;
			case VK_ESCAPE:
				DeselectAll();
				DrawSelf();
				break;
			case VK_DELETE:
				Delete();
				DrawSelf();
				break;
			case 'C':
				Copy();
				DrawSelf();
				break;
			case 'V':
				Paste();
				DrawSelf();
				break;
			case 'J':
				{
					g_pExpressionTool->OnCopyToFlex( g_pExpressionTool->GetScrubberSceneTime(), true );
				}
				break;
			case 'K':
				{
					g_pExpressionTool->OnCopyFromFlex( g_pExpressionTool->GetScrubberSceneTime(), false );
				}
				break;
			case 188: // VK_OEM_COMMA:
				{
					g_pExpressionTool->SetScrubTargetTime( 0.0f );
				}
				break;
			case 190: // VK_OEM_PERIOD:
				{
					CChoreoScene *scene = g_pChoreoView->GetScene();
					if ( scene )
					{
						g_pExpressionTool->SetScrubTargetTime( scene->FindStopTime() );
					}
				}
				break;
			case VK_LEFT: 
				{
					CChoreoScene *scene = g_pChoreoView->GetScene();
					if ( scene && scene->GetSceneFPS() > 0 )
					{
						float curscrub = g_pExpressionTool->GetScrub();
						curscrub -= ( 1.0f / (float)scene->GetSceneFPS() );
						curscrub = max( curscrub, 0.0f );
						g_pExpressionTool->SetScrubTargetTime( curscrub );
					}
				}
				break;
			case VK_RIGHT: 
				{
					CChoreoScene *scene = g_pChoreoView->GetScene();
					if ( scene && scene->GetSceneFPS() > 0 )
					{
						float curscrub = g_pExpressionTool->GetScrub();
						curscrub += ( 1.0f / (float)scene->GetSceneFPS() );
						curscrub = min( curscrub, scene->FindStopTime() );
						g_pExpressionTool->SetScrubTargetTime( curscrub );
					}
				}
				break;
			case 191:
				{
					if ( g_pChoreoView->IsPlayingScene() )
					{
						g_pChoreoView->StopScene();
					}
				}
				break;
			}
			iret = 1;
		}
		break;
	case mxEvent::KeyUp:
		{
			switch ( event->key )
			{
			case VK_SPACE:
				{
					CFlexAnimationTrack *track = GetSafeTrack();
					if ( track && track->IsComboType() )
					{
						SetEditType( m_nEditType == 0 ? 1 : 0 );
						DrawSelf();
					}
				}
				break;
			}
			iret = 1;
		}
		break;
	case mxEvent::MouseDown:
		{
			sound->Flush();

			SetFocus( (HWND)g_pExpressionTool->getHandle() );

			int height = m_rcBounds.bottom - m_rcBounds.top;

			bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false;

			if ( m_nDragging == DRAGTYPE_NONE )
			{
				bool ctrlDown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false;

				CExpressionSample *sample = GetSampleUnderMouse( event->x, event->y, ctrlDown ? FP_TL_ADDSAMPLE_TOLERANCE : FP_TL_SELECTION_TOLERANCE );

				if ( IsMouseOverGrowHandle( (short)event->x, (short)event->y ) )
				{
					m_nDragging = DRAGTYPE_GROW;
					m_nLastX = (short)event->x;
					m_nLastY = (short)event->y;

					m_nStartX = m_nLastX;
					m_nStartY = m_nLastY;

					MouseDrag( (short)event->x, (short)event->y, event->modifiers );

					DrawGrowRect();
				}
				else if ( sample )
				{
					if  ( event->modifiers & mxEvent::KeyShift ) 
					{
						sample->selected = !sample->selected;
						DrawSelf();
					}
					else if ( sample->selected )
					{
						m_nDragging = rightbutton ? DRAGTYPE_MOVEPOINTS_TIME : DRAGTYPE_MOVEPOINTS_VALUE;
						m_nLastX = (short)event->x;
						m_nLastY = (short)event->y;

						m_nStartX = m_nLastX;
						m_nStartY = m_nLastY;

						PreDataChanged( "Move sample point(s)" );

						MouseDrag( (short)event->x, (short)event->y, event->modifiers );

						DrawSelf();
					}
					else
					{
						if  ( !( event->modifiers & mxEvent::KeyShift ) ) 
						{
							DeselectAll();
							DrawSelf();
						}

						m_nDragging = DRAGTYPE_SELECTION;
						m_nLastX = (short)event->x;
						m_nLastY = (short)event->y;

						m_nStartX = m_nLastX;
						m_nStartY = m_nLastY;

						MouseDrag( (short)event->x, (short)event->y, event->modifiers );

						DrawFocusRect();
					}
				}
				else if ( event->modifiers & mxEvent::KeyCtrl )
				{
					CChoreoEvent *e = g_pExpressionTool->GetSafeEvent();
					if ( e )
					{
						// Add a sample point
						float t = GetTimeForMouse( (short)event->x + m_rcBounds.left );

						CExpressionSample sample;
						sample.time = FacePoser_SnapTime( t );
						sample.value = 1.0f - (float)( (short)( event->y ) ) / (float)height;
						sample.selected = false;

						AddSample( sample );

						DrawSelf();
					}
				}
				else
				{
					if ( rightbutton )
					{
						POINT pt;
						pt.x = event->x;
						pt.y = event->y;
						ClientToScreen( (HWND)m_pWorkspace->getHandle(), &pt );
						ScreenToClient( (HWND)g_pExpressionTool->getHandle(), &pt );
						event->x = pt.x;
						event->y = pt.y;
						g_pExpressionTool->ShowContextMenu( event, true );
						return iret;
					}

					if  ( !( event->modifiers & mxEvent::KeyShift ) ) 
					{
						DeselectAll();
						DrawSelf();
					}

					m_nDragging = DRAGTYPE_SELECTION;
					m_nLastX = (short)event->x;
					m_nLastY = (short)event->y;

					m_nStartX = m_nLastX;
					m_nStartY = m_nLastY;

					MouseDrag( (short)event->x, (short)event->y, event->modifiers );

					DrawFocusRect();
				}
			}
			iret = 1;
		}
		break;
	case mxEvent::MouseDrag:
	case mxEvent::MouseMove:
		{
			if ( m_nDragging != DRAGTYPE_NONE )
			{
				if ( m_nDragging == DRAGTYPE_SELECTION )
				{
					DrawFocusRect();
				}
				else if ( m_nDragging == DRAGTYPE_GROW )
				{
					DrawGrowRect();
				}

				MouseDrag( (short)event->x, (short)event->y, event->modifiers );
				
				if ( m_nDragging == DRAGTYPE_SELECTION )
				{
					DrawFocusRect();
				}
				else if ( m_nDragging == DRAGTYPE_GROW )
				{
					DrawGrowRect();
				}

				if ( m_nDragging == DRAGTYPE_MOVEPOINTS_TIME ||
					 m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE )
				{
					DrawSelf();
				}
			}
			else
			{
				// See if anything is selected
				CountSelected();
				if ( m_nNumSelected <= 0 && g_pExpressionTool->IsFocusItem( this ) )
				{
					// Nothing selected
					// Draw auto highlight
					DrawAutoHighlight( event );
				}
			}
			iret = 1;
		}
		break;
	case mxEvent::MouseUp:
		{
			bool overgrow = IsMouseOverGrowHandle( (short)event->x, (short)event->y );
				
			if ( m_nDragging != DRAGTYPE_NONE )
			{
				if ( m_nDragging == DRAGTYPE_SELECTION )
				{
					DrawFocusRect();
				}
				else if ( m_nDragging == DRAGTYPE_GROW )
				{
					DrawGrowRect();
				}

				MouseDrag( (short)event->x, (short)event->y, event->modifiers, true );

				if ( m_nDragging == DRAGTYPE_GROW )
				{
					// Finish grow by resizing control
					int desiredheight = m_nCurrentHeight + event->y - m_nStartY;
					if ( desiredheight >= 10 )
					{
						m_nCurrentHeight = desiredheight;
						g_pExpressionTool->LayoutItems( true );
					}
				}
				else if ( m_nDragging != DRAGTYPE_MOVEPOINTS_VALUE &&
					 m_nDragging != DRAGTYPE_MOVEPOINTS_TIME )
				{
					SelectPoints();
				}
				else
				{
					PostDataChanged( "Move sample point(s)" );
				}

				m_nDragging = DRAGTYPE_NONE;

				DrawSelf();
			}
			
			bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false;
			bool shift = ( event->modifiers & mxEvent::KeyShift ) ? true : false;
			bool ctrl = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false;

			if ( !rightbutton && !shift && !ctrl )
			{
				if ( realtime - m_flLastClickTime < DOUBLE_CLICK_TIME )
				{
					if ( overgrow || IsCollapsed() )
					{
						OnDoubleClicked();
					}
				}

				m_flLastClickTime = realtime;
			}

			iret = 1;
		}
		break;
	}

	return iret;
}

void TimelineItem::MouseDrag( int x, int y, int modifiers, bool snap /*=false*/ )
{
	if ( m_nDragging == DRAGTYPE_NONE )
		return;

	int width = m_rcBounds.right - m_rcBounds.left;
	int height = m_rcBounds.bottom - m_rcBounds.top;

	if ( m_nDragging == DRAGTYPE_MOVEPOINTS_TIME ||
		 m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE )
	{
		int dx = x - m_nLastX;
		int dy = y - m_nLastY;

		if ( !( modifiers & mxEvent::KeyCtrl ) )
		{
			// Zero out motion on other axis
			if ( m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE )
			{
				dx = 0;
				x = m_nLastX;
			}
			else
			{
				dy = 0;
				y = m_nLastY;
			}
		}

		float dfdx = (float)dx / g_pExpressionTool->GetPixelsPerSecond();
		float dfdy = (float)dy / (float)height;

		g_pExpressionTool->MoveSelectedSamples( dfdx, dfdy, snap );

		// Update the scrubber
		if ( (float)width > 0 )
		{
			float t = GetTimeForMouse( x +  m_rcBounds.left );
			g_pExpressionTool->ForceScrubPosition( t );

			g_pMatSysWindow->Frame();
		}
	}

	m_nLastX = x;
	m_nLastY = y;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::DrawFocusRect( void )
{
	RECT rcFocus;
	
	rcFocus.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX;
	rcFocus.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX;

	rcFocus.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY;
	rcFocus.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY;

	POINT offset;
	offset.x = m_rcBounds.left;
	offset.y = m_rcBounds.top;
	ClientToScreen( (HWND)m_pWorkspace->getHandle(), &offset );
	OffsetRect( &rcFocus, offset.x, offset.y );

	HDC dc = GetDC( NULL );

	::DrawFocusRect( dc, &rcFocus );

	ReleaseDC( NULL, dc );
}

void TimelineItem::DrawSelf( void )
{
	CChoreoWidgetDrawHelper drawHelper( m_pWorkspace, m_rcBounds );
	Draw( drawHelper );
}

void TimelineItem::Draw( CChoreoWidgetDrawHelper& drawHelper )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	CChoreoEvent *e = track->GetEvent();
	if ( !e )
		return;

	Assert( e->HasEndTime() );

	bool active = track && ( IsValid() || IsActive() );

	float starttime;
	float endtime;

	g_pExpressionTool->GetStartAndEndTime( starttime, endtime );

	CountSelected();
	int scount = GetNumSelected();

	COLORREF bgColor = RGB( 230, 230, 200 );
	if ( IsCollapsed() && active )
	{
		bgColor = RGB( 200, 230, 200 );
	}

	RECT rcClient = m_rcBounds;

	drawHelper.DrawFilledRect( bgColor, rcClient );

	COLORREF gray = RGB( 200, 200, 200 );

	DrawEventEnd( drawHelper );

	DrawRelativeTags( drawHelper );
	if ( !IsCollapsed() && track )
	{
		if ( m_nEditType == 1 )
		{
			float zero = track->GetZeroValue( m_nEditType, true );

			drawHelper.DrawColoredLine( RGB( 180, 200, 220 ), PS_SOLID, 1, 
				rcClient.left, ( rcClient.top * zero + rcClient.bottom * (1 - zero)) ,
				rcClient.right, ( rcClient.top * zero + rcClient.bottom  * (1 - zero)) );
		}

		drawHelper.DrawOutlinedRect( RGB( 100, 150, 200 ), PS_SOLID, 1, rcClient );

		// Draw grow handle into background...
		if ( CanHaveGrowHandle() )
		{
			RECT handleRect;
			GetGrowHandleRect( handleRect );
			DrawGrowHandle( drawHelper, handleRect );
		}

		// Draw left/right underneath amount so go backbard
		for ( int type = ( track->IsComboType() ? 1 : 0 ); type >= 0; type-- )
		{
			COLORREF lineColor = ( type == m_nEditType ) ? RGB( 0, 0, 255 ) : gray;
			COLORREF shadowColor = ( type == m_nEditType ) ? RGB( 150, 150, 250 ) : gray;
			COLORREF dotColor = ( type == m_nEditType ) ? RGB( 0, 0, 255 ) : gray;
			COLORREF dotColorSelected = ( type == m_nEditType ) ? RGB( 240, 80, 20 ) : gray;

			int height = rcClient.bottom - rcClient.top;
			int bottom = rcClient.bottom;

			// Fixme, could look at 1st derivative and do more sampling at high rate of change?
			// or near actual sample points!
			float linelength = g_pExpressionTool->IsFocusItem( this ) ? 2.0f : 8.0f;

			float timestepperpixel = linelength / g_pExpressionTool->GetPixelsPerSecond();

			float stoptime = min( endtime, e->GetDuration() );
			
			float prev_t = starttime;
			float prev_value = track->GetFracIntensity( prev_t, type );

			CUtlVector< POINT > segments;

			/*
			if (type == m_nEditType)
			{
				// draw hermite version of time step
				float i0, i1, i2;
				float time10hz = starttime;

				i0 = track->GetFracIntensity( time10hz, type );
				i1 = i0;
				time10hz = starttime + 0.1;
				i2 = track->GetFracIntensity( time10hz, type );;
				
				for ( float t = starttime; t <= stoptime; t += timestepperpixel )
				{
					while (t >= time10hz)
					{
						time10hz += 0.1;
						i0 = i1;
						i1 = i2;
						i2 = track->GetFracIntensity( time10hz, type );;
					}

					float value = Hermite_Spline( i0, i1, i2, (t - time10hz + 0.1) / 0.1 );

					int prevx, x;

					bool clipped1, clipped2;
					x = GetMouseForTime( t, &clipped1 );
					prevx = GetMouseForTime( prev_t, &clipped2 );

					//if ( !clipped1 && !clipped2 )
					{
						// Draw segment
						//drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1,
						//	prevx, bottom - prev_value * height,
						//	x, bottom - value * height );
						
						POINT pt;

						if ( segments.Count() == 0 )
						{
							pt.x = prevx;	
							pt.y = bottom - prev_value * height;

							segments.AddToTail( pt );
						}

						pt.x = x;
						pt.y = bottom - value * height;

						segments.AddToTail( pt );
					}

					prev_t = t;
					prev_value = value;
				}

				if ( segments.Count() >= 2  )
				{
					drawHelper.DrawColoredPolyLine( shadowColor, PS_SOLID, 1, segments );
				}

				segments.RemoveAll();
			}
			*/
			for ( float t = starttime; t <= stoptime; t += timestepperpixel )
			{
				float value = track->GetFracIntensity( t, type );

				int prevx, x;

				bool clipped1, clipped2;
				x = GetMouseForTime( t, &clipped1 );
				prevx = GetMouseForTime( prev_t, &clipped2 );

				//if ( !clipped1 && !clipped2 )
				{
					// Draw segment
					//drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1,
					//	prevx, bottom - prev_value * height,
					//	x, bottom - value * height );
					
					POINT pt;

					if ( segments.Count() == 0 )
					{
						pt.x = prevx;	
						pt.y = bottom - prev_value * height;

						segments.AddToTail( pt );
					}

					pt.x = x;
					pt.y = bottom - value * height;

					segments.AddToTail( pt );
				}

				prev_t = t;
				prev_value = value;
			}

			if ( segments.Count() >= 2  )
			{
				drawHelper.DrawColoredPolyLine( lineColor, PS_SOLID, 1, segments );
			}

			for ( int sample = 0; sample < track->GetNumSamples( type ); sample++ )
			{
				bool dummy;
				CExpressionSample *start = track->GetBoundedSample( sample, dummy, type );

				/*
				int pixel = (int)( ( start->time / event_time ) * width + 0.5f);
				int x = m_rcBounds.left + pixel;
				float roundedfrac = (float)pixel / (float)width;
				*/
				float value = start->value; // track->GetFracIntensity( start->time, type );
				bool clipped = false;
				int x = GetMouseForTime( start->time, &clipped );
				if ( clipped )
					continue;
				int y = bottom - value * height;

				int dotsize = 6;
				int dotSizeSelected = 6;

				COLORREF clr = dotColor;
				COLORREF clrSelected = dotColorSelected;

				drawHelper.DrawCircle( 
					start->selected ? clrSelected : clr, 
					x, y, 
					start->selected ? dotSizeSelected : dotsize,
					true );


				if ( !start->selected )
					continue;

				if ( start->GetCurveType() == CURVE_DEFAULT )
					continue;

				// Draw curve type indicator...
				char sz[ 128 ];
				Q_snprintf( sz, sizeof( sz ), "%s", Interpolator_NameForCurveType( start->GetCurveType(), true ) );
				RECT rc;
				int fontSize = 9;
				rc.top = clamp( y + 5, rcClient.top + 2, rcClient.bottom - 2 - fontSize );
				rc.bottom = rc.top + fontSize + 1;
				rc.left = x - 75;
				rc.right = x + 175;
				drawHelper.DrawColoredText( "Arial", fontSize, 500, shadowColor, rc, sz );
			}
		}
	}

	if ( track && track->IsComboType() && !IsCollapsed() )
	{
		RECT title = rcClient;
		title.left += 10;
		title.top += 14;
		title.bottom = title.top + 9;

		char sz[ 128 ];

		if ( m_nEditType == 1 )
		{
			sprintf( sz, "left" );

			drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz );

			sprintf( sz, "right" );

			title.top = rcClient.bottom - 22;
			title.bottom = rcClient.bottom;

			drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz );
		}

		int mid = ( rcClient.top + rcClient.bottom ) / 2;

		title.top = mid - 10;
		title.bottom = mid;

		sprintf( sz, "editmode:  <%s>", m_nEditType == 0 ? "amount" : "left/right" );

		drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz );
	}

	if ( track )
	{
		RECT title = rcClient;
		title.left += 2;
		title.top += 2;
		title.bottom = title.top + 9;

		char const *name = track->GetFlexControllerName();
		char sz[ 128 ];

		if ( scount > 0 )
		{
			sprintf( sz, "{%i} - ", scount );

			int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz );
			drawHelper.DrawColoredText( "Arial", 9, 500, 
				RGB( 120, 120, 0 ), title, sz );

			title.left += len + 2;
		}

		sprintf( sz, "%s -", name );

		int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz );

		drawHelper.DrawColoredText( "Arial", 9, 500, 
			active ? RGB( 0, 150, 100 ) : RGB( 100, 100, 100 ), 
			title, sz );

		sprintf( sz, "%s", IsActive() ? "enabled" : "disabled" );

		title.left += len + 2;

		len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz );
		drawHelper.DrawColoredText( "Arial", 9, 500, 
			active ? RGB( 0, 150, 100 ) : RGB( 100, 100, 100 ), 
			title, sz );

		if ( active )
		{
			title.left += len + 2;

			sprintf( sz, " <%i>", track->GetNumSamples( 0 ) );

			len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz );
			drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 220, 0, 00 ), title, sz );
		}
	}
}

void TimelineItem::DrawAutoHighlight( mxEvent *event )
{
	if ( IsCollapsed() )
		return;
	
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	CExpressionSample *hover = GetSampleUnderMouse( event->x, event->y, 0.0f );
	CChoreoWidgetDrawHelper drawHelper( m_pWorkspace, m_rcBounds, true );

	RECT rcClient = m_rcBounds;

	// Draw left/right underneath amount so go backbard
	int type = m_nEditType;

	COLORREF dotColor = RGB( 0, 0, 255 );
	COLORREF dotColorSelected = RGB( 240, 80, 20 );
	COLORREF clrHighlighted = RGB( 0, 200, 0 );

	int height = rcClient.bottom - rcClient.top;
	int bottom = rcClient.bottom;

	int dotsize = 6;
	int dotSizeSelected = 6;
	int dotSizeHighlighted = 6;

	COLORREF clr = dotColor;
	COLORREF clrSelected = dotColorSelected;
	COLORREF bgColor = RGB( 230, 230, 200 );

	// Fixme, could look at 1st derivative and do more sampling at high rate of change?
	// or near actual sample points!
	for ( int sample = 0; sample < track->GetNumSamples( type ); sample++ )
	{
		bool dummy;
		CExpressionSample *start = track->GetBoundedSample( sample, dummy, type );

		float value = start->value;
		bool clipped = false;
		int x = GetMouseForTime( start->time, &clipped );
		if ( clipped )
			continue;
		int y = bottom - value * height;

		if ( hover == start )
		{
			drawHelper.DrawCircle( 
				bgColor, 
				x, y, 
				dotSizeHighlighted,
				true );

			drawHelper.DrawCircle( 
				clrHighlighted, 
				x, y, 
				dotSizeHighlighted,
				false );


		}
		else
		{
			drawHelper.DrawCircle( 
				start->selected ? clrSelected : clr, 
				x, y, 
				start->selected ? dotSizeSelected : dotsize,
				true );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : drawHelper - 
//-----------------------------------------------------------------------------
void TimelineItem::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	CChoreoEvent *event = track->GetEvent();
	if ( !event )
		return;

	float duration = event->GetDuration();

	if ( duration <= 0.0f )
		return;

	CChoreoScene *scene = g_pChoreoView->GetScene();
	if ( !scene )
		return;

	RECT rcClient = m_rcBounds;;
	//drawHelper.GetClientRect( rcClient );

	// Iterate relative tags
	for ( int i = 0; i < scene->GetNumActors(); i++ )
	{
		CChoreoActor *a = scene->GetActor( i );
		if ( !a )
			continue;

		for ( int j = 0; j < a->GetNumChannels(); j++ )
		{
			CChoreoChannel *c = a->GetChannel( j );
			if ( !c )
				continue;

			for ( int k = 0 ; k < c->GetNumEvents(); k++ )
			{
				CChoreoEvent *e = c->GetEvent( k );
				if ( !e )
					continue;

				// add each tag to combo box
				for ( int t = 0; t < e->GetNumRelativeTags(); t++ )
				{
					CEventRelativeTag *tag = e->GetRelativeTag( t );
					if ( !tag )
						continue;

					//SendMessage( control, CB_ADDSTRING, 0, (LPARAM)va( "\"%s\" \"%s\"", tag->GetName(), e->GetParameters() ) ); 
					bool clipped = false;
					int tagx = GetMouseForTime( tag->GetStartTime() - event->GetStartTime(), &clipped );
					if ( clipped )
						continue;

					drawHelper.DrawColoredLine( RGB( 180, 180, 220 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom );
				}
			}
		}
	}

	for ( int t = 0; t < event->GetNumTimingTags(); t++ )
	{
		CFlexTimingTag *tag = event->GetTimingTag( t );
		if ( !tag )
			continue;

		bool clipped = false;
		int tagx = GetMouseForTime( tag->GetStartTime() - event->GetStartTime(), &clipped );
		if ( clipped )
			continue;
		
		// Draw relative tag marker
		drawHelper.DrawColoredLine( RGB( 220, 180, 180 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *exp - 
//			flexnum - 
//-----------------------------------------------------------------------------
void TimelineItem::SetExpressionInfo( CFlexAnimationTrack *track, int flexnum )
{
	m_szTrackName[ 0 ] = 0;
	if ( track )
	{
		V_strcpy_safe( m_szTrackName, track->GetFlexControllerName() );
		SetActive( track->IsTrackActive() );
	}

	m_nFlexNum = flexnum;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::Copy( void )
{
	if ( !g_pExpressionTool )
		return;

	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	g_pExpressionTool->Copy( track );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::Paste( void )
{
	if ( !g_pExpressionTool )
		return;

	if ( !g_pExpressionTool->HasCopyData() )
		return;

	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	g_pExpressionTool->Paste( track );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::Clear( bool preserveundo )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	if ( preserveundo )
	{
		PreDataChanged( "Clear" );
	}

	track->Clear();

	if ( preserveundo )
	{
		PostDataChanged( "Clear" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : state - 
//-----------------------------------------------------------------------------
void TimelineItem::SetCollapsed( bool state )
{
	m_bCollapsed = state;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool TimelineItem::IsCollapsed( void ) const
{
	return m_bCollapsed;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int TimelineItem::GetHeight( void )
{
	return ( IsCollapsed() ? 12 : m_nCurrentHeight );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : state - 
//-----------------------------------------------------------------------------
void TimelineItem::SetActive( bool state )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	track->SetTrackActive( state );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool TimelineItem::IsActive( void )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return false;

	return track->IsTrackActive();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool TimelineItem::IsValid( void )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return false;

	if ( track->GetNumSamples( 0 ) > 0 )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : CFlexAnimationTrack
//-----------------------------------------------------------------------------
CFlexAnimationTrack *TimelineItem::GetSafeTrack( void )
{	
	if ( !g_pExpressionTool )
		return NULL;

	CChoreoEvent *ev = g_pExpressionTool->GetSafeEvent();
	if ( !ev )
		return NULL;

	// Find track by name
	for ( int i = 0; i < ev->GetNumFlexAnimationTracks() ; i++ )
	{
		CFlexAnimationTrack *track = ev->GetFlexAnimationTrack( i );
		if ( track && !stricmp( track->GetFlexControllerName(), m_szTrackName ) )
		{
			return track;
		}
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : type - 
//-----------------------------------------------------------------------------
void TimelineItem::SetEditType( int type )
{
	Assert( type == 0 || type == 1 );

	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track || !track->IsComboType() )
	{
		type = 0;
	}
	
	m_nEditType = type;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int TimelineItem::GetEditType( void )
{
	return m_nEditType;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::SelectPoints( void )
{
	RECT rcSelection;
	
	rcSelection.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX;
	rcSelection.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX;

	rcSelection.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY;
	rcSelection.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY;

	int selW = rcSelection.right - rcSelection.left;
	int selH = rcSelection.bottom - rcSelection.top;

	float tolerance = FP_TL_SELECTION_RECTANGLE_TOLERANCE;
	// If they are just clicking and releasing in one spot, capture any items w/in a larger tolerance
	if ( selW <= 2 && selH <= 2 )
	{
		tolerance = FP_TL_SELECTION_TOLERANCE;

		CExpressionSample *sample = GetSampleUnderMouse( rcSelection.left + selW * 0.5f, rcSelection.top + selH * 0.5f );
		if ( sample )
		{
			sample->selected = true;
			return;
		}
	}
	else
	{
		InflateRect( &rcSelection, 3, 3 );
	}

	int width = m_rcBounds.right - m_rcBounds.left;
	int height = m_rcBounds.bottom - m_rcBounds.top;

	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track || !width || !height )
		return;

	CChoreoEvent *e = track->GetEvent();
	Assert( e );
	if ( !e )
		return;

	float duration = e->GetDuration();

	float fleft = (float)GetTimeForMouse( rcSelection.left + m_rcBounds.left );
	float fright = (float)GetTimeForMouse( rcSelection.right + m_rcBounds.left );

	//fleft *= duration;
	//fright *= duration;

	float ftop = (float)rcSelection.top / (float)height;
	float fbottom = (float)rcSelection.bottom / (float)height;

	fleft = clamp( fleft, 0.0f, duration );
	fright = clamp( fright, 0.0f, duration );
	ftop = clamp( ftop, 0.0f, 1.0f );
	fbottom = clamp( fbottom, 0.0f, 1.0f );

	float timestepperpixel = 1.0f / g_pExpressionTool->GetPixelsPerSecond();
	float yfracstepperpixel = 1.0f / (float)height;

	float epsx = tolerance*timestepperpixel;
	float epsy = tolerance*yfracstepperpixel;

	for ( int i = 0; i < track->GetNumSamples( m_nEditType ); i++ )
	{
		CExpressionSample *sample = track->GetSample( i, m_nEditType );
		
		if ( sample->time + epsx < fleft )
			continue;

		if ( sample->time - epsx > fright )
			continue;

		if ( (1.0f - sample->value ) + epsy < ftop )
			continue;

		if ( (1.0f - sample->value ) - epsy > fbottom )
			continue;

		sample->selected = true;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *undodescription - 
//-----------------------------------------------------------------------------
void TimelineItem::PreDataChanged( char const *undodescription )
{
	if ( m_nUndoSetup  == 0 )
	{
		g_pChoreoView->SetDirty( true );
		g_pChoreoView->PushUndo( undodescription );
	}
	++m_nUndoSetup;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *redodescription - 
//-----------------------------------------------------------------------------
void TimelineItem::PostDataChanged( char const *redodescription )
{
	--m_nUndoSetup;
	if ( m_nUndoSetup == 0 )
	{
		g_pChoreoView->PushRedo( redodescription );
		g_pExpressionTool->InvalidateLayout();
	}
}

void TimelineItem::SetBounds( const RECT& rect )
{
	m_rcBounds = rect;
}

void TimelineItem::GetBounds( RECT& rect )
{
	rect = m_rcBounds;
}

void TimelineItem::SetVisible( bool vis )
{
	m_bVisible = vis;
}

bool TimelineItem::GetVisible( void ) const
{
	return m_bVisible;
}

int TimelineItem::GetNumSelected( void ) 
{
	return m_nNumSelected;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::SnapAll()
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	for ( int t = 0; t < 2; t++ )
	{
		for ( int i = 0; i <  track->GetNumSamples( t ); i++ )
		{
			CExpressionSample *sample = track->GetSample( i, t );
			sample->time = FacePoser_SnapTime( sample->time );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::SnapSelected()
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	for ( int t = 0; t < 2; t++ )
	{
		for ( int i = 0; i <  track->GetNumSamples( t ); i++ )
		{
			CExpressionSample *sample = track->GetSample( i, t );
			if ( !sample->selected )
				continue;

			sample->time = FacePoser_SnapTime( sample->time );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : start - 
//			end - 
//-----------------------------------------------------------------------------
void TimelineItem::DeletePoints( float start, float end )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	for ( int t = 0; t < 2; t++ )
	{
		int num = track->GetNumSamples( t );
		for ( int i = num - 1; i >= 0 ; i-- )
		{
			CExpressionSample *sample = track->GetSample( i, t );
			if ( sample->time < start || sample->time > end )
				continue;

			track->RemoveSample( i, t );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TimelineItem::OnDoubleClicked()
{
	// Disabled for now by request of BillF
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	SetCollapsed( !IsCollapsed() );
	g_pExpressionTool->LayoutItems( true );
}

void TimelineItem::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper )
{
	CFlexAnimationTrack *track = GetSafeTrack();
	if ( !track )
		return;

	CChoreoEvent *e = track->GetEvent();
	if ( !e )
		return;

	float duration = e->GetDuration();
	if ( !duration )
		return;

	int leftx = GetMouseForTime( duration );
	if ( leftx > m_rcBounds.right )
		return;

	drawHelper.DrawColoredLine(
		COLOR_CHOREO_ENDTIME, PS_SOLID, 1,
		leftx, m_rcBounds.top, leftx, m_rcBounds.bottom );

}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : helper - 
//			handleRect - 
//-----------------------------------------------------------------------------
void TimelineItem::DrawGrowHandle( CChoreoWidgetDrawHelper& helper, RECT& handleRect )
{
	Assert(CanHaveGrowHandle());

	RECT useRect = handleRect;
	helper.OffsetSubRect( useRect );

	POINT region[4];
	int cPoints = 4;

	region[ 0 ].x = useRect.left + GROW_HANDLE_INSETPIXELS;
	region[ 0 ].y = useRect.top;

	region[ 1 ].x = useRect.right - GROW_HANDLE_INSETPIXELS;
	region[ 1 ].y = useRect.top;

	region[ 2 ].x = useRect.right;
	region[ 2 ].y = useRect.bottom;

	region[ 3 ].x = useRect.left;
	region[ 3 ].y = useRect.bottom;

	HDC dc = helper.GrabDC();

	HRGN rgn = CreatePolygonRgn( region, cPoints, ALTERNATE );

	int oldPF = SetPolyFillMode( dc, ALTERNATE );
	
	HBRUSH brBg = CreateSolidBrush( RGB( 150, 150, 150 ) );
	HBRUSH brBorder = CreateSolidBrush( RGB( 200, 200, 200 ) );

	FillRgn( dc, rgn, brBg );
	FrameRgn( dc, rgn, brBorder, 1, 1 );

	SetPolyFillMode( dc, oldPF );

	DeleteObject( rgn );

	DeleteObject( brBg );
	DeleteObject( brBorder );

	// draw a line in the middle
	int midy = ( handleRect.bottom + handleRect.top ) * 0.5f;
	int lineinset = GROW_HANDLE_INSETPIXELS *1.5;

	helper.DrawColoredLine( RGB( 63, 63, 63 ), PS_SOLID, 1, 
		handleRect.left + lineinset, midy,
		handleRect.right - lineinset, midy );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : rc - 
//-----------------------------------------------------------------------------
void TimelineItem::GetGrowHandleRect( RECT& rc )
{
	rc = m_rcBounds;
	rc.bottom -= 1;
	rc.top = rc.bottom - GROW_HANDLE_HEIGHT;
	rc.left = ( rc.right + rc.left ) / 2 - GROW_HANDLE_WIDTH / 2;
	rc.right = rc.left +  GROW_HANDLE_WIDTH;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool TimelineItem::CanHaveGrowHandle()
{
	if ( IsCollapsed() )
		return false;

	if ( !g_pExpressionTool->IsFocusItem( this ) )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : x - 
//			y - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool TimelineItem::IsMouseOverGrowHandle( int x, int y)
{
	if ( !CanHaveGrowHandle() )
		return false;

	RECT rcGrowHandle;
	GetGrowHandleRect( rcGrowHandle );

	POINT pt;
	pt.x = x + m_rcBounds.left;
	pt.y = y + m_rcBounds.top;

	return PtInRect( &rcGrowHandle, pt ) ? true : false;
}

void TimelineItem::DrawGrowRect()
{
	RECT rcFocus = m_rcBounds;
	rcFocus.bottom = m_rcBounds.top + m_nLastY;
	OffsetRect( &rcFocus, -m_rcBounds.left, -m_rcBounds.top );

	POINT offset;
	offset.x = m_rcBounds.left;
	offset.y = m_rcBounds.top;
	ClientToScreen( (HWND)m_pWorkspace->getHandle(), &offset );
	OffsetRect( &rcFocus, offset.x, offset.y );

	HDC dc = GetDC( NULL );

	::DrawFocusRect( dc, &rcFocus );

	ReleaseDC( NULL, dc );
}

void TimelineItem::GetWorkList( bool reflect, CUtlVector< TimelineItem * >& list )
{
	if ( !reflect )
	{
		list.AddToTail( this );
	}
	else
	{
		g_pExpressionTool->GetTimelineItems( list );
	}
}