/*==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==*/
#include <float.h>
#include "hsStream.h"
#include "hsTimer.h"
#include "hsStlUtils.h"
#include "plSDL.h"

#include "pnProduct/pnProduct.h"
#include "pnFactory/plCreatable.h"
#include "pnKeyedObject/plUoid.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plKeyImp.h"
#include "pnNetCommon/plNetApp.h"
#include "pnNetCommon/pnNetCommon.h"

#include "plResMgr/plResManager.h"
#include "plResMgr/plKeyFinder.h"
#include "plUnifiedTime/plClientUnifiedTime.h"


#include "plResMgr/plResManager.h"
#include "plUnifiedTime/plClientUnifiedTime.h"


/*****************************************************************************
*
*   VALIDATE_WITH_FALSE_RETURN
*   Used in var getters and setters to validate incoming parameters and 
*   bail in a non-fatal way on error.
*
***/

#define VALIDATE_WITH_FALSE_RETURN(cond)    \
    if (!(cond)) {                          \
        plSDLMgr::GetInstance()->GetNetApp()->DebugMsg("SDL var. Validation failed: "#cond);    \
        ASSERT(!(cond));                    \
        return false;                       \
    }                                       //




//
// generic creatable which holds any kind of creatable blob
//
class plSDLCreatableStub : public plCreatable
{
private:
    UInt16 fClassIndex;
public:
    void* fData;
    int fDataLen;

    plSDLCreatableStub(UInt16 classIndex, int len) : fClassIndex(classIndex),fData(nil),fDataLen(len) {}
    ~plSDLCreatableStub() { delete [] fData; }

    const char*         ClassName() const { return "SDLCreatable";  }
    UInt16              ClassIndex() const { return fClassIndex;    }

    void Read(hsStream* s, hsResMgr* mgr) { delete [] fData; fData = TRACKED_NEW char[fDataLen]; s->Read(fDataLen, fData); }
    void Write(hsStream* s, hsResMgr* mgr) { s->Write(fDataLen, fData); }
};

/////////////////////////////////////////////////////
// plStateVarNotificationInfo
/////////////////////////////////////////////////////

void plStateVarNotificationInfo::Read(hsStream* s, UInt32 readOptions)
{
    UInt8 saveFlags=s->ReadByte();  // unused
    char* hint=s->ReadSafeString();
    if (hint && !(readOptions & plSDL::kSkipNotificationInfo))
        fHintString = (const char*)hint;
    // we're done with it...
    delete [] hint;
}

void plStateVarNotificationInfo::Write(hsStream* s, UInt32 writeOptions) const
{
    UInt8 saveFlags=0;              // unused   
    s->WriteSwap(saveFlags);
    s->WriteSafeString(fHintString.c_str());
}

/////////////////////////////////////////////////////
// plStateVariable
/////////////////////////////////////////////////////
bool plStateVariable::ReadData(hsStream* s, float timeConvert, UInt32 readOptions)
{
    UInt8 saveFlags;
    s->ReadSwap(&saveFlags);
    if (saveFlags & plSDL::kHasNotificationInfo)
    {
        GetNotificationInfo().Read(s, readOptions);
    }
    return true;
}

bool plStateVariable::WriteData(hsStream* s, float timeConvert, UInt32 writeOptions) const
{
    bool writeNotificationInfo = ((writeOptions & plSDL::kSkipNotificationInfo)==0);

    UInt8 saveFlags=0;
    if (writeNotificationInfo)
        saveFlags |= plSDL::kHasNotificationInfo;

    s->WriteSwap(saveFlags);
    if (writeNotificationInfo)
    {
        GetNotificationInfo().Write(s, writeOptions);
    }
    return true;
}

/////////////////////////////////////////////////////
// plSimpleStateVariable
/////////////////////////////////////////////////////

void plSimpleStateVariable::IInit() 
{
    SetDirty(false);
    SetUsed(false);
    fBy=nil;
    fS=nil;
    fI=nil;
    fF=nil;
    fD=nil;
    fB=nil;
    fU=nil;
    fS32=nil;
    fC=nil;
    fT=nil;
    fTimeStamp.ToEpoch();   
}

//
// delete memory
//

#define DEALLOC(type, var)  \
    case type:  \
        delete [] var;  \
        break;

void plSimpleStateVariable::IDeAlloc()
{
    int cnt = fVar.GetAtomicCount()*fVar.GetCount();
    int type = fVar.GetAtomicType();
    switch (type)
    {
    DEALLOC (plVarDescriptor::kInt, fI)
    DEALLOC (plVarDescriptor::kAgeTimeOfDay, fF)
    DEALLOC (plVarDescriptor::kShort, fS)
    DEALLOC (plVarDescriptor::kByte, fBy)
    DEALLOC (plVarDescriptor::kFloat, fF)
    DEALLOC (plVarDescriptor::kTime, fT)
    DEALLOC (plVarDescriptor::kDouble, fD)
    DEALLOC (plVarDescriptor::kBool, fB)
    DEALLOC (plVarDescriptor::kKey, fU)
    DEALLOC (plVarDescriptor::kString32, fS32)
    case plVarDescriptor::kCreatable:
        {
            if(fC)
            {
                int i;
                // delete each creatable
                for(i=0;i<cnt; i++)
                    delete fC[i];
                // delete creatable array
                delete [] fC;
            }
        }
        break;
    default:
        hsAssert(false, xtl::format("undefined atomic type:%d var:%s cnt:%d", 
            type, GetName() ? GetName() : "?", GetCount()).c_str());
        break;
    };

}

//
// alloc memory
//

#define SDLALLOC(typeName, type, var)   \
    case typeName:  \
        var = TRACKED_NEW type[cnt];    \
        break;

void plSimpleStateVariable::Alloc(int listSize)
{
    if (listSize != -1)
        fVar.SetCount(listSize);
    
    IDeAlloc();

    IInit();
    
    int cnt = fVar.GetAtomicCount()*fVar.GetCount();
    if (cnt)
    {
        switch (fVar.GetAtomicType())
        {
        SDLALLOC(plVarDescriptor::kInt, int, fI)
        SDLALLOC(plVarDescriptor::kAgeTimeOfDay, float, fF)
        SDLALLOC(plVarDescriptor::kByte, byte, fBy)
        SDLALLOC(plVarDescriptor::kShort, short, fS)
        SDLALLOC(plVarDescriptor::kFloat, float, fF)
        SDLALLOC(plVarDescriptor::kDouble, double, fD)
        SDLALLOC(plVarDescriptor::kBool, bool, fB)
        SDLALLOC(plVarDescriptor::kCreatable, plCreatable*, fC)
        case plVarDescriptor::kTime:
            fT = TRACKED_NEW plClientUnifiedTime[cnt];
            break;
        case plVarDescriptor::kKey:
            fU = TRACKED_NEW plUoid[cnt];
            break;
        case plVarDescriptor::kString32:
            fS32 = TRACKED_NEW plVarDescriptor::String32[cnt];
            break;
        default:
            hsAssert(false, "undefined atomic type");
            break;
        };
    }

    Reset();
}

#define RESET(typeName, type, var)  \
    case typeName:  \
        for(i=0;i<cnt;i++)  \
            var[i]=0;   \
        break;
void plSimpleStateVariable::Reset()
{
    int i, cnt = fVar.GetAtomicCount()*fVar.GetCount();
    if (cnt)
    {
        switch (fVar.GetAtomicType())
        {
        RESET(plVarDescriptor::kInt, int, fI)
        RESET(plVarDescriptor::kAgeTimeOfDay, float, fF)
        RESET(plVarDescriptor::kByte, byte, fBy)
        RESET(plVarDescriptor::kShort, short, fS)
        RESET(plVarDescriptor::kFloat, float, fF)
        RESET(plVarDescriptor::kDouble, double, fD)
        RESET(plVarDescriptor::kBool, bool, fB)
        RESET(plVarDescriptor::kCreatable, plCreatable*, fC)
        case plVarDescriptor::kTime:
            break;
        case plVarDescriptor::kKey:
            break;
        case plVarDescriptor::kString32:
            for(i=0;i<cnt;i++)  
                *fS32[i]=0; 
            break;
        default:
            hsAssert(false, "undefined atomic type");
            break;
        };
    }
}

//
// Copy the descriptor settings and allocate list
//
void plSimpleStateVariable::CopyFrom(plVarDescriptor* v)
{
    if (v)
    {
        plSimpleVarDescriptor* simV=(plSimpleVarDescriptor*)v;
        if (simV)
            fVar.CopyFrom(simV);
        else
            fVar.CopyFrom(v);   // copy base class

        Alloc();    
    }
}

void plSimpleStateVariable::TimeStamp( const plUnifiedTime & ut/*=plUnifiedTime::GetCurrentTime()*/ )
{
    fTimeStamp = ut;
}

//
// Set value from string.  Used to set default values which are specified as strings.
//
bool plSimpleStateVariable::SetFromString(const char* valueConst, int idx, bool timeStampNow)
{
    if (!valueConst)
        return false;

    std::string value = valueConst;
    plVarDescriptor::Type type=fVar.GetAtomicType();
    switch(type)
    {
    case plVarDescriptor::kAgeTimeOfDay:
    case plVarDescriptor::kTime:
    case plVarDescriptor::kDouble:
    case plVarDescriptor::kFloat:
    case plVarDescriptor::kInt:
    case plVarDescriptor::kShort:
    case plVarDescriptor::kByte:
        {   
            // handles value in the form "(i,j,k)" for vectors
            static char seps[] = "( ,)";
            char* ptr = strtok( (char*)value.c_str(), seps );
            int i=idx*fVar.GetAtomicCount();
            while (ptr)
            {
                if ((type==plVarDescriptor::kInt) && fI)
                    fI[i++] = atoi(ptr);
                else if (type==plVarDescriptor::kShort && fS)
                    fS[i++] = (short)atoi(ptr);
                else if (type==plVarDescriptor::kByte && fBy)
                    fBy[i++] = (byte)atoi(ptr);
                else if ( (type==plVarDescriptor::kFloat || type==plVarDescriptor::kAgeTimeOfDay) && fF)
                    fF[i++] = (float)atof(ptr);
                else if ( (type==plVarDescriptor::kDouble || type==plVarDescriptor::kTime) && fD)
                    fD[i++] = atof(ptr);
                ptr = strtok(nil, seps);
            }
        }
        break;
    case plVarDescriptor::kBool:
        {   
            // handles value in the form "(i,j,k)" for things like vectors
            static char seps[] = "( ,)";
            char* ptr = strtok( (char*)value.c_str(), seps );
            int i=idx*fVar.GetAtomicCount();
            while (ptr)
            {
                if (!stricmp(ptr, "true"))
                    fB[i++]=true;
                else
                if (!stricmp(ptr, "false"))
                    fB[i++]=false;
                else
                    fB[i++] = (atoi(ptr) != 0);
                ptr = strtok(nil, seps);
            }
        }
        break;
    case plVarDescriptor::kString32:
        {   
            // handles value in the form "(i,j,k)" for things like vectors
            static char seps[] = "( ,)";
            char* ptr = strtok( (char*)value.c_str(), seps );
            int i=idx*fVar.GetAtomicCount();
            while (ptr)
            {
                hsStrncpy(fS32[i++], ptr, 32);
                ptr = strtok(nil, seps);
            }
        }
        break;
    default:
        return false;   // err
    }   

    IVarSet(timeStampNow);
    return true;    // ok
}

//
// Called when a var has been set with a value
//
void plSimpleStateVariable::IVarSet(bool timeStampNow/*=true*/)
{
    if (timeStampNow)
        TimeStamp();
    SetDirty(true); 
    SetUsed(true);
}

//
// Get value as string.
//
char* plSimpleStateVariable::GetAsString(int idx) const
{
    int j;
    std::string str;
    if (fVar.GetAtomicCount()>1)
        str = str + "(";

    plVarDescriptor::Type type=fVar.GetAtomicType();
    switch(type)
    {
    case plVarDescriptor::kAgeTimeOfDay:
    case plVarDescriptor::kTime:
    case plVarDescriptor::kDouble:
    case plVarDescriptor::kFloat:
    case plVarDescriptor::kInt:
    case plVarDescriptor::kByte:
    case plVarDescriptor::kShort:
        {   
            // handles value in the form "(i,j,k)" for vectors
            int i=idx*fVar.GetAtomicCount();
            for(j=0;j<fVar.GetAtomicCount();j++)
            {
                if (type==plVarDescriptor::kInt)
                    str.append( xtl::format( "%d", fI[i++]) );
                else if (type==plVarDescriptor::kShort)
                    str.append( xtl::format( "%d", fS[i++]) );
                else if (type==plVarDescriptor::kByte)
                    str.append( xtl::format( "%d", fBy[i++]) );
                else if (type==plVarDescriptor::kFloat  || type==plVarDescriptor::kAgeTimeOfDay)
                    str.append( xtl::format( "%.3f", fF[i++]) );
                else if (type==plVarDescriptor::kDouble)
                    str.append( xtl::format( "%.3f", fD[i++]) );
                else if (type==plVarDescriptor::kTime)
                {
                    double tmp;
                    Get(&tmp, i++);
                    str.append( xtl::format( "%.3f", tmp) );
                }

                if (j==fVar.GetAtomicCount()-1)
                {
                    if (j)
                        str += ")";
                }
                else
                    str += ",";
            }
        }
        break;
    case plVarDescriptor::kBool:
        {   
            // handles value in the form "(i,j,k)" for things like vectors
            int i=idx*fVar.GetAtomicCount();
            for(j=0;j<fVar.GetAtomicCount();j++)
            {
                str.append( xtl::format( "%s", fB[i++] ? "true" : "false") );

                if (j==fVar.GetAtomicCount()-1)
                {
                    if (j)
                        str += ")";
                }
                else
                    str += ",";
            }
        }
        break;
    case plVarDescriptor::kString32:
        {   
            // handles value in the form "(i,j,k)" for things like vectors
            int i=idx*fVar.GetAtomicCount();
            for(j=0;j<fVar.GetAtomicCount();j++)
            {
                str.append( xtl::format( "%s", fS32[i++]) );

                if (j==fVar.GetAtomicCount()-1)
                {
                    if (j)
                        str += ")";
                }
                else
                    str += ",";
            }
        }
        break;
    default:
        {   
            // handles value in the form "(i,j,k)" for things like vectors
            int i=idx*fVar.GetAtomicCount();
            for(j=0;j<fVar.GetAtomicCount();j++)
            {
                str.append( xtl::format( "%s", "other") );

                if (j==fVar.GetAtomicCount()-1)
                {
                    if (j)
                        str += ")";
                }
                else
                    str += ",";
            }
        }
        break;
    }   

    return hsStrcpy(str.c_str());
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromRGB(plVarDescriptor::Type newType)
{
    switch(newType)
    {
    case plVarDescriptor::kRGBA:
        {
            // rgb to rgba
            int i,j;
            float* newF = TRACKED_NEW float[fVar.GetCount()*4];     // make more space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy with alpha=0 by default
                for(i=0;i<3;i++)
                    newF[j*4+i] = fF[j*fVar.GetAtomicCount()+i];
                newF[j*4+3] = 0;
            }
            delete [] fF;   // delete old
            fF = newF;      // use new
        }
        break;
    case plVarDescriptor::kRGBA8:
        {
            // rgb to rgba8
            int i,j;
            byte * newB = TRACKED_NEW byte [fVar.GetCount()*4];     // make more space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy with alpha=0 by default
                for(i=0;i<3;i++)
                    newB[j*4+i] = byte(fF[j*fVar.GetAtomicCount()+i]*255+.5);
                newB[j*4+3] = 0;
            }
            delete [] fF;   // delete old
            fBy = newB;     // use new
        }
        break;
    case plVarDescriptor::kRGB8:
        {
            // rgb to rgb8
            int i,j;
            byte * newB = TRACKED_NEW byte [fVar.GetCount()*3];     // make space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy with alpha=0 by default
                for(i=0;i<3;i++)
                    newB[j*3+i] = byte(fF[j*fVar.GetAtomicCount()+i]*255+.5);
            }
            delete [] fF;   // delete old
            fBy = newB;     // use new
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

bool plSimpleStateVariable::IConvertFromRGB8(plVarDescriptor::Type newType)
{
    switch(newType)
    {
    case plVarDescriptor::kRGBA:
        {
            // rgb8 to rgba
            int i,j;
            float* newF = TRACKED_NEW float[fVar.GetCount()*4];     // make more space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy with alpha=0 by default
                for(i=0;i<3;i++)
                    newF[j*4+i] = fBy[j*fVar.GetAtomicCount()+i]/255.f;
                newF[j*4+3] = 0;
            }
            delete [] fBy;  // delete old
            fF = newF;      // use new
        }
        break;
    case plVarDescriptor::kRGB:
        {
            // rgb8 to rgb
            int i,j;
            float* newF = TRACKED_NEW float[fVar.GetCount()*3];     // make more space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy with alpha=0 by default
                for(i=0;i<3;i++)
                    newF[j*3+i] = fBy[j*fVar.GetAtomicCount()+i]/255.f;
            }
            delete [] fBy;  // delete old
            fF = newF;      // use new
        }
        break;
    case plVarDescriptor::kRGBA8:
        {
            // rgb8 to rgba8
            int i,j;
            byte * newB = TRACKED_NEW byte [fVar.GetCount()*4];     // make more space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy with alpha=0 by default
                for(i=0;i<3;i++)
                    newB[j*4+i] = fBy[j*fVar.GetAtomicCount()+i];
                newB[j*4+3] = 0;
            }
            delete [] fBy;  // delete old
            fBy = newB;     // use new
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromRGBA(plVarDescriptor::Type newType)
{
    switch(newType)
    {
    case plVarDescriptor::kRGB:
        {
            // rgba to rgb
            int i,j;
            float* newF = TRACKED_NEW float[fVar.GetCount()*3];     // make less space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy and ignore alpha
                for(i=0;i<3;i++)
                    newF[j*3+i] = fF[j*fVar.GetAtomicCount()+i];
            }
            delete [] fF;   // delete old
            fF = newF;      // use new
        }
        break;
    case plVarDescriptor::kRGB8:
        {
            // rgba to rgb8
            int i,j;
            byte* newB = TRACKED_NEW byte[fVar.GetCount()*3];       // make less space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy and ignore alpha
                for(i=0;i<3;i++)
                    newB[j*3+i] = byte(fF[j*fVar.GetAtomicCount()+i]*255+.5);
            }
            delete [] fF;   // delete old
            fBy = newB;     // use new
        }
        break;
    case plVarDescriptor::kRGBA8:
        {
            // rgba to rgba8
            int i,j;
            byte* newBy = TRACKED_NEW byte [fVar.GetCount()*4];     // make less space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy and ignore alpha
                for(i=0;i<4;i++)
                    newBy[j*4+i] = byte(fF[j*fVar.GetAtomicCount()+i]*255+.5);
            }
            delete [] fF;   // delete old
            fBy = newBy;        // use new
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromRGBA8(plVarDescriptor::Type newType)
{
    switch(newType)
    {
    case plVarDescriptor::kRGB:
        {
            // rgba8 to rgb
            int i,j;
            float* newF = TRACKED_NEW float[fVar.GetCount()*3];     // make less space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy and ignore alpha
                for(i=0;i<3;i++)
                    newF[j*3+i] = fBy[j*fVar.GetAtomicCount()+i]/255.f;
            }
            delete [] fBy;  // delete old
            fF = newF;      // use new
        }
        break;
    case plVarDescriptor::kRGB8:
        {
            // rgba8 to rgb8
            int i,j;
            byte* newB = TRACKED_NEW byte[fVar.GetCount()*3];       // make less space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy and ignore alpha
                for(i=0;i<3;i++)
                    newB[j*3+i] = fBy[j*fVar.GetAtomicCount()+i];
            }
            delete [] fBy;  // delete old
            fBy = newB;     // use new
        }
        break;
    case plVarDescriptor::kRGBA:
        {
            // rgba8 to rgba
            int i,j;
            float* newF = TRACKED_NEW float[fVar.GetCount()*4];     // make less space
            for(j=0;j<fVar.GetCount(); j++)
            {
                // recopy and ignore alpha
                for(i=0;i<4;i++)
                    newF[j*4+i] = fBy[j*fVar.GetAtomicCount()+i]/255.f;
            }
            delete [] fBy;  // delete old
            fF = newF;      // use new
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromInt(plVarDescriptor::Type newType)
{
    int j;
    switch(newType)
    {
    case plVarDescriptor::kFloat:
        {
            // int to float
            float* newF = TRACKED_NEW float[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newF[j] = (float)(fI[j]);
            delete [] fI;
            fF = newF;
        }
        break;
    case plVarDescriptor::kShort:
        {
            // int to short
            short* newS = TRACKED_NEW short[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newS[j] = short(fI[j]);
            delete [] fI;
            fS = newS;
        }
        break;
    case plVarDescriptor::kByte:
        {
            // int to byte
            byte* newBy = TRACKED_NEW byte[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = byte(fI[j]);
            delete [] fI;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            // int to double
            double * newD = TRACKED_NEW double[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newD[j] = fI[j];
            delete [] fI;
            fD = newD;
        }
        break;
    case plVarDescriptor::kBool:
        {
            // int to bool
            bool * newB = TRACKED_NEW bool[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newB[j] = (fI[j]!=0);
            delete [] fI;
            fB = newB;
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromShort(plVarDescriptor::Type newType)
{
    int j;
    switch(newType)
    {
    case plVarDescriptor::kFloat:
        {
            float* newF = TRACKED_NEW float[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newF[j] = fS[j];
            delete [] fS;
            fF = newF;
        }
        break;
    case plVarDescriptor::kInt:
        {
            int* newI = TRACKED_NEW int[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newI[j] = short(fS[j]);
            delete [] fS;
            fI = newI;
        }
    case plVarDescriptor::kByte:
        {
            byte* newBy = TRACKED_NEW byte[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = byte(fS[j]);
            delete [] fS;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            double * newD = TRACKED_NEW double[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newD[j] = fS[j];
            delete [] fS;
            fD = newD;
        }
        break;
    case plVarDescriptor::kBool:
        {
            bool * newB = TRACKED_NEW bool[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newB[j] = (fS[j]!=0);
            delete [] fS;
            fB = newB;
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromByte(plVarDescriptor::Type newType)
{
    int j;
    switch(newType)
    {
    case plVarDescriptor::kFloat:
        {
            float* newF = TRACKED_NEW float[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newF[j] = fBy[j];
            delete [] fBy;
            fF = newF;
        }
        break;
    case plVarDescriptor::kInt:
        {
            int* newI = TRACKED_NEW int[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newI[j] = short(fBy[j]);
            delete [] fBy;
            fI = newI;
        }
    case plVarDescriptor::kShort:
        {
            short* newS = TRACKED_NEW short[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newS[j] = fBy[j];
            delete [] fBy;
            fS = newS;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            double * newD = TRACKED_NEW double[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newD[j] = fBy[j];
            delete [] fBy;
            fD = newD;
        }
        break;
    case plVarDescriptor::kBool:
        {
            bool * newB = TRACKED_NEW bool[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newB[j] = (fBy[j]!=0);
            delete [] fBy;
            fB = newB;
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromFloat(plVarDescriptor::Type newType)
{
    int j;
    switch(newType)
    {
    case plVarDescriptor::kInt:
        {
            int* newI = TRACKED_NEW int[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newI[j] = (int)(fF[j]+.5f); // round to nearest int
            delete [] fF;
            fI = newI;
        }
        break;
    case plVarDescriptor::kShort:
        {
            short* newS = TRACKED_NEW short[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newS[j] = (short)(fF[j]+.5f);   // round to nearest int
            delete [] fF;
            fS = newS;
        }
        break;
    case plVarDescriptor::kByte:
        {
            byte* newBy = TRACKED_NEW byte[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = (byte)(fF[j]+.5f);   // round to nearest int
            delete [] fF;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            double* newD = TRACKED_NEW double[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newD[j] = fF[j];
            delete [] fF;
            fD = newD;
        }
        break;
    case plVarDescriptor::kBool:
        {
            bool* newB = TRACKED_NEW bool[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newB[j] = (fF[j]!=0);
            delete [] fF;
            fB = newB;
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromDouble(plVarDescriptor::Type newType)
{
    int j;
    switch(newType)
    {
    case plVarDescriptor::kInt:
        {
            int* newI = TRACKED_NEW int[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newI[j] = (int)(fD[j]+.5f); // round to nearest int
            delete [] fD;
            fI = newI;
        }
        break;
    case plVarDescriptor::kShort:
        {
            short* newS = TRACKED_NEW short[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newS[j] = (short)(fD[j]+.5f);   // round to nearest int
            delete [] fD;
            fS = newS;
        }
        break;
    case plVarDescriptor::kByte:
        {
            byte* newBy = TRACKED_NEW byte[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = (byte)(fD[j]+.5f);   // round to nearest int
            delete [] fD;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kFloat:
        {
            float* newF = TRACKED_NEW float[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newF[j] = (float)(fD[j]);
            delete [] fD;
            fF = newF;
        }
        break;
    case plVarDescriptor::kBool:
        {
            bool* newB = TRACKED_NEW bool[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newB[j] = (fD[j]!=0);
            delete [] fD;
            fB = newB;
        }
        break;
    default:
        return false;   // err
    }
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromBool(plVarDescriptor::Type newType)
{
    int j;
    switch(newType)
    {
    case plVarDescriptor::kInt:
        {
            int* newI = TRACKED_NEW int[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newI[j] = (fB[j] == true ? 1 : 0);
            delete [] fB;
            fI = newI;
        }
        break;
    case plVarDescriptor::kShort:
        {
            short* newS = TRACKED_NEW short[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newS[j] = (fB[j] == true ? 1 : 0);
            delete [] fB;
            fS = newS;
        }
        break;
    case plVarDescriptor::kByte:
        {
            byte* newBy = TRACKED_NEW byte[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = (fB[j] == true ? 1 : 0);
            delete [] fB;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kFloat:
        {
            float* newF = TRACKED_NEW float[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newF[j] = (fB[j] == true ? 1.f : 0.f);
            delete [] fB;
            fF = newF;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            double* newD= TRACKED_NEW double[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newD[j] = (fB[j] == true ? 1.f : 0.f);
            delete [] fB;
            fD = newD;
        }
        break;
    default:
        return false;   // err
    }
    
    return true;
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromString(plVarDescriptor::Type newType)
{
    int j;
    switch(newType)
    {
        case plVarDescriptor::kBool:
        // string to bool
            for(j=0;j<fVar.GetCount(); j++)
            {
                if (!stricmp(fS32[j], "true") || !stricmp(fS32[j], "1"))
                    fB[j]=true;
                else
                    if (!stricmp(fS32[j], "false") || !stricmp(fS32[j], "0"))
                        fB[j]=false;
                    else
                        return false;   // err
            }
        break;
            
        case plVarDescriptor::kInt:
        // string to int
            for(j=0;j<fVar.GetCount(); j++)
            {
                fI[j] = atoi(fS32[j]);
            }
        break;
            
        case plVarDescriptor::kFloat:
        // string to float
            for(j=0;j<fVar.GetCount(); j++)
            {
                fF[j] = (float) atof(fS32[j]);
            }
        break;

        default:
            return false;   // err
    }       
                
    return true;
}

//
// Try to convert my value to another type.
// return false on err.
// called when a data record is read in
//
bool plSimpleStateVariable::ConvertTo(plSimpleVarDescriptor* toVar, bool force )
{
    // NOTE: 'force' has no meaning here really, so it is not inforced.

    plVarDescriptor::Type newType = toVar->GetType(); 

    int cnt = toVar->GetCount() ? toVar->GetCount() : fVar.GetCount();

    if (cnt > fVar.GetCount()) {
        #if BUILD_TYPE == BUILD_TYPE_DEV
            FATAL("SDL Convert: array size increased, conversion loses data");
        #endif
        // Reallocate new memory (destroys existing variable state)
        Alloc(cnt);
        // match types now
        fVar.SetCount(cnt);
        fVar.SetType(toVar->GetType());
        fVar.SetAtomicType(toVar->GetAtomicType());
        return true;
    }

    fVar.SetCount(cnt); // convert count

    // types are already the same, done.
    if (fVar.GetType()==newType )
        return true;

    hsLogEntry( plNetApp::StaticDebugMsg( "SSV(%p) converting %s from %s to %s",
        this, fVar.GetName(), fVar.GetTypeString(), toVar->GetTypeString() ) );

    switch(fVar.GetType())  // original type
    {
        // FROM RGB
    case plVarDescriptor::kRGB:
        if (!IConvertFromRGB(newType))
            return false;
        break;

        // FROM RGBA
    case plVarDescriptor::kRGBA:
        if (!IConvertFromRGBA(newType))
            return false;
        break;

        // FROM RGB8
    case plVarDescriptor::kRGB8:
        if (!IConvertFromRGB8(newType))
            return false;
        break;

        // FROM RGBA8
    case plVarDescriptor::kRGBA8:
        if (!IConvertFromRGBA8(newType))
            return false;
        break;

        // FROM INT
    case plVarDescriptor::kInt:
        if (!IConvertFromInt(newType))
            return false;
        break;

        // FROM SHORT
    case plVarDescriptor::kShort:
        if (!IConvertFromShort(newType))
            return false;
        break;

        // FROM Byte
    case plVarDescriptor::kByte:
        if (!IConvertFromByte(newType))
            return false;
        break;

        // FROM FLOAT
    case plVarDescriptor::kFloat:
        if (!IConvertFromFloat(newType))
            return false;
        break;

        // FROM DOUBLE
    case plVarDescriptor::kDouble:
        if (!IConvertFromDouble(newType))
            return false;
        break;

        // FROM BOOL
    case plVarDescriptor::kBool:
        if (!IConvertFromBool(newType))
            return false;
        break;

        // FROM STRING32
    case plVarDescriptor::kString32:
        if (!IConvertFromString(newType))
            return false;
        break;

    default:
        return false;   // err  
    }   

    // match types now
    fVar.SetType(toVar->GetType());
    fVar.SetAtomicType(toVar->GetAtomicType());

    return true;    // ok
}

/////////////////////////////////////////////////////////////
// SETTERS
/////////////////////////////////////////////////////////////

bool plSimpleStateVariable::Set(float v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kAgeTimeOfDay)
    {
        hsAssert(false, "AgeTime variables are read-only, can't set");
    }
    else
    if (fVar.GetType()==plVarDescriptor::kFloat)
    {
        fF[idx]=v;
        IVarSet();
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

bool plSimpleStateVariable::Set(double v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kDouble)
    {
        fD[idx]=v;
        IVarSet();
        return true;
    }

    if (fVar.GetType()==plVarDescriptor::kTime)
    {   // convert from, 
        fT[idx].SetFromGameTime(v, hsTimer::GetSysSeconds());       
        IVarSet();
        return true;
    }

    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

// floatvector
bool plSimpleStateVariable::Set(float* v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kAgeTimeOfDay)
    {
        hsAssert(false, "AgeTime variables are read-only, can't set");
    }
    else    
    if (fVar.GetAtomicType()==plVarDescriptor::kFloat)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            fF[cnt+i]=v[i];
        IVarSet();
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

// bytevector
bool plSimpleStateVariable::Set(byte* v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kByte)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            fBy[cnt+i]=v[i];
        IVarSet();
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

// 
bool plSimpleStateVariable::Set(double* v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kDouble)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            fD[cnt+i]=v[i];
        IVarSet();
        return true;
    }

    if (fVar.GetAtomicType()==plVarDescriptor::kTime)
    {
        double secs=hsTimer::GetSysSeconds();
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            fT[cnt+i].SetFromGameTime(v[i], secs);      
        IVarSet();
        return true;
    }

    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

bool plSimpleStateVariable::Set(int v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kInt)
    {
        fI[idx]=v;
        IVarSet();
        return true;
    }
    else
    if (fVar.GetType()==plVarDescriptor::kBool)
        return Set((bool)(v?true:false), idx);  // since 'true' is coming in as an int not bool
    else
    if (fVar.GetType()==plVarDescriptor::kShort)
        return Set((short)v, idx);
    else
    if (fVar.GetType()==plVarDescriptor::kByte)
        return Set((byte)v, idx);

    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

bool plSimpleStateVariable::Set(short v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kShort)
    {
        fS[idx]=v;
        IVarSet();
        return true;
    }
    else
    if (fVar.GetType()==plVarDescriptor::kInt)
        return Set((int)v, idx);
    else
    if (fVar.GetType()==plVarDescriptor::kByte)
        return Set((byte)v, idx);

    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

bool plSimpleStateVariable::Set(byte v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kByte)
    {
        fBy[idx]=v;
        IVarSet();
        return true;
    }
    else
    if (fVar.GetType()==plVarDescriptor::kBool)
        return Set((bool)(v?true:false), idx);
    else
    if (fVar.GetType()==plVarDescriptor::kInt)
        return Set((int)v, idx);
    else
    if (fVar.GetType()==plVarDescriptor::kShort)
        return Set((short)v, idx);

    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

bool plSimpleStateVariable::Set(const char* v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (v && fVar.GetType()==plVarDescriptor::kString32)
    {
        hsAssert(hsStrlen(v)<32, "string length overflow");
        hsStrncpy(fS32[idx], v, 32);
        IVarSet();
        return true;
    }
    hsAssert(false, v ? "passing wrong value type to SDL variable" : "trying to set nil string");
    return false;
}

bool plSimpleStateVariable::Set(bool v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kBool)
    {
        fB[idx]=v;
        IVarSet();
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

bool plSimpleStateVariable::Set(const plKey& v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kKey)
    {
        if(v) 
        {
            fU[idx] = v->GetUoid();
        } 
        else 
        {
            fU[idx] = plUoid();
        }
        IVarSet();
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

bool plSimpleStateVariable::Set(plCreatable* v, int idx)
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kCreatable)
    {
        // copy creatable via stream
        hsRAMStream stream;
        if(v)
        {
            hsgResMgr::ResMgr()->WriteCreatable(&stream, v);
            stream.Rewind();
        }
        plCreatable* copy = v ? hsgResMgr::ResMgr()->ReadCreatable(&stream): nil;
        hsAssert(!v || copy, "failed to create creatable copy");
        fC[idx]=copy;
        IVarSet();
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable");
    return false;
}

/////////////////////////////////////////////////////////////
// GETTERS
/////////////////////////////////////////////////////////////

bool plSimpleStateVariable::Get(int* value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());
    
    if (fVar.GetAtomicType()==plVarDescriptor::kInt)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fI[cnt+i];
        return true;
    }
    
    if (fVar.GetAtomicType()==plVarDescriptor::kShort)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fS[cnt+i];
        return true;
    }

    if (fVar.GetAtomicType()==plVarDescriptor::kByte)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fBy[cnt+i];
        return true;
    }

    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

bool plSimpleStateVariable::Get(short* value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kShort)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fS[cnt+i];
        return true;
    }
    
    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

bool plSimpleStateVariable::Get(byte* value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kByte)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fBy[cnt+i];
        return true;
    }
    
    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

// float or floatVector
bool plSimpleStateVariable::Get(float* value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kAgeTimeOfDay)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            if (plNetClientApp::GetInstance())
                fF[cnt+i] = plNetClientApp::GetInstance()->GetCurrentAgeTimeOfDayPercent();
            value[i]=fF[cnt+i];
        }
        return true;
    }

    if (fVar.GetAtomicType()==plVarDescriptor::kFloat)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fF[cnt+i];
        return true;
    }

    if (fVar.GetAtomicType()==plVarDescriptor::kDouble) 
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=(float)fD[cnt+i];
        return true;
    }

    if (fVar.GetAtomicType()==plVarDescriptor::kTime)   // && fIsUsed)
    {
        double secs=hsTimer::GetSysSeconds();
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            double tmp;
            fT[cnt+i].ConvertToGameTime(&tmp, secs);
            value[i] = (float)tmp;
        }
        return true;
    }

    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

// double
bool plSimpleStateVariable::Get(double* value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kDouble)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fD[cnt+i];
        return true;
    }

    if (fVar.GetAtomicType()==plVarDescriptor::kTime)
    {
        double secs=hsTimer::GetSysSeconds();
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            fT[cnt+i].ConvertToGameTime(&value[i], secs);

        return true;
    }

    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

bool plSimpleStateVariable::Get(bool* value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kBool)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i]=fB[cnt+i];
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

bool plSimpleStateVariable::Get(plKey* value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kKey)
    {
        if(!(fU[idx] == plUoid()))  // compare to default "nil uoid"
        {
            *value = hsgResMgr::ResMgr()->FindKey(fU[idx]);
            if (*value)
            {
                const plUoid& newUoid = (*value)->GetUoid();
                if (stricmp(newUoid.GetObjectName(), fU[idx].GetObjectName()) != 0)
                {
                    // uoid names don't match... chances are the key changed in the local data after the key was written to the sdl
                    // do a search by name, which takes longer, to get the correct key
                    std::vector<plKey> foundKeys;
                    plKeyFinder::Instance().ReallyStupidSubstringSearch(fU[idx].GetObjectName(), fU[idx].GetClassType(), foundKeys, fU[idx].GetLocation());
                    // not really sure what we can do if it finds MORE then one (they are supposed to be unique names), so just grab the
                    // first one and return it
                    if (foundKeys.size() >= 1)
                        *value = foundKeys[0];
                    else
                        *value = nil;
                }
            }
        } else {
            *value = nil;
        }
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

bool plSimpleStateVariable::Get(char value[], int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetType()==plVarDescriptor::kString32)
    {
        hsStrcpy(value, fS32[idx]);
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

bool plSimpleStateVariable::Get(plCreatable** value, int idx) const
{
    VALIDATE_WITH_FALSE_RETURN(idx < fVar.GetCount());

    if (fVar.GetAtomicType()==plVarDescriptor::kCreatable)
    {
        *value = nil;
        plCreatable* v = fC[idx];
        if (v)
        {
            *value = plFactory::Create(v->ClassIndex());
            hsAssert(*value, "failed to create creatable copy");
            hsRAMStream stream;
            v->Write(&stream, hsgResMgr::ResMgr());
            stream.Rewind();
            (*value)->Read(&stream, hsgResMgr::ResMgr());
        }
        return true;
    }
    hsAssert(false, "passing wrong value type to SDL variable"); 
    return false;
}

/////////////////////////////////////////////////////////////

const char* plSimpleStateVariable::GetKeyName(int idx) const
{
    if (fVar.GetAtomicType()==plVarDescriptor::kKey)
    {
        if(!(fU[idx] == plUoid()))  // compare to default "nil uoid"
        {
            return fU[idx].GetObjectName();
        }
    }
    hsAssert(false, "passing wrong value type to SDL variable"); 
    return "(nil)";
}

#pragma optimize( "g", off )    // disable float optimizations
bool plSimpleStateVariable::IWriteData(hsStream* s, float timeConvert, int idx, UInt32 writeOptions) const
{
#ifdef HS_DEBUGGING
    if (!IsUsed())
    {
        // hsAssert(false, "plSimpleStateVariable::WriteData doesn't contain data?");
        plNetApp::StaticWarningMsg("plSimpleStateVariable::WriteData Var %s doesn't contain data?",
            GetName());
    }
#endif

    int j=idx*fVar.GetAtomicCount();
    int i;
    switch(fVar.GetAtomicType())
    {
    case plVarDescriptor::kAgeTimeOfDay:
        // don't need to write out ageTime, since it's computed on the fly when Get is called
        break;
    case plVarDescriptor::kInt:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteSwap32(fI[j+i]);
        break;
    case plVarDescriptor::kShort:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteSwap16(fS[j+i]);
        break;
    case plVarDescriptor::kByte:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteByte(fBy[j+i]);
        break;
    case plVarDescriptor::kFloat:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteSwapScalar(fF[j+i]);
        break;
    case plVarDescriptor::kTime:
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            if (timeConvert != 0.0)
            {
                double utDouble=fT[j+i].GetSecsDouble();
                hsDoublePrecBegin
                utDouble += timeConvert;
                hsDoublePrecEnd
                plUnifiedTime ut(utDouble);
                ut.Write(s);
            }
            else
                fT[j+i].Write(s);
        }
        break;
    case plVarDescriptor::kDouble:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteSwapDouble(fD[j+i]);
        break;
    case plVarDescriptor::kBool:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->Writebool(fB[j+i]);
        break;
    case plVarDescriptor::kKey:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fU[j+i].Write(s);
        break;
    case plVarDescriptor::kString32:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->Write(32, fS32[j+i]);
        break;      
    case plVarDescriptor::kCreatable:
        {
            hsAssert(fVar.GetAtomicCount()==1, "invalid atomic count");
            plCreatable* cre = fC[j];
            s->WriteSwap16(cre ? cre->ClassIndex() : 0x8000);   // creatable class index
            if (cre)
            {
                hsRAMStream ramStream;
                cre->Write(&ramStream, hsgResMgr::ResMgr());
                s->WriteSwap32(ramStream.GetEOF());     // write length
                cre->Write(s, hsgResMgr::ResMgr());     // write data
            }
        }
        break;
    }
    return true;
}

bool plSimpleStateVariable::IReadData(hsStream* s, float timeConvert, int idx, UInt32 readOptions) 
{   
    int j=idx*fVar.GetAtomicCount();
    int i;
    switch(fVar.GetAtomicType())
    {
    case plVarDescriptor::kAgeTimeOfDay:
        // don't need to read in ageTime, since it's computed on the fly when Get is called
        break;
    case plVarDescriptor::kInt:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fI[j+i]=s->ReadSwap32();
        break;
    case plVarDescriptor::kShort:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fS[j+i]=s->ReadSwap16();
        break;
    case plVarDescriptor::kByte:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fBy[j+i]=s->ReadByte();
        break;
    case plVarDescriptor::kFloat:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fF[j+i]=s->ReadSwapScalar();
        break;
    case plVarDescriptor::kTime:
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            fT[j+i].Read(s);
            if (timeConvert != 0.0)
            {
                hsDoublePrecBegin
                double newUt = (fT[j+i].GetSecsDouble() + timeConvert);
                hsDoublePrecEnd
                hsAssert(newUt>=0, "negative unified time");
                fT[j+i].SetSecsDouble(newUt);
            }
        }
        break;
    case plVarDescriptor::kDouble:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fD[j+i]=s->ReadSwapDouble();
        break;
    case plVarDescriptor::kBool:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fB[j+i]=s->Readbool();
        break;
    case plVarDescriptor::kKey:
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            fU[j+i].Invalidate();
            fU[j+i].Read(s);
        }
        break;
    case plVarDescriptor::kString32:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->Read(32, fS32[j+i]);
        break;  
    case plVarDescriptor::kCreatable:
        {
            hsAssert(fVar.GetAtomicCount()==1, "invalid atomic count");
            UInt16 hClass = s->ReadSwap16();    // class index
            if (hClass != 0x8000)
            {               
                UInt32 len = s->ReadSwap32();   // length   
                if (plFactory::CanCreate(hClass))
                {
                    delete fC[j];
                    fC[j] = plFactory::Create(hClass);
                }
                else
                {
                    plSDLCreatableStub* stub = TRACKED_NEW plSDLCreatableStub(hClass, len);
                    fC[j] = stub;
                }
                fC[j]->Read(s, hsgResMgr::ResMgr());    // data         
            }
        }
        break;
    default:
        hsAssert(false, "invalid atomic type");
        return false;
    }
    
    return true;
}
#pragma optimize( "", on )  // restore optimizations to their defaults

bool plSimpleStateVariable::WriteData(hsStream* s, float timeConvert, UInt32 writeOptions) const
{
#ifdef HS_DEBUGGING
    if (!IsUsed())
    {
        // hsAssert(false, "plSimpleStateVariable::WriteData Var doesn't contain data?");
        plNetApp::StaticWarningMsg("plSimpleStateVariable::WriteData Var %s doesn't contain data?",
            GetName());
    }
#endif

    // write base class data
    plStateVariable::WriteData(s, timeConvert, writeOptions);   

    // check if the same as default
    bool sameAsDefaults=false;
    if (!GetVarDescriptor()->IsVariableLength())
    {
        plSimpleStateVariable def;
        def.fVar.CopyFrom(&fVar);   // copy descriptor
        def.Alloc();                // and rest
        def.SetFromDefaults(false /* timeStamp */);     // may do nothing if nor default
        sameAsDefaults = (def == *this);
    }

    bool writeTimeStamps = (writeOptions & plSDL::kWriteTimeStamps)!=0;
    bool writeDirtyFlags = (writeOptions & plSDL::kDontWriteDirtyFlag)==0;
    bool forceDirtyFlags = (writeOptions & plSDL::kMakeDirty)!=0;
    bool wantTimeStamp   = (writeOptions & plSDL::kTimeStampOnRead)!=0;
    bool needTimeStamp   = (writeOptions & plSDL::kTimeStampOnWrite)!=0;
    forceDirtyFlags = forceDirtyFlags || (!sameAsDefaults && (writeOptions & plSDL::kDirtyNonDefaults)!=0);

    // write save flags
    UInt8 saveFlags = 0;
    saveFlags |= writeTimeStamps ? plSDL::kHasTimeStamp : 0;
    saveFlags |= forceDirtyFlags || (writeDirtyFlags && IsDirty()) ? plSDL::kHasDirtyFlag : 0;
    saveFlags |= wantTimeStamp ? plSDL::kWantTimeStamp : 0;
    saveFlags |= needTimeStamp ? plSDL::kHasTimeStamp : 0;

    if (sameAsDefaults)
        saveFlags |= plSDL::kSameAsDefault;
    s->WriteSwap(saveFlags);
    
    if (needTimeStamp) {
        // timestamp on write
        fTimeStamp.ToCurrentTime();
        fTimeStamp.Write(s);
    }
    else if (writeTimeStamps) {
        // write time stamps
        fTimeStamp.Write(s);
    }

    // write var data
    if (!sameAsDefaults)
    {
        // list size
        if (GetVarDescriptor()->IsVariableLength())
            s->WriteSwap32(GetVarDescriptor()->GetCount());     // have to write out as long since we don't know how big the list is

        // list
        int i;
        for(i=0;i<fVar.GetCount();i++)
            if (!IWriteData(s, timeConvert, i, writeOptions))
                return false;
    }

    return true;
}

// assumes var is created from the right type of descriptor (count, type, etc.)
bool plSimpleStateVariable::ReadData(hsStream* s, float timeConvert, UInt32 readOptions)
{
    // read base class data
    plStateVariable::ReadData(s, timeConvert, readOptions);

    plUnifiedTime ut;
    ut.ToEpoch();
    
    UInt8 saveFlags;
    s->ReadSwap(&saveFlags);

    bool isDirty = ( saveFlags & plSDL::kHasDirtyFlag )!=0;
    bool setDirty = ( isDirty && ( readOptions & plSDL::kKeepDirty ) ) || ( readOptions & plSDL::kMakeDirty );
    bool wantTimestamp = isDirty &&
        plSDLMgr::GetInstance()->AllowTimeStamping() &&
        (   ( saveFlags & plSDL::kWantTimeStamp ) ||
            ( readOptions & plSDL::kTimeStampOnRead )   );

    if (saveFlags & plSDL::kHasTimeStamp)
        ut.Read(s);
    else if ( wantTimestamp )
        ut.ToCurrentTime();

    if (!(saveFlags & plSDL::kSameAsDefault))
    {
        setDirty = setDirty || ( readOptions & plSDL::kDirtyNonDefaults )!=0;

        // read list size
        if (GetVarDescriptor()->IsVariableLength())
        {
            UInt32 cnt;
            s->ReadSwap(&cnt);      // have to read as long since we don't know how big the list is

            if (cnt>=0 && cnt<plSDL::kMaxListSize)
                fVar.SetCount(cnt);
            else
                return false;
        
            Alloc();        // alloc after setting count
        }
        else
        {
            hsAssert(fVar.GetCount(), "empty var?");
        }
    }

    // compare timestamps
    if (fTimeStamp > ut)
        return true;
        
    if ( (saveFlags & plSDL::kHasTimeStamp) || (readOptions & plSDL::kTimeStampOnRead) )
        TimeStamp(ut);
    
    // read list
    if (!(saveFlags & plSDL::kSameAsDefault))
    {
        int i;
        for(i=0;i<fVar.GetCount();i++)
            if (!IReadData(s, timeConvert, i, readOptions))
                return false;
    }
    else
    {
        Reset();
        SetFromDefaults(false);
    }

    SetUsed( true );
    SetDirty( setDirty );

    return true;
}

void plSimpleStateVariable::CopyData(const plSimpleStateVariable* other, UInt32 writeOptions/*=0*/)
{
    // use stream as a medium
    hsRAMStream stream;
    other->WriteData(&stream, 0, writeOptions);
    stream.Rewind();
    ReadData(&stream, 0, writeOptions);
}

//
// send notification msg if necessary, called internally
//

#define NOTIFY_CHECK(type, var)     \
case type:  \
    for(i=0;i<cnt;i++)  \
        if (hsABS(var[i] - other->var[i])>d)    \
        {   \
            notify=true;    \
            break;  \
        }   \
    break;  

void plSimpleStateVariable::NotifyStateChange(const plSimpleStateVariable* other, const char* sdlName)
{
    if (fChangeNotifiers.size()==0)
        return;

    bool different=!(*this == *other);
    bool notify=false;
    int numNotifiers=0;
    if (different)
    {
        StateChangeNotifiers::iterator it=fChangeNotifiers.begin();
        for( ; it!=fChangeNotifiers.end(); it++)
        {
            float d=(*it).fDelta;
            if (d==0 && different)      // delta of 0 means notify on any change
            {
                notify=true;
            }
            else
            {
                int i;
                int cnt = fVar.GetAtomicCount()*fVar.GetCount();
                switch(fVar.GetAtomicType())
                {
                    NOTIFY_CHECK(plVarDescriptor::kInt, fI)
                    NOTIFY_CHECK(plVarDescriptor::kShort, fS)
                    NOTIFY_CHECK(plVarDescriptor::kByte, fBy)
                    NOTIFY_CHECK(plVarDescriptor::kFloat, fF)
                    NOTIFY_CHECK(plVarDescriptor::kDouble, fD)
                }           
            }
            if (notify)
            {
                numNotifiers += (*it).fKeys.size();
                (*it).SendNotificationMsg(other /* src */, this /* dst */, sdlName);
            }
        }
    }

    if (plNetObjectDebuggerBase::GetInstance() && plNetObjectDebuggerBase::GetInstance()->GetDebugging())
    {
        plNetObjectDebuggerBase::GetInstance()->LogMsg(
            xtl::format("Var %s did %s send notification difference. Has %d notifiers with %d recipients.", 
                GetName(), !notify ? "NOT" : "", fChangeNotifiers.size(), numNotifiers).c_str());
    }

}
    
//
// Checks to see if data contents are the same on two matching vars.
//

#define EQ_CHECK(type, var)     \
case type:  \
    for(i=0;i<cnt;i++)  \
        if (var[i]!=other.var[i])   \
            return false;   \
    break;  

bool plSimpleStateVariable::operator==(const plSimpleStateVariable &other) const
{
    hsAssert(fVar.GetType() == other.GetVarDescriptor()->GetType(), "type mismatch in equality check");
    hsAssert(fVar.GetAtomicCount() == other.GetVarDescriptor()->GetAsSimpleVarDescriptor()->GetAtomicCount(),
        "atomic cnt mismatch in equality check");
    
    if (GetCount() != other.GetCount())
        return false;

    int i;
    int cnt = fVar.GetAtomicCount()*fVar.GetCount();
    switch(fVar.GetAtomicType())
    {
        EQ_CHECK(plVarDescriptor::kAgeTimeOfDay, fF)
        EQ_CHECK(plVarDescriptor::kInt, fI)
        EQ_CHECK(plVarDescriptor::kFloat, fF)
        EQ_CHECK(plVarDescriptor::kTime, fT)
        EQ_CHECK(plVarDescriptor::kDouble, fD)
        EQ_CHECK(plVarDescriptor::kBool, fB)
        EQ_CHECK(plVarDescriptor::kKey, fU)
        EQ_CHECK(plVarDescriptor::kCreatable, fC)
        EQ_CHECK(plVarDescriptor::kShort, fS)
        EQ_CHECK(plVarDescriptor::kByte, fBy)       
    case plVarDescriptor::kString32:
        for(i=0;i<cnt;i++)
            if (stricmp(fS32[i],other.fS32[i]))
                return false;
        break;  
    default:
        hsAssert(false, "invalid atomic type");
        return false;
        break;
    }
    
    return true;
}

//
// Add and coalate
//
void plSimpleStateVariable::AddStateChangeNotification(plStateChangeNotifier& n)
{
    StateChangeNotifiers::iterator it=fChangeNotifiers.begin();
    for( ; it != fChangeNotifiers.end(); it++)
    {
        if ((*it).fDelta==n.fDelta)
        {
            // merged into an existing entry
            (*it).AddNotificationKeys(n.fKeys);
            return;
        }
    }

    // add new entry
    fChangeNotifiers.push_back(n);
}

//
// remove entries with this key 
//
void plSimpleStateVariable::RemoveStateChangeNotification(plKey notificationObj)
{
    StateChangeNotifiers::iterator it=fChangeNotifiers.end();
    for(; it != fChangeNotifiers.begin();)
    {
        it--;
        int size=(*it).RemoveNotificationKey(notificationObj);
        if (size==0)
            it=fChangeNotifiers.erase(it);  // iterator is moved to item after this one
    }
}

//
// remove entries which match
//
void plSimpleStateVariable::RemoveStateChangeNotification(plStateChangeNotifier n)
{
    StateChangeNotifiers::iterator it=fChangeNotifiers.end();
    for(; it != fChangeNotifiers.begin();)
    {
        it--;
        if ( (*it).fDelta==n.fDelta)
        {
            int size=(*it).RemoveNotificationKeys(n.fKeys);
            if (size==0)
                it=fChangeNotifiers.erase(it);  // iterator is moved to item after this one
        }
    }
}

//
//
//
void plSimpleStateVariable::DumpToObjectDebugger(bool dirtyOnly, int level) const
{
    plNetObjectDebuggerBase* dbg = plNetObjectDebuggerBase::GetInstance();
    if (!dbg)
        return;

    std::string pad;
    int i;
    for(i=0;i<level; i++)
        pad += "   ";

    std::string logMsg = xtl::format( "%sSimpleVar, name:%s[%d]", pad.c_str(), GetName(), GetCount());
    if (GetCount()>1)
    {
        dbg->LogMsg(logMsg.c_str());    // it's going to be a long msg, so print it on its own line
        logMsg = "";
    }
    
    pad += "\t";
    for(i=0;i<GetCount(); i++)
    {
        char* s=GetAsString(i);
        if (fVar.GetAtomicType() == plVarDescriptor::kTime)
        {
            const char* p=fT[i].PrintWMillis();
            logMsg += xtl::format( "%sVar:%d gameTime:%s pst:%s ts:%s", 
                pad.c_str(), i, s ? s : "?", p, fTimeStamp.Format("%c").c_str() );
        }
        else
        {
            logMsg +=xtl::format( "%sVar:%d value:%s ts:%s", 
                pad.c_str(), i, s ? s : "?", fTimeStamp.AtEpoch() ? "0" : fTimeStamp.Format("%c").c_str() );
        }
        delete [] s;

        if ( !dirtyOnly )
            logMsg += xtl::format( " dirty:%d", IsDirty() );

        dbg->LogMsg(logMsg.c_str());
        logMsg = "";
    }
}

void plSimpleStateVariable::DumpToStream(hsStream* stream, bool dirtyOnly, int level) const
{
    std::string pad;
    int i;
    for(i=0;i<level; i++)
        pad += "   ";

    std::string logMsg = xtl::format( "%sSimpleVar, name:%s[%d]", pad.c_str(), GetName(), GetCount());
    if (GetCount()>1)
    {
        stream->WriteString(logMsg.c_str());    // it's going to be a long msg, so print it on its own line
        logMsg = "";
    }
    
    pad += "\t";
    for(i=0;i<GetCount(); i++)
    {
        char* s=GetAsString(i);
        if (fVar.GetAtomicType() == plVarDescriptor::kTime)
        {
            const char* p=fT[i].PrintWMillis();
            logMsg += xtl::format( "%sVar:%d gameTime:%s pst:%s ts:%s", 
                pad.c_str(), i, s ? s : "?", p, fTimeStamp.Format("%c").c_str() );
        }
        else
        {
            logMsg +=xtl::format( "%sVar:%d value:%s ts:%s", 
                pad.c_str(), i, s ? s : "?", fTimeStamp.AtEpoch() ? "0" : fTimeStamp.Format("%c").c_str() );
        }
        delete [] s;

        if ( !dirtyOnly )
            logMsg += xtl::format( " dirty:%d", IsDirty() );

        stream->WriteString(logMsg.c_str());
        logMsg = "";
    }
}

//
// set var to its defalt value
//
void plSimpleStateVariable::SetFromDefaults(bool timeStampNow)
{
    int i;
    for(i=0;i<GetCount();i++)
        SetFromString(GetVarDescriptor()->GetDefault(), i, timeStampNow);
}

///////////////////////////////////////////////////////////////////////////////
// plSDStateVariable
///////////////////////////////////////////////////////////////////////////////

plSDStateVariable::plSDStateVariable(plSDVarDescriptor* sdvd) : fVarDescriptor(nil)
{ 
    Alloc(sdvd);
}

plSDStateVariable::~plSDStateVariable()
{
    IDeInit();
}

//
// resize the array of state data records
//
void plSDStateVariable::Resize(int cnt)
{
    int origCnt=GetCount();
    // when sending, this is always freshly allocated. if you then set it to zero,
    // the change won't be sent, even though the version on the server might not be zero
    // for now, we're just not going to do this optimization
    // we could, however, change it to (origCnt==cnt==0), because for sizes other
    // than zero the bug won't happen
//  if (origCnt==cnt)
//      return;     // no work to do

    // shrinking
    if (cnt<origCnt)
    {
        int i;
        for(i=cnt;i<origCnt;i++)
            delete fDataRecList[i];
    }

    fDataRecList.resize(cnt);

    // growing
    if (cnt>origCnt)
    {
        int i;
        for(i=origCnt;i<cnt;i++)
            fDataRecList[i] = TRACKED_NEW plStateDataRecord(fVarDescriptor->GetStateDescriptor());
    }

    SetDirty(true);
    SetUsed(true);
}

//
// create/allocate data records based on the given SDVarDesc
//
void plSDStateVariable::Alloc(plSDVarDescriptor* sdvd, int listSize)
{
    if (sdvd==fVarDescriptor)
    {
        // trick to not have to delete and recreate fVarDescriptor
        fVarDescriptor=nil;
        IDeInit();  
        fVarDescriptor=sdvd;
    }
    else
        IDeInit();  // will delete fVarDescriptor

    if (sdvd) 
    {
        if (fVarDescriptor==nil)
        {
            fVarDescriptor = TRACKED_NEW plSDVarDescriptor;
            fVarDescriptor->CopyFrom(sdvd);
        }

        int cnt = listSize==-1 ? sdvd->GetCount() : listSize;
        fDataRecList.resize(cnt); 
        int j;
        for (j=0;j<cnt; j++)
            InsertStateDataRecord(TRACKED_NEW plStateDataRecord(sdvd->GetStateDescriptor()), j);
    }
}

//
// help alloc fxn
//
void plSDStateVariable::Alloc(int listSize)
{
    Alloc(fVarDescriptor, listSize);
}

//
// delete all records
//
void plSDStateVariable::IDeInit()
{
    DataRecList::iterator it;
    for (it=fDataRecList.begin(); it != fDataRecList.end(); it++)
        delete *it;
    fDataRecList.clear();
    delete fVarDescriptor;
    fVarDescriptor=nil;
}

//
// Make 'this' into a copy of 'other'.
//
void plSDStateVariable::CopyFrom(plSDStateVariable* other, UInt32 writeOptions/*=0*/)
{
    // IDeInit();
    Alloc(other->GetSDVarDescriptor(), other->GetCount());
    int i;
    for(i=0; i<other->GetCount(); i++)
        fDataRecList[i]->CopyFrom(*other->GetStateDataRecord(i),writeOptions);
}

//
// Find the data items which are dirty in 'other' and 
// copy them to my corresponding item.
// Requires that records have the same descriptor.
//
void plSDStateVariable::UpdateFrom(plSDStateVariable* other, UInt32 writeOptions/*=0*/)
{
    hsAssert(!stricmp(other->GetSDVarDescriptor()->GetName(), fVarDescriptor->GetName()), 
        xtl::format("var descriptor mismatch in UpdateFrom, name %s,%s ver %d,%d",
        GetName(), other->GetName()).c_str());
    Resize(other->GetCount());  // make sure sizes match

    bool dirtyOnly = (writeOptions & plSDL::kDirtyOnly);

    int i;
    for(i=0; i<other->GetCount(); i++)
    {
        if ( (dirtyOnly && other->GetStateDataRecord(i)->IsDirty()) ||
             (!dirtyOnly &&other->GetStateDataRecord(i)->IsUsed()) )
            fDataRecList[i]->UpdateFrom(*other->GetStateDataRecord(i), writeOptions);
    }
}

//
// Convert all my stateDataRecords to the type defined by the 'other var'
//
void plSDStateVariable::ConvertTo(plSDStateVariable* otherSDVar, bool force )
{
    plStateDescriptor* otherSD=otherSDVar->GetSDVarDescriptor()->GetStateDescriptor();

    hsLogEntry( plNetApp::StaticDebugMsg( "SDSV(%p) converting %s from %s to %s (force:%d)",
        this, fVarDescriptor->GetName(), fVarDescriptor->GetTypeString(),
        otherSDVar->GetSDVarDescriptor()->GetTypeString(), force ) );

    int j;
    for(j=0;j<GetCount(); j++)
    {
        GetStateDataRecord(j)->ConvertTo( otherSD, force );
    }
}       

bool plSDStateVariable::IsDirty() const
{
    if (plStateVariable::IsDirty())
        return true;

    int j;
    for(j=0;j<GetCount(); j++)
        if (GetStateDataRecord(j)->IsDirty())
            return true;
    return false;
}

int plSDStateVariable::GetDirtyCount() const
{
    int cnt=0;
    int j;
    for(j=0;j<GetCount(); j++)
        if (GetStateDataRecord(j)->IsDirty())
            cnt++;
    return cnt;
}

bool plSDStateVariable::IsUsed() const
{
    if (plStateVariable::IsUsed())
        return true;
    
    int j;
    for(j=0;j<GetCount(); j++)
        if (GetStateDataRecord(j)->IsUsed())
            return true;
    return false;
}

int plSDStateVariable::GetUsedCount() const
{
    int cnt=0;
    int j;
    for(j=0;j<GetCount(); j++)
        if (GetStateDataRecord(j)->IsUsed())
            cnt++;
    return cnt;
}

void plSDStateVariable::GetUsedDataRecords(ConstDataRecList* recList) const
{
    recList->clear();
    int j;
    for(j=0;j<GetCount(); j++)
        if (GetStateDataRecord(j)->IsUsed())
            recList->push_back(GetStateDataRecord(j));
}

void plSDStateVariable::GetDirtyDataRecords(ConstDataRecList* recList) const
{
    recList->clear();
    int j;
    for(j=0;j<GetCount(); j++)
        if (GetStateDataRecord(j)->IsDirty())
            recList->push_back(GetStateDataRecord(j));
}

//
// read all SDVars
//
bool plSDStateVariable::ReadData(hsStream* s, float timeConvert, UInt32 readOptions)
{
    plStateVariable::ReadData(s, timeConvert, readOptions);

    UInt8 saveFlags;
    s->ReadSwap(&saveFlags);    // unused

    // read total list size
    if (GetVarDescriptor()->IsVariableLength())
    {
        UInt32 total;
        s->ReadSwap(&total);
        Resize(total);
    }
    
    // read dirty list size 
    int cnt;
    plSDL::VariableLengthRead(s, 
        GetVarDescriptor()->IsVariableLength() ? 0xffffffff : GetVarDescriptor()->GetCount(), &cnt);

    // if we are reading the entire list in, then we don't need to read each index
    bool all = (cnt==fDataRecList.size());

    // read list
    int i;
    for(i=0;i<cnt; i++)
    {
        int idx;
        if (!all)
            plSDL::VariableLengthRead(s, 
                GetVarDescriptor()->IsVariableLength() ? 0xffffffff : GetVarDescriptor()->GetCount(), &idx);
        else
            idx=i;
        
        if (idx<fDataRecList.size())
            fDataRecList[idx]->Read(s, timeConvert, readOptions);
        else
            return false;
    }
    return true;
}

//
// write all SDVars
//
bool plSDStateVariable::WriteData(hsStream* s, float timeConvert, UInt32 writeOptions) const
{   
    plStateVariable::WriteData(s, timeConvert, writeOptions);

    UInt8 saveFlags=0;  // unused   
    s->WriteSwap(saveFlags);

    // write total list size
    UInt32 total=GetCount();
    if (GetVarDescriptor()->IsVariableLength())
        s->WriteSwap(total);

    // write dirty list size
    bool dirtyOnly = (writeOptions & plSDL::kDirtyOnly) != 0;
    int writeCnt = dirtyOnly ? GetDirtyCount() : GetUsedCount();
    plSDL::VariableLengthWrite(s, 
        GetVarDescriptor()->IsVariableLength() ? 0xffffffff : GetVarDescriptor()->GetCount(), writeCnt);

    // if we are writing the entire list in, then we don't need to read each index
    bool all = (writeCnt==fDataRecList.size());

    // write list
    int i, written=0;
    for(i=0;i<total;i++)
    {
        if ( (dirtyOnly && fDataRecList[i]->IsDirty()) || 
            (!dirtyOnly && fDataRecList[i]->IsUsed()) )
        {
            if (!all)
                plSDL::VariableLengthWrite(s, 
                    GetVarDescriptor()->IsVariableLength() ? 0xffffffff : GetVarDescriptor()->GetCount(), i);   // idx
            fDataRecList[i]->Write(s, timeConvert, dirtyOnly);  // item
            written++;
        }
    }
    hsAssert(writeCnt==written, "write mismatch");
    return true;
}

//
//
//
void plSDStateVariable::DumpToObjectDebugger(bool dirtyOnly, int level) const
{
    plNetObjectDebuggerBase* dbg = plNetObjectDebuggerBase::GetInstance();
    if (!dbg)
        return;

    std::string pad;
    int i;
    for(i=0;i<level; i++)
        pad += "   ";

    int cnt = dirtyOnly ? GetDirtyCount() : GetUsedCount();
    dbg->LogMsg(xtl::format( "%sSDVar, name:%s dirtyOnly:%d count:%d", 
        pad.c_str(), GetName(), dirtyOnly, cnt).c_str());

    for(i=0;i<GetCount();i++)
    {
        if ( (dirtyOnly && fDataRecList[i]->IsDirty()) || 
            (!dirtyOnly && fDataRecList[i]->IsUsed()) )
        {
            fDataRecList[i]->DumpToObjectDebugger(nil, dirtyOnly, level+1);
        }
    }
}

void plSDStateVariable::DumpToStream(hsStream* stream, bool dirtyOnly, int level) const
{
    std::string pad;
    int i;
    for(i=0;i<level; i++)
        pad += "   ";

    int cnt = dirtyOnly ? GetDirtyCount() : GetUsedCount();
    stream->WriteString(xtl::format( "%sSDVar, name:%s dirtyOnly:%d count:%d", 
        pad.c_str(), GetName(), dirtyOnly, cnt).c_str());

    for(i=0;i<GetCount();i++)
    {
        if ( (dirtyOnly && fDataRecList[i]->IsDirty()) || 
            (!dirtyOnly && fDataRecList[i]->IsUsed()) )
        {
            fDataRecList[i]->DumpToStream(stream, nil, dirtyOnly, level+1);
        }
    }
}

//
// Checks to see if data contents are the same on two matching vars.
//
bool plSDStateVariable::operator==(const plSDStateVariable &other) const
{
    hsAssert(GetSDVarDescriptor()->GetStateDescriptor() == other.GetSDVarDescriptor()->GetStateDescriptor(),
        "SD var descriptor mismatch in equality check");

    if (GetCount() != other.GetCount())
        return false;   // different list sizes

    int i;
    for(i=0;i<GetCount(); i++)
    {
        if (! (*GetStateDataRecord(i) == *other.GetStateDataRecord(i)))
            return false;
    }

    return true;
}

void plSDStateVariable::SetFromDefaults(bool timeStampNow)
{
    int i;
    for(i=0;i<GetCount(); i++)
        GetStateDataRecord(i)->SetFromDefaults(timeStampNow);
}

void plSDStateVariable::TimeStamp( const plUnifiedTime & ut/*=plUnifiedTime::GetCurrentTime()*/ )
{
    hsAssert( false, "not impl" );
}

void plSDStateVariable::FlagNewerState(const plSDStateVariable& other, bool respectAlwaysNew)
{
    int i;
    for(i=0;i<GetCount(); i++)
        GetStateDataRecord(i)->FlagNewerState(*other.GetStateDataRecord(i), respectAlwaysNew);
}

void plSDStateVariable::FlagAlwaysNewState()
{
    int i;
    for(i=0;i<GetCount(); i++)
        GetStateDataRecord(i)->FlagAlwaysNewState();
}