/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//																			//
//	plAnimStealthNode - Stealthy hidden INode that represents a single 		//
//						segment's worth of animation info for a material.	//
//						Stored as an INode so they can be "selected"		//
//						by components as targets of animation messages.		//
//																			//
//////////////////////////////////////////////////////////////////////////////

#include "hsTypes.h"
#include "plAnimStealthNode.h"
#include "plPassMtlBase.h"
#include "resource.h"

#include "../MaxComponent/plMaxAnimUtils.h"
#include "../MaxComponent/plPickNodeBase.h"

#include "iparamm2.h"

extern TCHAR *GetString( int id );
extern HINSTANCE hInstance;


//// Stealthy Class Desc /////////////////////////////////////////////////////

class plStealthClassDesc : public ClassDesc2
{
public:
	int 			IsPublic()		{ return FALSE; }
	void*			Create(BOOL loading) { return TRACKED_NEW plAnimStealthNode(loading); }
	const TCHAR*	ClassName()		{ return GetString( IDS_STEALTH_NAME ); }
	SClass_ID		SuperClassID()	{ return HELPER_CLASS_ID; }
	Class_ID		ClassID()		{ return ANIMSTEALTH_CLASSID; }
	const TCHAR* 	Category()		{ return NULL; }
	const TCHAR*	InternalName()	{ return _T("PlasmaAnimStealthInfo"); }
	HINSTANCE		HInstance()		{ return hInstance; }
};
static plStealthClassDesc sStealthClassDesc;
ClassDesc2* GetStealthClassDesc() { return &sStealthClassDesc; }

//// plStealthDlgProc /////////////////////////////////////////////////////////
//	Dialog proc for the anim stealth child dialog

class plStealthDlgProc : public ParamMap2UserDlgProc
{
protected:
	// Combo itemdata values
	enum
	{
		kName,		// Name of an animation/loop
		kDefault,	// Default combo value
		kInvalid,	// Invalid entry (couldn't find)
	};

	SegmentMap *fSegMap;

	HWND fhWnd;

public:
	BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
	void DeleteThis() { IDeleteSegMap(); }
	void SetThing(ReferenceTarget *m);

	void Update( TimeValue t, Interval &valid, IParamMap2 *pmap );

protected:
	// Set all the controls to their stored value
	void IInitControls( plAnimStealthNode *stealth, IParamBlock2 *pb);

	// Deletes all the allocated memory
	void IDeleteSegMap();

	void ILoadLoops(IParamBlock2 *pb);

	void ISetSel(HWND hCombo, const char *name);
};

const char *kAnimNameNone = ENTIRE_ANIMATION_NAME;

static plStealthDlgProc sStealthDlgProc;





//// Stealthy ParamBlock Desc ////////////////////////////////////////////////

static plEaseAccessor sEaseAccessor( plAnimStealthNode::kBlockPB, plAnimStealthNode::kPBEaseInMin, 
										plAnimStealthNode::kPBEaseInMax, plAnimStealthNode::kPBEaseInLength,
										plAnimStealthNode::kPBEaseOutMin, plAnimStealthNode::kPBEaseOutMax,
										plAnimStealthNode::kPBEaseOutLength );

