/*==LICENSE==*

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

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

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

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

Additional permissions under GNU GPL version 3 section 7

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

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

*==LICENSE==*/

#include "plSDL.h"

#include "plProduct.h"
#include "hsResMgr.h"
#include "hsStream.h"

#include "pnFactory/plCreatable.h"
#include "pnFactory/plFactory.h"
#include "pnNetCommon/plNetApp.h"
#include "pnNetCommon/pnNetCommon.h"

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

#include <cfloat>
#include <cmath>
#include <type_traits>
#include <vector>

/*****************************************************************************
*
*   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_t fClassIndex;
public:
    void* fData;
    int fDataLen;

    plSDLCreatableStub(uint16_t classIndex, int len) : fClassIndex(classIndex), fData(), fDataLen(len) { }
    ~plSDLCreatableStub() { delete[] (char*)fData; }

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

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

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

void plStateVarNotificationInfo::Read(hsStream* s)
{
    (void)s->ReadByte();  // unused: saveFlags
    ST::string hint=s->ReadSafeString();
    if (!hint.empty())
        fHintString = hint;
}

void plStateVarNotificationInfo::Write(hsStream* s) const
{
    (void)s->WriteByte(uint8_t(0));   // unused: saveFlags
    s->WriteSafeString(fHintString);
}

/////////////////////////////////////////////////////
// plStateVariable
/////////////////////////////////////////////////////
// Options: kSkipNotificationInfo, kTimeStampOnRead, kKeepDirty, kMakeDirty, kDirtyNonDefaults, [plSDStateVariable only: kForceConvert]
bool plStateVariable::ReadData(hsStream* s, float timeConvert, uint32_t readOptions)
{
    uint8_t saveFlags = s->ReadByte();
    if (saveFlags & plSDL::kHasNotificationInfo)
    {
        if (readOptions & plSDL::kSkipNotificationInfo) {
            plStateVarNotificationInfo().Read(s);
        } else {
            GetNotificationInfo().Read(s);
        }
    }
    return true;
}

// Options: kSkipNotificationInfo, [plSDStateVariable only: kDirtyOnly], [plSimpleStateVariable only: kWriteTimeStamps, kTimeStampOnRead, kTimeStampOnWrite, kDontWriteDirtyFlag, kMakeDirty, kDirtyNonDefaults]
bool plStateVariable::WriteData(hsStream* s, float timeConvert, uint32_t writeOptions) const
{
    bool writeNotificationInfo = ((writeOptions & plSDL::kSkipNotificationInfo)==0);

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

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

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

void plSimpleStateVariable::IInit() 
{
    SetDirty(false);
    SetUsed(false);
    fBy = nullptr;
    fS = nullptr;
    fI = nullptr;
    fF = nullptr;
    fD = nullptr;
    fB = nullptr;
    fU = nullptr;
    fS32 = nullptr;
    fC = nullptr;
    fT = nullptr;
    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, ST::format("undefined atomic type:{} var:{} cnt:{}",
                                   type, GetName(), GetCount()).c_str());
        break;
    };

}

//
// alloc memory
//

#define SDLALLOC(typeName, type, var)   \
    case typeName:  \
        var = 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, uint8_t, 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 = new plClientUnifiedTime[cnt];
            break;
        case plVarDescriptor::kKey:
            fU = new plUoid[cnt];
            break;
        case plVarDescriptor::kString32:
            fS32 = new plVarDescriptor::String32[cnt];
            break;
        default:
            hsAssert(false, "undefined atomic type");
            break;
        };
    }

    Reset();
}

#define RESET(typeName, type, var)  \
    case typeName:  \
        for (int i = 0; i < cnt; i++)  \
            var[i] = 0;   \
        break;
void plSimpleStateVariable::Reset()
{
    int cnt = fVar.GetAtomicCount()*fVar.GetCount();
    if (cnt)
    {
        switch (fVar.GetAtomicType())
        {
        RESET(plVarDescriptor::kInt, int, fI)
        RESET(plVarDescriptor::kAgeTimeOfDay, float, fF)
        RESET(plVarDescriptor::kByte, uint8_t, fBy)
        RESET(plVarDescriptor::kShort, short, fS)
        RESET(plVarDescriptor::kFloat, float, fF)
        RESET(plVarDescriptor::kDouble, double, fD)
        RESET(plVarDescriptor::kBool, bool, fB)
        case plVarDescriptor::kCreatable:
            for (int i = 0; i < cnt; i++)
                fC[i] = nullptr;
            break;
        case plVarDescriptor::kTime:
        case plVarDescriptor::kKey:
            break;
        case plVarDescriptor::kString32:
            for (int 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::GetCurrent()*/ )
{
    fTimeStamp = ut;
}

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

    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
            std::vector<ST::string> bits = value.tokenize("( ,)");
            int i=idx*fVar.GetAtomicCount();
            for (std::vector<ST::string>::iterator ptr = bits.begin(); ptr != bits.end(); ++ptr)
            {
                if ((type==plVarDescriptor::kInt) && fI)
                    fI[i++] = ptr->to_int();
                else if (type==plVarDescriptor::kShort && fS)
                    fS[i++] = static_cast<short>(ptr->to_int());
                else if (type==plVarDescriptor::kByte && fBy)
                    fBy[i++] = static_cast<uint8_t>(ptr->to_int());
                else if ( (type==plVarDescriptor::kFloat || type==plVarDescriptor::kAgeTimeOfDay) && fF)
                    fF[i++] = static_cast<float>(ptr->to_float());
                else if ( (type==plVarDescriptor::kDouble || type==plVarDescriptor::kTime) && fD)
                    fD[i++] = ptr->to_double();
            }
        }
        break;
    case plVarDescriptor::kBool:
        {   
            // handles value in the form "(i,j,k)" for things like vectors
            std::vector<ST::string> bits = value.tokenize("( ,)");
            int i=idx*fVar.GetAtomicCount();
            for (std::vector<ST::string>::iterator ptr = bits.begin(); ptr != bits.end(); ++ptr)
                fB[i++] = ptr->to_bool();
        }
        break;
    case plVarDescriptor::kString32:
        {   
            // handles value in the form "(i,j,k)" for things like vectors
            std::vector<ST::string> bits = value.tokenize("( ,)");
            int i=idx*fVar.GetAtomicCount();
            for (std::vector<ST::string>::iterator ptr = bits.begin(); ptr != bits.end(); ++ptr)
            {
                hsStrncpy(fS32[i++], ptr->c_str(), 32);
            }
        }
        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.
