/*==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/>.

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 "MaxMain/MaxCompat.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:
    virtual BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    virtual void DeleteThis() { IDeleteSegMap(); }
    void SetThing(ReferenceTarget *m);

    virtual 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();

    DependentIterator di(this);
    ReferenceMaker* item = di.Next();
    while( item != nil )
    {
        TSTR s;
        item->GetClassName( s );

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

        else if( item->ClassID() == Class_ID(MULTI_CLASS_ID,0) )
        {
            // Multi-sub, run the refs on that guy (we only go one up)
            Mtl *multisub = (Mtl *)item;

            DependentIterator sub(multisub);
            ReferenceMaker* item2 = sub.Next();
            while( item2 != nil )
            {
                if( item2->SuperClassID() == BASENODE_CLASS_ID )
                    return true;        // Horray, a node has a ref to us!
                item2 = sub.Next();
            }

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

        item = di.Next();
    }

    // Enum dependents
    plGetRefs callback;
    ENUMDEPENDENTS(GetParentMtl(), &callback);
    for(int 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.
    DependentIterator di(this);
    ReferenceMaker* item = di.Next();
    while( item )
    {
        if( item->SuperClassID() == BASENODE_CLASS_ID )
            return (INode *)item;

        item = di.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 );
}