ParamBlockDesc2 plAnimStealthNode::sAnimStealthPB
(
	kBlockPB, _T( "animStealth" ), IDS_STEALTH_NAME, GetStealthClassDesc(),//NULL,
										P_AUTO_CONSTRUCT + P_AUTO_UI, kRefParamBlock,

	// UI
	IDD_STEALTH_ANIM, IDS_STEALTH_NAME, 0, 0, &sStealthDlgProc,

	kPBName,			_T("animName"),		TYPE_STRING,		0, 0,
		end,

	kPBAutoStart,	_T("autoStart"),	TYPE_BOOL,			0, 0,
		p_ui,		TYPE_SINGLECHEKBOX, IDC_AUTO_START,
		p_default,	FALSE,
		end,

	kPBLoop,		_T("loop"),			TYPE_BOOL,			0, 0,
		p_ui,		TYPE_SINGLECHEKBOX, IDC_LOOP,
		p_default,	TRUE,
		end,
	kPBLoopName,	_T("loopName"),		TYPE_STRING,		0, 0,
		end,

	// Anim Ease
	kPBEaseInType,	_T("easeInType"),	TYPE_INT,		0, 0,
		p_ui,		TYPE_RADIO, 3, IDC_PASS_ANIM_EASE_IN_NONE, IDC_PASS_ANIM_EASE_IN_CONST_ACCEL, IDC_PASS_ANIM_EASE_IN_SPLINE,
		p_vals,		plAnimEaseTypes::kNoEase, plAnimEaseTypes::kConstAccel, plAnimEaseTypes::kSpline,
		p_default,	plAnimEaseTypes::kNoEase,
		end,
	kPBEaseInLength,	_T("easeInLength"),	TYPE_FLOAT,		0, 0,	
		p_default, 1.0,
		p_range, 0.1, 99.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	IDC_PASS_ANIM_EASE_IN_TIME, IDC_PASS_ANIM_EASE_IN_TIME_SPIN, 1.0,
		p_accessor, &sEaseAccessor,
		end,
	kPBEaseInMin,		_T("easeInMin"),	TYPE_FLOAT,		0, 0,	
		p_default, 1.0,
		p_range, 0.1, 99.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	IDC_PASS_ANIM_EASE_IN_MIN, IDC_PASS_ANIM_EASE_IN_MIN_SPIN, 1.0,
		p_accessor, &sEaseAccessor,
		end,
	kPBEaseInMax,	_T("easeInMax"),	TYPE_FLOAT,		0, 0,	
		p_default, 1.0,
		p_range, 0.1, 99.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	IDC_PASS_ANIM_EASE_IN_MAX, IDC_PASS_ANIM_EASE_IN_MAX_SPIN, 1.0,
		p_accessor, &sEaseAccessor,
		end,

	kPBEaseOutType,	_T("easeOutType"),	TYPE_INT,		0, 0,
		p_ui,		TYPE_RADIO, 3, IDC_PASS_ANIM_EASE_OUT_NONE, IDC_PASS_ANIM_EASE_OUT_CONST_ACCEL, IDC_PASS_ANIM_EASE_OUT_SPLINE,
		p_vals,		plAnimEaseTypes::kNoEase, plAnimEaseTypes::kConstAccel, plAnimEaseTypes::kSpline,
		p_default,	plAnimEaseTypes::kNoEase,
		end,
	kPBEaseOutLength,	_T("easeOutLength"),	TYPE_FLOAT,		0, 0,	
		p_default, 1.0,
		p_range, 0.1, 99.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	IDC_PASS_ANIM_EASE_OUT_TIME, IDC_PASS_ANIM_EASE_OUT_TIME_SPIN, 1.0,
		p_accessor, &sEaseAccessor,
		end,
	kPBEaseOutMin,		_T("easeOutMin"),	TYPE_FLOAT,		0, 0,	
		p_default, 1.0,
		p_range, 0.1, 99.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	IDC_PASS_ANIM_EASE_OUT_MIN, IDC_PASS_ANIM_EASE_OUT_MIN_SPIN, 1.0,
		p_accessor, &sEaseAccessor,
		end,
	kPBEaseOutMax,	_T("easeOutMax"),	TYPE_FLOAT,		0, 0,	
		p_default, 1.0,
		p_range, 0.1, 99.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	IDC_PASS_ANIM_EASE_OUT_MAX, IDC_PASS_ANIM_EASE_OUT_MAX_SPIN, 1.0,
		p_accessor, &sEaseAccessor,
		end,

	end
);

plAnimStealthNode::plAnimStealthNode( BOOL loading ) : fClassDesc(nil), fParamBlock(nil), fParentMtl(nil)
{
	fCachedSegMap = nil;
	fClassDesc = &sStealthClassDesc;
	fClassDesc->MakeAutoParamBlocks( this );
}

plAnimStealthNode::~plAnimStealthNode()
{
//	DeleteAllRefsFromMe();
}

CreateMouseCallBack *plAnimStealthNode::GetCreateMouseCallBack()
{
	return nil;
}

void	plAnimStealthNode::SetParentMtl( plPassMtlBase *parent )
{
	fParentMtl = parent;
}

bool	plAnimStealthNode::CanConvertToStealth( INode *objNode )
{
	return ( ConvertToStealth( objNode ) != nil );
}

plAnimStealthNode	*plAnimStealthNode::ConvertToStealth( INode *objNode )
{
	if( objNode == nil )
		return nil;

	Object *obj = objNode->GetObjectRef();
	if( obj == nil )
		return nil;

	if( obj->CanConvertToType( ANIMSTEALTH_CLASSID ) )
		return (plAnimStealthNode *)obj;

	return nil;
}