//
ST::string plSimpleStateVariable::GetAsString(int idx) const
{
    int j;
    ST::string_stream str;
    if (fVar.GetAtomicCount()>1)
        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 << fI[i++];
                else if (type==plVarDescriptor::kShort)
                    str << fS[i++];
                else if (type==plVarDescriptor::kByte)
                    str << static_cast<unsigned>(fBy[i++]);
                else if (type==plVarDescriptor::kFloat  || type==plVarDescriptor::kAgeTimeOfDay)
                    str << ST::format("{.3f}", fF[i++]);
                else if (type==plVarDescriptor::kDouble)
                    str << ST::format("{.3f}", fD[i++]);
                else if (type==plVarDescriptor::kTime)
                {
                    double tmp;
                    Get(&tmp, i++);
                    str << ST::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 << (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 << 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
            for(j=0;j<fVar.GetAtomicCount();j++)
            {
                str << "other";

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

    return str.to_string();
}

//
// return false on err
//
bool plSimpleStateVariable::IConvertFromRGB(plVarDescriptor::Type newType)
{
    switch(newType)
    {
    case plVarDescriptor::kRGBA:
        {
            // rgb to rgba
            int i,j;
            float* newF = 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;
            uint8_t * newB = new uint8_t [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] = uint8_t(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;
            uint8_t * newB = new uint8_t [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] = uint8_t(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 = 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 = 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;
            uint8_t * newB = new uint8_t [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 = 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;
            uint8_t* newB = new uint8_t[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] = uint8_t(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;
            uint8_t* newBy = new uint8_t [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] = uint8_t(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 = 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;
            uint8_t* newB = new uint8_t[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 = 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 = 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 = 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
            uint8_t* newBy = new uint8_t[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = uint8_t(fI[j]);
            delete [] fI;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            // int to double
            double * newD = 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 = 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 = 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 = new int[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newI[j] = short(fS[j]);
            delete [] fS;
            fI = newI;
        }
        break;
    case plVarDescriptor::kByte:
        {
            uint8_t* newBy = new uint8_t[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = uint8_t(fS[j]);
            delete [] fS;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            double * newD = 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 = 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 = 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 = new int[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newI[j] = short(fBy[j]);
            delete [] fBy;
            fI = newI;
        }
        break;
    case plVarDescriptor::kShort:
        {
            short* newS = 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 = 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 = 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 = 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 = 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:
        {
            uint8_t* newBy = new uint8_t[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = (uint8_t)(fF[j]+.5f);   // round to nearest int
            delete [] fF;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kDouble:
        {
            double* newD = 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 = 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 = 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 = 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:
        {
            uint8_t* newBy = new uint8_t[fVar.GetCount()];
            for(j=0;j<fVar.GetCount(); j++)
                newBy[j] = (uint8_t)(fD[j]+.5f);   // round to nearest int
            delete [] fD;
            fBy = newBy;
        }
        break;
    case plVarDescriptor::kFloat:
        {
            float* newF = 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 = 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 = 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 = 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:
        {
            uint8_t* newBy = new uint8_t[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 = 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= 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()) {
        #ifndef PLASMA_EXTERNAL_RELEASE
            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(0x{x}) converting {} from {} to {}",
        uintptr_t(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);
        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(uint8_t* 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)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            fT[cnt+i].SetFromGameTime(v[i]);
        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((uint8_t)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((uint8_t)v, idx);

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

bool plSimpleStateVariable::Set(uint8_t 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(strlen(v)<32, "string length overflow");
        strncpy(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): nullptr;
        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(uint8_t* 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)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            value[i] = static_cast<float>(fT[cnt+i].ConvertToGameTime());
        }
        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)
    {
        int i;
        int cnt=fVar.GetAtomicCount()*idx;
        for(i=0;i<fVar.GetAtomicCount();i++)
            value[i] = fT[cnt+i].ConvertToGameTime();

        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 (newUoid.GetObjectName().compare(fU[idx].GetObjectName(), ST::case_insensitive) != 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 = nullptr;
                }
            }
        } else {
            *value = nullptr;
        }
        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 = nullptr;
        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;
}

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

ST::string 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)";
}

bool plSimpleStateVariable::IWriteData(hsStream* s, float timeConvert, int idx) const
{
#ifdef HS_DEBUGGING
    if (!IsUsed())
    {
        // hsAssert(false, "plSimpleStateVariable::WriteData doesn't contain data?");
        plNetApp::StaticWarningMsg("plSimpleStateVariable::WriteData Var {} 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->WriteLE32(fI[j+i]);
        break;
    case plVarDescriptor::kShort:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteLE16(fS[j+i]);
        break;
    case plVarDescriptor::kByte:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteByte(fBy[j+i]);
        break;
    case plVarDescriptor::kFloat:
        s->WriteLEFloat(fVar.GetAtomicCount(), &fF[j]);
        break;
    case plVarDescriptor::kTime:
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            if (timeConvert != 0.0)
            {
                double utDouble=fT[j+i].GetSecsDouble();
                utDouble += timeConvert;
                plUnifiedTime ut(utDouble);
                ut.Write(s);
            }
            else
                fT[j+i].Write(s);
        }
        break;
    case plVarDescriptor::kDouble:
        for(i=0;i<fVar.GetAtomicCount();i++)
            s->WriteLEDouble(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->WriteLE16(cre ? cre->ClassIndex() : uint16_t(0x8000));   // creatable class index
            if (cre)
            {
                hsRAMStream ramStream;
                cre->Write(&ramStream, hsgResMgr::ResMgr());
                s->WriteLE32(ramStream.GetEOF());     // write length
                cre->Write(s, hsgResMgr::ResMgr());     // write data
            }
        }
        break;
    default:
        break;
    }
    return true;
}

bool plSimpleStateVariable::IReadData(hsStream* s, float timeConvert, int idx) 
{   
    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->ReadLE32();
        break;
    case plVarDescriptor::kShort:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fS[j+i]=s->ReadLE16();
        break;
    case plVarDescriptor::kByte:
        for(i=0;i<fVar.GetAtomicCount();i++)
            fBy[j+i]=s->ReadByte();
        break;
    case plVarDescriptor::kFloat:
        s->ReadLEFloat(fVar.GetAtomicCount(), &fF[j]);
        break;
    case plVarDescriptor::kTime:
        for(i=0;i<fVar.GetAtomicCount();i++)
        {
            fT[j+i].Read(s);
            if (timeConvert != 0.0)
            {
                double newUt = (fT[j+i].GetSecsDouble() + timeConvert);
                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->ReadLEDouble();
        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_t hClass = s->ReadLE16();    // class index
            if (hClass != 0x8000)
            {               
                uint32_t len = s->ReadLE32();   // length   
                if (plFactory::CanCreate(hClass))
                {
                    delete fC[j];
                    fC[j] = plFactory::Create(hClass);
                }
                else
                {
                    plSDLCreatableStub* stub = 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;
}

// Options: kSkipNotificationInfo, kWriteTimeStamps, kTimeStampOnRead, kTimeStampOnWrite, kDontWriteDirtyFlag, kMakeDirty, kDirtyNonDefaults
bool plSimpleStateVariable::WriteData(hsStream* s, float timeConvert, uint32_t writeOptions) const
{
#ifdef HS_DEBUGGING
    if (!IsUsed())
    {
        // hsAssert(false, "plSimpleStateVariable::WriteData Var doesn't contain data?");
        plNetApp::StaticWarningMsg("plSimpleStateVariable::WriteData Var {} 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_t 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->WriteByte(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->WriteLE32(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))
                return false;
    }

    return true;
}

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

    plUnifiedTime ut;
    ut.ToEpoch();
    
    uint8_t saveFlags = s->ReadByte();

    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_t cnt = s->ReadLE32(); // have to read as long since we don't know how big the list is

            if (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))
                return false;
    }
    else
    {
        Reset();
        SetFromDefaults(false);
    }

    SetUsed( true );
    SetDirty( setDirty );

    return true;
}

// Options: all except for kDirtyOnly, kBroadcast, kForceConvert
void plSimpleStateVariable::CopyData(const plSimpleStateVariable* other, uint32_t 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
//

template <typename _T>
typename std::enable_if<std::is_floating_point<_T>::value, _T>::type
_generic_abs(_T value)
{
    return fabs(value);
}

template <typename _T>
typename std::enable_if<std::is_integral<_T>::value, _T>::type
_generic_abs(_T value)
{
    return abs(value);
}

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

void plSimpleStateVariable::NotifyStateChange(const plSimpleStateVariable* other, const ST::string& 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)
                    default: break;
                }           
            }
            if (notify)
            {
                numNotifiers += (*it).fKeys.size();
                (*it).SendNotificationMsg(other /* src */, this /* dst */, sdlName);
            }
        }
    }

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

}
    
//
// 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.emplace_back(std::move(n));
}

//
// remove entries with this key 
//
void plSimpleStateVariable::RemoveStateChangeNotification(const 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(const 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;

    ST::string pad = ST::string::fill(level * 3, ' ');

    ST::string_stream logMsg;
    logMsg << pad << "SimpleVar, name:" << GetName() << '[' << GetCount() << ']';

    if (GetCount()>1)
    {
        dbg->LogMsg(logMsg.to_string());    // it's going to be a long msg, so print it on its own line
        logMsg.truncate();
    }

    pad += "\t";
    for (int i=0; i<GetCount(); i++)
    {
        logMsg << pad;
        logMsg << "Var:" << i;

        ST::string s=GetAsString(i);
        if (fVar.GetAtomicType() == plVarDescriptor::kTime)
        {
            logMsg << " gameTime:" << s;
            logMsg << " pst:" << fT[i].PrintWMillis();
            logMsg << " ts:" << fTimeStamp.Format("%c");
        }
        else
        {
            logMsg << " value:" << s;
            logMsg << " ts:" << (fTimeStamp.AtEpoch() ? ST_LITERAL("0") : fTimeStamp.Format("%c"));
        }

        if (!dirtyOnly)
        {
            logMsg << " dirty:";
            logMsg << (IsDirty() ? 0 : 1);
        }

        dbg->LogMsg(logMsg.to_string());
        logMsg.truncate();
    }
}

void plSimpleStateVariable::DumpToStream(hsStream* stream, bool dirtyOnly, int level) const
{
    ST::string pad = ST::string::fill(level * 3, ' ');

    ST::string_stream logMsg;
    logMsg << pad << "SimpleVar, name:" << GetName() << '[' << GetCount() << ']';

    if (GetCount()>1)
    {
        stream->WriteString(logMsg.to_string());    // it's going to be a long msg, so print it on its own line
        logMsg.truncate();
    }

    pad += "\t";
    for (int i=0; i<GetCount(); i++)
    {
        logMsg << pad;
        logMsg << "Var:" << i;

        ST::string s = GetAsString(i);
        if (fVar.GetAtomicType() == plVarDescriptor::kTime)
        {
            logMsg << " gameTime:" << s;
            logMsg << " pst:" << fT[i].PrintWMillis();
            logMsg << " ts:" << fTimeStamp.Format("%c");
        }
        else
        {
            logMsg << " value:" << s;
            logMsg << " ts:" << (fTimeStamp.AtEpoch() ? ST_LITERAL("0") : fTimeStamp.Format("%c"));
        }

        if (!dirtyOnly)
        {
            logMsg << " dirty:";
            logMsg << (IsDirty() ? 0 : 1);
        }

        stream->WriteString(logMsg.to_string());
        logMsg.truncate();
    }
}

//
// 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()
{ 
    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] = 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 = nullptr;
        IDeInit();  
        fVarDescriptor=sdvd;
    }
    else
        IDeInit();  // will delete fVarDescriptor

    if (sdvd) 
    {
        if (fVarDescriptor == nullptr)
        {
            fVarDescriptor = new plSDVarDescriptor;
            fVarDescriptor->CopyFrom(sdvd);
        }

        int cnt = listSize==-1 ? sdvd->GetCount() : listSize;
        fDataRecList.resize(cnt); 
        int j;
        for (j=0;j<cnt; j++)
            InsertStateDataRecord(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 = nullptr;
}

//
// Make 'this' into a copy of 'other'.
//
// Options: all except for kDirtyOnly, kBroadcast, kForceConvert
void plSDStateVariable::CopyFrom(plSDStateVariable* other, uint32_t 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.
//
// Options: all except for kBroadcast, kForceConvert
void plSDStateVariable::UpdateFrom(plSDStateVariable* other, uint32_t writeOptions/*=0*/)
{
    hsAssert(!other->GetSDVarDescriptor()->GetName().compare_i(fVarDescriptor->GetName()),
        ST::format("var descriptor mismatch in UpdateFrom, name {},{}",
                   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(0x{x}) converting {} from {} to {} (force:{})",
        uintptr_t(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
//
// Options: kSkipNotificationInfo, kTimeStampOnRead, kKeepDirty, kMakeDirty, kDirtyNonDefaults, kForceConvert
bool plSDStateVariable::ReadData(hsStream* s, float timeConvert, uint32_t readOptions)
{
    plStateVariable::ReadData(s, timeConvert, readOptions);

    (void)s->ReadByte();    // unused: saveFlags

    // read total list size
    if (GetVarDescriptor()->IsVariableLength())
    {
        uint32_t total = s->ReadLE32();
        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
//
// Options: kSkipNotificationInfo, kDirtyOnly
bool plSDStateVariable::WriteData(hsStream* s, float timeConvert, uint32_t writeOptions) const
{   
    plStateVariable::WriteData(s, timeConvert, writeOptions);

    s->WriteByte(uint8_t(0));  // unused: saveFlags

    // write total list size
    uint32_t total=GetCount();
    if (GetVarDescriptor()->IsVariableLength())
        s->WriteLE32(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 ? plSDL::kDirtyOnly : 0); // item
            written++;
        }
    }
    hsAssert(writeCnt==written, "write mismatch");
    return true;
}

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

    ST::string pad = ST::string::fill(level * 3, ' ');

    int cnt = dirtyOnly ? GetDirtyCount() : GetUsedCount();
    dbg->LogMsg(ST::format("{}SDVar, name:{} dirtyOnly:{} count:{}",
                           pad, GetName(), dirtyOnly, cnt));

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

void plSDStateVariable::DumpToStream(hsStream* stream, bool dirtyOnly, int level) const
{
    ST::string pad = ST::string::fill(level * 3, ' ');

    int cnt = dirtyOnly ? GetDirtyCount() : GetUsedCount();
    stream->WriteString(ST::format("{}SDVar, name:{} dirtyOnly:{} count:{}",
                                   pad, GetName(), dirtyOnly, cnt));

    for(int i=0;i<GetCount();i++)
    {
        if ( (dirtyOnly && fDataRecList[i]->IsDirty()) || 
            (!dirtyOnly && fDataRecList[i]->IsUsed()) )
        {
            fDataRecList[i]->DumpToStream(stream, nullptr, 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::GetCurrent()*/ )
{
    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();
}