const char	*plAnimStealthNode::GetSegmentName( void ) const
{
	const char *str = fParamBlock->GetStr( (ParamID)kPBName );
	if( str == nil || str[ 0 ] == 0 )
		return ENTIRE_ANIMATION_NAME;
	return str;
}

void	plAnimStealthNode::SetSegment( const char *name )
{
	if( name == nil || strcmp(name, ENTIRE_ANIMATION_NAME) == 0 || name[ 0 ] == 0 )
		fParamBlock->SetValue( (ParamID)kPBName, 0, "" );
	else
		fParamBlock->SetValue( (ParamID)kPBName, 0, (char *)name );
}

void	plAnimStealthNode::SetNodeName( const char *parentName )
{
	INode *node = GetINode();
	if( node != nil )
	{
		char name[ 512 ], newName[ 512 ];
		sprintf( name, "%s : %s", parentName, GetSegmentName() );

		if( GetCOREInterface()->GetINodeByName( name ) != nil )
		{
			// For whatever reason, MakeNameUnique() doesn't ACTUALLY make a name unique!
			// So we just need to more or less do it ourselves...
			int i;
			for( i = 1; i < 1024; i++ )
			{
				sprintf( newName, "%s(%d)", name, i );
				if( GetCOREInterface()->GetINodeByName( newName ) == nil )
					break;
			}
			if( i == 1024 )
			{
				// You've got to be kidding me...
				char msg[ 2048 ];
				sprintf( msg, "WARNING: For some reason, we cannot find a unique name for the node '%s'. This"
							" will most likely cause export problems. Exactly how many of these do we HAVE??",
							name );
				hsMessageBox( msg, "WARNING!", hsMessageBoxNormal );
			}
		}
		else
			strcpy( newName, name );


		node->SetName( newName );
	}
}

int plAnimStealthNode::NumParamBlocks()
{
	return 1;
}

IParamBlock2 *plAnimStealthNode::GetParamBlock( int i )
{
	if( i == kRefParamBlock )
		return fParamBlock;

	return nil;
}

IParamBlock2 *plAnimStealthNode::GetParamBlockByID( BlockID id )
{
	if( fParamBlock && fParamBlock->ID() == id )
		return fParamBlock;

	return nil;
}

RefTargetHandle plAnimStealthNode::Clone(RemapDir &remap)
{
	plAnimStealthNode *obj = (plAnimStealthNode *)fClassDesc->Create( false );
	// Do the base clone
	BaseClone(this, obj, remap);
	// Copy our references
	if (fParamBlock)
		obj->ReplaceReference( kRefParamBlock, fParamBlock->Clone( remap ) );

	return obj;
}

void plAnimStealthNode::BuildMesh(TimeValue t)
{
}

void plAnimStealthNode::FreeCaches()
{
}

void plAnimStealthNode::GetLocalBoundBox(TimeValue t, INode *node, ViewExp *vpt, Box3 &box)
{
	box.MakeCube(Point3(0,0,0), 0);
}

void plAnimStealthNode::GetWorldBoundBox(TimeValue t, INode *node, ViewExp *vpt, Box3 &box)
{
	box.MakeCube(Point3(0,0,0), 0);
}

int plAnimStealthNode::Display(TimeValue t, INode *node, ViewExp *vpt, int flags)
{
	return 0;
}

int plAnimStealthNode::HitTest(TimeValue t, INode *node, int type, int crossing, int flags, IPoint2 *p, ViewExp *vpt)
{
	return 0;
}

int plAnimStealthNode::NumRefs()
{
	return 1;
}

RefTargetHandle plAnimStealthNode::GetReference( int i )
{
	if( i == kRefParamBlock )
		return fParamBlock;
	else if( i == kRefParentMtl )
		return fParentMtl;

	return nil;
}

void plAnimStealthNode::SetReference( int i, RefTargetHandle rtarg )
{
	if( i == kRefParamBlock )
		fParamBlock = (IParamBlock2 *)rtarg;
	else if( i == kRefParentMtl )
		fParentMtl = (plPassMtlBase *)rtarg;
}

RefResult plAnimStealthNode::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message)
{
	return REF_SUCCEED;
}

IOResult plAnimStealthNode::Save(ISave* isave)
{
	return IO_OK;
}

IOResult plAnimStealthNode::Load(ILoad* iload)
{
	return IO_OK;
}

plPassMtlBase	*plAnimStealthNode::GetParentMtl( void )
{
	return fParentMtl;
}

class plGetRefs : public DependentEnumProc
{
	public:

		hsTArray<ReferenceMaker *>	fList;

		plGetRefs() { }

		virtual int proc( ReferenceMaker *rmaker )
		{
			fList.Append( rmaker );
			return DEP_ENUM_CONTINUE;
		}
};

hsBool		plAnimStealthNode::IsParentUsedInScene( void )
{
	if( GetParentMtl() == nil )
		return false;

	// There are two possibilities: either a node uses us and thus has a ref to us,
	// or a multi-sub uses us that a node has a ref to us.

	// Note: we could do the loop as a helper function, but we only do it twice,
	// so it's not *really* worth the effort...
	//// NOTE: the following doesn't seem to work, but keeping here in case it ever does. 
	//// What really actually finds something is the enum dependents loop below
	const char *mtlName = GetParentMtl()->GetName();

	RefList		&refList = GetRefList();
	RefListItem	*item = refList.FirstItem();
	while( item != nil )
	{
		TSTR s;
		item->maker->GetClassName( s );

		if( item->maker->SuperClassID() == BASENODE_CLASS_ID && !CanConvertToStealth( (INode *)( item->maker ) ) )
			return true;		// Horray, a node has a ref to us!

		else if( item->maker->ClassID() == Class_ID(MULTI_CLASS_ID,0) )
		{
			// Multi-sub, run the refs on that guy (we only go one up)
			Mtl *multisub = (Mtl *)item->maker;
			RefList		&refList2 = multisub->GetRefList();
			RefListItem	*item2 = refList.FirstItem();
			while( item2 != nil )
			{
				if( item2->maker->SuperClassID() == BASENODE_CLASS_ID )
					return true;		// Horray, a node has a ref to us!
				item2 = item2->next;
			}

			// No go, keep trying
		}
		else if( item->maker->SuperClassID() == MATERIAL_CLASS_ID )
		{
			int q = 0;
		}

		item = item->next;
	}

	// Enum dependents
	int		i;

	plGetRefs callback;
	GetParentMtl()->EnumDependents( &callback );
	for( i = 0; i < callback.fList.GetCount(); i++ )
	{
		ReferenceMaker *maker = callback.fList[ i ];

		TSTR s;
		maker->GetClassName( s );

		if( maker->SuperClassID() == BASENODE_CLASS_ID && !CanConvertToStealth( (INode *)maker ) )
			return true;		// Horray, a node has a ref to us!
	}
	return false;
}

INode *plAnimStealthNode::GetINode()
{
	// Go through the reflist looking for RefMakers with a ref to this component.
	// There should only be one INode in this list.
	RefList &refList = GetRefList();
	RefListItem *item = refList.FirstItem();
	while( item )
	{
		if( item->maker->SuperClassID() == BASENODE_CLASS_ID )
			return (INode *)item->maker;

		item = item->next;
	}

	return nil;
}

void plStealthDlgProc::Update(TimeValue t, Interval& valid, IParamMap2* pmap)
{
	// Does the pmap match our pmap?

}

//// plStealthMouseOverrideProc //////////////////////////////////////////////
//	Because of wonderful linking problems with the MAX libraries, we can't
//	actually use CreateChildMParamMap2 like we should. So instead, we use
//	CreateChildCPParamMap2. However, *that* function calls the wrong interface
//	to handle untrapped mouse messages, with the result that clicking and 
//	dragging scrolls the command pane (where components are displayed) instead
//	of the material editor pane.
//	To override this, we subclass each dialog so that we can capture the mouse
//	messages before MAX processes them and then reroute them appropriately.
//	Note: because MAX already uses the window long of the given window, we can't
//	store the old proc of the window. However, since we always use 
//	CreateChildCPParamMap2, and because the MAX source code shows us that it
//	always uses the same dialog proc for all windows created with that function,
//	we can simply store the address of that proc the first time we subclass and
//	use it for restoring every time thereafter (see the following DlgProc)

static WNDPROC	sOldStealthDlgProc = nil;

static INT_PTR CALLBACK plStealthMouseOverrideProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	IParamMap2 *map = (IParamMap2 *)GetWindowLongPtr( hWnd, GWLP_USERDATA );

	switch( msg )
	{
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:
			{
				// We don't want the COREInterface to process our mouse messages with RollupMouseMessage;
				// rather, we want IMtlParams to do it just like it would if we could actually call
				// CreateChildMParamMap2
				IParamBlock2 *pb = map->GetParamBlock();
				if( pb != nil )
				{
					plAnimStealthNode *stealth = (plAnimStealthNode *)pb->GetOwner();
					if( stealth != nil )
					{
						plPassMtlBase *mtl = (plPassMtlBase *)stealth->GetParentMtl();
						mtl->fIMtlParams->RollupMouseMessage( hWnd, msg, wParam, lParam );
					}
				}
			return 0;
			}
	}

	if( sOldStealthDlgProc != nil )
		return CallWindowProc( sOldStealthDlgProc, hWnd, msg, wParam, lParam );
	else
		return 0;
}

BOOL plStealthDlgProc::DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	IParamBlock2 *pb = map->GetParamBlock();
	plAnimStealthNode *stealth = (plAnimStealthNode *)pb->GetOwner();

	switch (msg)
	{
		case WM_INITDIALOG:
		{
			// Install our override proc so we can capture mouse messages ourselves.
			// Note that the first time, we grab the old proc so we can restore with that
			// one every time after, since they should always be the same proc
			WNDPROC old = (WNDPROC)SetWindowLongPtr( hWnd, DWLP_DLGPROC, (LONG_PTR)plStealthMouseOverrideProc );
			if( sOldStealthDlgProc == nil )
				sOldStealthDlgProc = old;

			fhWnd = hWnd;
			IInitControls( stealth, pb );

			return TRUE;
		}

		case WM_DESTROY:
			// Restore our old proc
			SetWindowLongPtr( hWnd, DWLP_DLGPROC, (LONG_PTR)sOldStealthDlgProc );
			break;

		case WM_ENABLE:
			// The entire dialog was either enabled or disabled. 
			break;

	case WM_COMMAND:
		// Loop selection changed
		if( LOWORD( wParam ) == IDC_LOOPS && HIWORD( wParam ) == CBN_SELCHANGE )
		{
			// If a loop is selected, save it
			HWND hCombo = (HWND)lParam;
			int sel = SendMessage( hCombo, CB_GETCURSEL, 0, 0 );
			if( sel != CB_ERR )
			{
				if( SendMessage( hCombo, CB_GETITEMDATA, sel, 0 ) == kName )
				{
					char buf[256];
					SendMessage( hCombo, CB_GETLBTEXT, sel, (LPARAM)buf );
					pb->SetValue( (ParamID)plAnimStealthNode::kPBLoopName, 0, buf );
				}
				else
					pb->SetValue( (ParamID)plAnimStealthNode::kPBLoopName, 0, "" );
			}

			return TRUE;
		}

		// Auto-start or loop checkbox checked
		if( LOWORD( wParam ) == IDC_LOOP && HIWORD( wParam ) == BN_CLICKED )
		{
			BOOL checked = ( SendMessage( (HWND)lParam, BM_GETCHECK, 0, 0 ) == BST_CHECKED );

			pb->SetValue( plAnimStealthNode::kPBLoop, 0, checked );
			EnableWindow( GetDlgItem( hWnd, IDC_LOOPS ), checked );
			return TRUE;
		}

		// Refresh clicked
		else if( LOWORD( wParam ) == IDC_REFRESH_ANIMS && HIWORD( wParam ) == BN_CLICKED )
		{
			IInitControls( stealth, pb );
			return TRUE;
		}

		break;
	}

	return FALSE;
}

void plStealthDlgProc::SetThing(ReferenceTarget *m)
{
	plAnimStealthNode *stealth = (plAnimStealthNode *)m;
	IParamBlock2 *pb = stealth->GetParamBlockByID( plAnimStealthNode::kBlockPB );

	IInitControls( stealth, pb );
}

void plStealthDlgProc::IDeleteSegMap()
{
	// If we have a segment map, delete the memory associated with it
	DeleteSegmentMap( fSegMap );
	fSegMap = nil;
}

void plStealthDlgProc::ISetSel(HWND hCombo, const char *name)
{
	// If there is a name, try and set that
	if( name && strcmp( name, "" ) )
	{
		int idx = SendMessage( hCombo, CB_FINDSTRINGEXACT, -1, (LPARAM)name );
		// If we can't find the saved name add a "not found" entry, so they know what it was
		if( idx == -1 )
		{
			char buf[256];
			sprintf( buf, "(not found) %s", name );
			idx = SendMessage( hCombo, CB_ADDSTRING, 0, (LPARAM)buf );
			SendMessage( hCombo, CB_SETITEMDATA, idx, kInvalid );
		}

		SendMessage( hCombo, CB_SETCURSEL, idx, 0 );
	}
	// No name, set it to none
	else
	{
		int count = SendMessage( hCombo, CB_GETCOUNT, 0, 0 );
		for( int i = 0; i < count; i++ )
		{
			if( SendMessage( hCombo, CB_GETITEMDATA, i, 0 ) == kDefault )
				SendMessage( hCombo, CB_SETCURSEL, i, 0 );
		}
	}
}

void plStealthDlgProc::IInitControls( plAnimStealthNode *stealth, IParamBlock2 *pb )
{
	IDeleteSegMap();

	if( stealth->GetParentMtl() != nil )
	{
		fSegMap = GetAnimSegmentMap( stealth->GetParentMtl(), nil );

		ILoadLoops( pb );
	}
	else
	{
		// ?? What should we do?
		fSegMap = nil;
		hsStatusMessage( "No parent material yet in plStealthDlgProc::IInitControls()...not good..." );
	}

	// Enable/disable the loop dropdown
	EnableWindow( GetDlgItem( fhWnd, IDC_LOOPS ), pb->GetInt( (ParamID)plAnimStealthNode::kPBLoop ) );
}

void plStealthDlgProc::ILoadLoops(IParamBlock2 *pb)
{
	HWND hLoops = GetDlgItem( fhWnd, IDC_LOOPS );
	SendMessage( hLoops, CB_RESETCONTENT, 0, 0 );

	// Add the default option
	int defIdx = SendMessage( hLoops, CB_ADDSTRING, 0, (LPARAM)ENTIRE_ANIMATION_NAME );
	SendMessage( hLoops, CB_SETITEMDATA, defIdx, kDefault );

	const char *segName = pb->GetStr( (ParamID)plAnimStealthNode::kPBName );
	if( segName == nil || fSegMap == nil )
	{
		// Default of "entire animation", no other loop options
		SendMessage( hLoops, CB_SETCURSEL, defIdx, 0 );
		return;
	}
	
	SegmentSpec *animSpec = (*fSegMap)[ segName ];
	if( animSpec && fSegMap )
	{
		// for each segment we found: 
		for( SegmentMap::iterator i = fSegMap->begin(); i != fSegMap->end(); i++ )
		{
			SegmentSpec *spec = (*i).second;

			if( spec->fType == SegmentSpec::kLoop )
			{
				// If the loop is contained by the animation, add it
				if( (spec->fStart == -1 || spec->fStart >= animSpec->fStart) &&
					(spec->fEnd   == -1 || spec->fEnd   <= animSpec->fEnd) )
				{
					// Add the name
					int idx = SendMessage( hLoops, CB_ADDSTRING, 0, (LPARAM)spec->fName );
					SendMessage( hLoops, CB_SETITEMDATA, idx, kName );
				}		
			}
		}
	}

	ISetSel( hLoops, pb->GetStr( (ParamID)plAnimStealthNode::kPBLoopName ) );
}

void plAnimStealthNode::BeginEditParams(IObjParam *ip, ULONG flags, Animatable *prev)
{
	fClassDesc->BeginEditParams(ip, this, flags, prev);
}

void plAnimStealthNode::EndEditParams(IObjParam *ip, ULONG flags, Animatable *next)
{
	fClassDesc->EndEditParams(ip, this, flags, next);
}

//// ReleaseDlg //////////////////////////////////////////////////////////////

void	plAnimStealthNode::ReleaseDlg( void )
{
	IParamMap2 *map = fParamBlock->GetMap();
	fParamBlock->SetMap( nil );
	if( map != nil )
		DestroyChildCPParamMap2( map );
}

//// SwitchDlg ///////////////////////////////////////////////////////////////
//	Switch underlying objects in the dialog (to avoid unnecessary deletion/
//	recreations)

void	plAnimStealthNode::SwitchDlg( plAnimStealthNode *toSwitchTo )
{
	IParamMap2 *map = fParamBlock->GetMap();

	fParamBlock->SetMap( nil );
	toSwitchTo->fParamBlock->SetMap( map );

	map->SetParamBlock( toSwitchTo->fParamBlock );
	map->SetThing( (ReferenceTarget *)toSwitchTo );
	map->Invalidate();
	map->UpdateUI( 0 );
}

//// CreateAndEmbedDlg ///////////////////////////////////////////////////////
//	Create the dialog for this object and place it inside the given dialog, 
//	centering it in the given control if any.

bool	plAnimStealthNode::CreateAndEmbedDlg( IParamMap2 *parentMap, IMtlParams *parentParams, HWND frameCtrl )
{
	IParamMap2 *map = CreateChildCPParamMap2( fParamBlock, GetCOREInterface(), hInstance,
											parentMap, MAKEINTRESOURCE( IDD_STEALTH_ANIM ),
											nil, &sStealthDlgProc );
	fParamBlock->SetMap( map );

	if( frameCtrl != nil )
	{
		HWND child = fParamBlock->GetMap()->GetHWnd();
		RECT childFrame, centerFrame;

		::GetClientRect( child, &childFrame );
		::GetWindowRect( frameCtrl, &centerFrame );
		::MapWindowPoints( nil, parentMap->GetHWnd(), (POINT *)&centerFrame, 2 );

		int frameWidth = centerFrame.right - centerFrame.left;
		int frameHeight = centerFrame.bottom - centerFrame.top;
		int childWidth = childFrame.right - childFrame.left;
		int childHeight = childFrame.bottom - childFrame.top;

		::OffsetRect( &childFrame, ( frameWidth - childWidth ) >> 1, ( frameHeight - childHeight ) >> 1 );		
		::OffsetRect( &childFrame, centerFrame.left, centerFrame.top );		

		::SetWindowPos( child, nil, childFrame.left, childFrame.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER );
	}

	return true;
}

//// GetWinDlg ///////////////////////////////////////////////////////////////
//	Get the actual window handle of the currently active dialog displaying us

HWND	plAnimStealthNode::GetWinDlg( void ) const
{
	IParamMap2 *map = fParamBlock->GetMap();
	if( map != nil )
		return map->GetHWnd();

	return nil;
}

//// Picker Dialog for Restricted Animation Components //////////////////////////////////////////

class plPickAnimStealthNode : public plPickMtlNode
{
protected:
	ParamID fTypeID;

	void IAddUserType(HWND hList)
	{
		int type = fPB->GetInt(fTypeID);

		int idx = ListBox_AddString( hList, kUseParamBlockNodeString );
		if (type == plAnimObjInterface::kUseParamBlockNode && !fPB->GetINode(fNodeParamID))
			ListBox_SetCurSel(hList, idx);


		idx = ListBox_AddString( hList, kUseOwnerNodeString );
		if (type == plAnimObjInterface::kUseOwnerNode)
			ListBox_SetCurSel(hList, idx);
	}

	void ISetUserType(plMaxNode* node, const char* userType)
	{
		if( hsStrEQ( userType, kUseParamBlockNodeString ) )
		{
			ISetNodeValue(nil);
			fPB->SetValue(fTypeID, 0, plAnimObjInterface::kUseParamBlockNode);
		}
		else if( hsStrEQ(userType, kUseOwnerNodeString ) )
		{
			ISetNodeValue(nil);
			fPB->SetValue(fTypeID, 0, plAnimObjInterface::kUseOwnerNode);
		}
		else
			fPB->SetValue(fTypeID, 0, plAnimObjInterface::kUseParamBlockNode);
	}

public:
	plPickAnimStealthNode(IParamBlock2* pb, ParamID nodeParamID, ParamID typeID, Mtl *mtl) :
	  plPickMtlNode(pb, nodeParamID, mtl), fTypeID(typeID)
	{
	}
};

//// plAnimObjInterface Functions ////////////////////////////////////////////

void	plAnimStealthNode::PickTargetNode( IParamBlock2 *destPB, ParamID destParamID, ParamID typeID )
{
	plPickAnimStealthNode	pick( destPB, destParamID, typeID, (Mtl *)GetParentMtl() );
	pick.DoPick();
}

const char	*plAnimStealthNode::GetIfaceSegmentName( hsBool allowNil )
{
	// When sending messages to material animations, they're already addressed for the right
	// layer, no need for a segment name
	return nil;
}

//// Parameter Access Functions //////////////////////////////////////////////

#pragma warning( push )	
#pragma warning( disable:4800 )	// Forcing value to bool true or false (go figure, i'm even explicitly casting)
bool	plAnimStealthNode::GetAutoStart( void ) const	{ return (bool)fParamBlock->GetInt( (ParamID)kPBAutoStart ); }
void	plAnimStealthNode::SetAutoStart( bool b )		{ fParamBlock->SetValue( (ParamID)kPBAutoStart, 0, (int)b ); };

bool		plAnimStealthNode::GetLoop( void ) const					{ return fParamBlock->GetInt( (ParamID)kPBLoop ); }
const char	*plAnimStealthNode::GetLoopName( void ) const				{ return fParamBlock->GetStr( (ParamID)kPBLoopName ); }
void		plAnimStealthNode::SetLoop( bool b, const char *name )
{
	fParamBlock->SetValue( (ParamID)kPBLoop, 0, (int)b );
	if( name == nil )
		fParamBlock->SetValue( (ParamID)kPBLoopName, 0, "" );
	else
		fParamBlock->SetValue( (ParamID)kPBLoopName, 0, (char *)name );
}

UInt8		plAnimStealthNode::GetEaseInType( void ) const		{ return (UInt8)fParamBlock->GetInt( (ParamID)kPBEaseInType ); }
hsScalar	plAnimStealthNode::GetEaseInLength( void ) const	{ return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseInLength ); }
hsScalar	plAnimStealthNode::GetEaseInMin( void ) const		{ return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseInMin ); }
hsScalar	plAnimStealthNode::GetEaseInMax( void ) const		{ return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseInMax ); }
void		plAnimStealthNode::SetEaseIn( UInt8 type, hsScalar length, hsScalar min, hsScalar max )
{
	fParamBlock->SetValue( (ParamID)kPBEaseInType, 0, (int)type );
	fParamBlock->SetValue( (ParamID)kPBEaseInLength, 0, (float)length );
	fParamBlock->SetValue( (ParamID)kPBEaseInMin, 0, (float)min );
	fParamBlock->SetValue( (ParamID)kPBEaseInMax, 0, (float)max );
}

UInt8		plAnimStealthNode::GetEaseOutType( void ) const		{ return (UInt8)fParamBlock->GetInt( (ParamID)kPBEaseOutType ); }
hsScalar	plAnimStealthNode::GetEaseOutLength( void ) const	{ return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseOutLength ); }
hsScalar	plAnimStealthNode::GetEaseOutMin( void ) const		{ return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseOutMin ); }
hsScalar	plAnimStealthNode::GetEaseOutMax( void ) const		{ return (hsScalar)fParamBlock->GetFloat( (ParamID)kPBEaseOutMax ); }
void		plAnimStealthNode::SetEaseOut( UInt8 type, hsScalar length, hsScalar min, hsScalar max )
{
	fParamBlock->SetValue( (ParamID)kPBEaseOutType, 0, (int)type );
	fParamBlock->SetValue( (ParamID)kPBEaseOutLength, 0, (float)length );
	fParamBlock->SetValue( (ParamID)kPBEaseOutMin, 0, (float)min );
	fParamBlock->SetValue( (ParamID)kPBEaseOutMax, 0, (float)max );
}
#pragma warning( pop )	// Forcing value to bool true or false (go figure, i'm even explicitly casting)

//// Parent Accessor Functions ///////////////////////////////////////////////

plStealthNodeAccessor	&plStealthNodeAccessor::GetInstance( void )
{
	static plStealthNodeAccessor	instance;
	return instance;
}

void	plStealthNodeAccessor::ISetParent( ReferenceTarget *target, plPassMtlBase *parent )
{
	if( target != nil && target->ClassID() == ANIMSTEALTH_CLASSID )
	{
		( (plAnimStealthNode *)target )->SetParentMtl( parent );
	}
}

void	plStealthNodeAccessor::TabChanged( tab_changes changeCode, Tab<PB2Value> *tab, ReferenceMaker *owner, 
											ParamID id, int tabIndex, int count )
{
	if( changeCode == tab_insert || changeCode == tab_append )
	{
		if( owner->SuperClassID() != MATERIAL_CLASS_ID )
			return;
		plPassMtlBase *mtl = (plPassMtlBase *)owner;

		while( count > 0 )
		{
			ISetParent( (*tab)[ tabIndex ].r, mtl );
			tabIndex++;
			count--;
		}
	}
	else if( changeCode == tab_delete || changeCode == tab_ref_deleted )
	{
		// How are we supposed to handle this if we don't even get a stinkin pointer??
	}
}

void	plStealthNodeAccessor::Set( PB2Value &v, ReferenceMaker *owner, ParamID id, int tabIndex, TimeValue t )
{
	// Bit of error checking
	if( owner->SuperClassID() != MATERIAL_CLASS_ID )
		return;

	plPassMtlBase *mtl = (plPassMtlBase *)owner;

	IParamBlock2 *pb = mtl->fAnimPB;

	// A stealth node paramBlock value just got set. First make sure we 
	// un-set the old stealth's parent
	ISetParent( pb->GetReferenceTarget( id, tabIndex ), nil );
		
	// So make sure that the stealth node that was just added gets its parent mtl set properly
	ISetParent( v.r, mtl );
}