//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "basetypes.h"
#include "tier1/convar.h"
#include "tier1/strtools.h"
#include "tier1/characterset.h"
#include "tier1/utlbuffer.h"
#include "tier1/tier1.h"
#include "tier1/convar_serverbounded.h"
#include "icvar.h"
#include "tier0/dbg.h"
#include "Color.h"
#if defined(_X360)
#include "xbox/xbox_console.h"
#endif
#include "tier0/memdbgon.h"

#ifndef NDEBUG
// Comment this out when we release.
#define ALLOW_DEVELOPMENT_CVARS
#endif

#include <core/logging.hpp>
#include <core/interfaces.hpp>

//-----------------------------------------------------------------------------
// Statically constructed list of ConCommandBases,
// used for registering them with the ICVar interface
//-----------------------------------------------------------------------------
ConCommandBase *ConCommandBase::s_pConCommandBases   = NULL;
IConCommandBaseAccessor *ConCommandBase::s_pAccessor = NULL;
static int s_nCVarFlag                               = 0;
static int s_nDLLIdentifier                          = -1; // A unique identifier indicating which DLL this convar came from
static bool s_bRegistered                            = false;

void SetCVarInterface(ICvar *iface)
{
    g_pCVar = iface;
    logging::Info("Set interface to 0x%08x", iface);
}

class CDefaultAccessor : public IConCommandBaseAccessor
{
public:
    virtual bool RegisterConCommandBase(ConCommandBase *pVar)
    {
        // Link to engine's list instead
        g_pCVar->RegisterConCommand(pVar);
        return true;
    }
};

static CDefaultAccessor s_DefaultAccessor;

//-----------------------------------------------------------------------------
// Called by the framework to register ConCommandBases with the ICVar
//-----------------------------------------------------------------------------
void ConVar_Register(int nCVarFlag, IConCommandBaseAccessor *pAccessor)
{
    if (!g_pCVar || s_bRegistered)
        return;

    Assert(s_nDLLIdentifier < 0);
    s_bRegistered    = true;
    s_nCVarFlag      = nCVarFlag;
    s_nDLLIdentifier = g_pCVar->AllocateDLLIdentifier();

    ConCommandBase *pCur, *pNext;

    ConCommandBase::s_pAccessor = pAccessor ? pAccessor : &s_DefaultAccessor;
    pCur                        = ConCommandBase::s_pConCommandBases;
    while (pCur)
    {
        pNext = pCur->m_pNext;
        pCur->AddFlags(s_nCVarFlag);
        pCur->Init();
        pCur = pNext;
    }

    g_pCVar->ProcessQueuedMaterialThreadConVarSets();
    ConCommandBase::s_pConCommandBases = NULL;
}

void ConVar_Unregister()
{
    if (!g_pCVar || !s_bRegistered)
        return;

    Assert(s_nDLLIdentifier >= 0);
    g_pCVar->UnregisterConCommands(s_nDLLIdentifier);
    s_nDLLIdentifier = -1;
    s_bRegistered    = false;
}

//-----------------------------------------------------------------------------
// Purpose: Default constructor
//-----------------------------------------------------------------------------
ConCommandBase::ConCommandBase(void)
{
    m_bRegistered   = false;
    m_pszName       = NULL;
    m_pszHelpString = NULL;

    m_nFlags = 0;
    m_pNext  = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: The base console invoked command/cvar interface
// Input  : *pName - name of variable/command
//			*pHelpString - help text
//			flags - flags
//-----------------------------------------------------------------------------
ConCommandBase::ConCommandBase(const char *pName, const char *pHelpString /*=0*/, int flags /*= 0*/)
{
    CreateBase(pName, pHelpString, flags);
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ConCommandBase::~ConCommandBase(void)
{
}

//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool ConCommandBase::IsCommand(void) const
{
    //	Assert( 0 ); This can't assert. . causes a recursive assert in
    // Sys_Printf, etc.
    return true;
}

//-----------------------------------------------------------------------------
// Returns the DLL identifier
//-----------------------------------------------------------------------------
CVarDLLIdentifier_t ConCommandBase::GetDLLIdentifier() const
{
    return s_nDLLIdentifier;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : *pName -
//			callback -
//			*pHelpString -
//			flags -
//-----------------------------------------------------------------------------
void ConCommandBase::CreateBase(const char *pName, const char *pHelpString /*= 0*/, int flags /*= 0*/)
{
    m_bRegistered = false;

    // Name should be static data
    Assert(pName);
    m_pszName       = pName;
    m_pszHelpString = pHelpString ? pHelpString : "";

    m_nFlags = flags;

#ifdef ALLOW_DEVELOPMENT_CVARS
    m_nFlags &= ~FCVAR_DEVELOPMENTONLY;
#endif

    if (!(m_nFlags & FCVAR_UNREGISTERED))
    {
        m_pNext            = s_pConCommandBases;
        s_pConCommandBases = this;
    }
    else
    {
        // It's unregistered
        m_pNext = NULL;
    }

    // If s_pAccessor is already set (this ConVar is not a global variable),
    //  register it.
    if (s_pAccessor)
    {
        Init();
    }
}

//-----------------------------------------------------------------------------
// Purpose: Used internally by OneTimeInit to initialize.
//-----------------------------------------------------------------------------
void ConCommandBase::Init()
{
    if (s_pAccessor)
    {
        s_pAccessor->RegisterConCommandBase(this);
    }
}

void ConCommandBase::Shutdown()
{
    if (g_pCVar)
    {
        g_pCVar->UnregisterConCommand(this);
    }
}

//-----------------------------------------------------------------------------
// Purpose: Return name of the command/var
// Output : const char
//-----------------------------------------------------------------------------
const char *ConCommandBase::GetName(void) const
{
    return m_pszName;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : flag -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool ConCommandBase::IsFlagSet(int flag) const
{
    return (flag & m_nFlags) ? true : false;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : flags -
//-----------------------------------------------------------------------------
void ConCommandBase::AddFlags(int flags)
{
    m_nFlags |= flags;

#ifdef ALLOW_DEVELOPMENT_CVARS
    m_nFlags &= ~FCVAR_DEVELOPMENTONLY;
#endif
}

//-----------------------------------------------------------------------------
// Purpose:
// Output : const ConCommandBase
//-----------------------------------------------------------------------------
const ConCommandBase *ConCommandBase::GetNext(void) const
{
    return m_pNext;
}

ConCommandBase *ConCommandBase::GetNext(void)
{
    return m_pNext;
}

//-----------------------------------------------------------------------------
// Purpose: Copies string using local new/delete operators
// Input  : *from -
// Output : char
//-----------------------------------------------------------------------------
char *ConCommandBase::CopyString(const char *from)
{
    int len;
    char *to;

    len = V_strlen(from);
    if (len <= 0)
    {
        to    = new char[1];
        to[0] = 0;
    }
    else
    {
        to = new char[len + 1];
        Q_strncpy(to, from, len + 1);
    }
    return to;
}

//-----------------------------------------------------------------------------
// Purpose:
// Output : const char
//-----------------------------------------------------------------------------
const char *ConCommandBase::GetHelpText(void) const
{
    return m_pszHelpString;
}

//-----------------------------------------------------------------------------
// Purpose: Has this cvar been registered
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool ConCommandBase::IsRegistered(void) const
{
    return m_bRegistered;
}

//-----------------------------------------------------------------------------
//
// Con Commands start here
//
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Global methods
//-----------------------------------------------------------------------------
static characterset_t s_BreakSet;
static bool s_bBuiltBreakSet = false;

//-----------------------------------------------------------------------------
// Tokenizer class
//-----------------------------------------------------------------------------
CCommand::CCommand()
{
    if (!s_bBuiltBreakSet)
    {
        s_bBuiltBreakSet = true;
        CharacterSetBuild(&s_BreakSet, "{}()':");
    }

    Reset();
}

CCommand::CCommand(int nArgC, const char **ppArgV)
{
    Assert(nArgC > 0);

    if (!s_bBuiltBreakSet)
    {
        s_bBuiltBreakSet = true;
        CharacterSetBuild(&s_BreakSet, "{}()':");
    }

    Reset();

    char *pBuf  = m_pArgvBuffer;
    char *pSBuf = m_pArgSBuffer;
    m_nArgc     = nArgC;
    for (int i = 0; i < nArgC; ++i)
    {
        m_ppArgv[i] = pBuf;
        int nLen    = Q_strlen(ppArgV[i]);
        memcpy(pBuf, ppArgV[i], nLen + 1);
        if (i == 0)
        {
            m_nArgv0Size = nLen;
        }
        pBuf += nLen + 1;

        bool bContainsSpace = strchr(ppArgV[i], ' ') != NULL;
        if (bContainsSpace)
        {
            *pSBuf++ = '\"';
        }
        memcpy(pSBuf, ppArgV[i], nLen);
        pSBuf += nLen;
        if (bContainsSpace)
        {
            *pSBuf++ = '\"';
        }

        if (i != nArgC - 1)
        {
            *pSBuf++ = ' ';
        }
    }
}

void CCommand::Reset()
{
    m_nArgc          = 0;
    m_nArgv0Size     = 0;
    m_pArgSBuffer[0] = 0;
}

characterset_t *CCommand::DefaultBreakSet()
{
    return &s_BreakSet;
}

bool CCommand::Tokenize(const char *pCommand, characterset_t *pBreakSet)
{
    Reset();
    if (!pCommand)
        return false;

    // Use default break set
    if (!pBreakSet)
    {
        pBreakSet = &s_BreakSet;
    }

    // Copy the current command into a temp buffer
    // NOTE: This is here to avoid the pointers returned by DequeueNextCommand
    // to become invalid by calling AddText. Is there a way we can avoid the
    // memcpy?
    int nLen = Q_strlen(pCommand);
    if (nLen >= COMMAND_MAX_LENGTH - 1)
    {
        Warning("CCommand::Tokenize: Encountered command which overflows the "
                "tokenizer buffer.. Skipping!\n");
        return false;
    }

    memcpy(m_pArgSBuffer, pCommand, nLen + 1);

    // Parse the current command into the current command buffer
    CUtlBuffer bufParse(m_pArgSBuffer, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY);
    int nArgvBufferSize = 0;
    while (bufParse.IsValid() && (m_nArgc < COMMAND_MAX_ARGC))
    {
        char *pArgvBuf = &m_pArgvBuffer[nArgvBufferSize];
        int nMaxLen    = COMMAND_MAX_LENGTH - nArgvBufferSize;
        int nStartGet  = bufParse.TellGet();
        int nSize      = bufParse.ParseToken(pBreakSet, pArgvBuf, nMaxLen);
        if (nSize < 0)
            break;

        // Check for overflow condition
        if (nMaxLen == nSize)
        {
            Reset();
            return false;
        }

        if (m_nArgc == 1)
        {
            // Deal with the case where the arguments were quoted
            m_nArgv0Size        = bufParse.TellGet();
            bool bFoundEndQuote = m_pArgSBuffer[m_nArgv0Size - 1] == '\"';
            if (bFoundEndQuote)
            {
                --m_nArgv0Size;
            }
            m_nArgv0Size -= nSize;
            Assert(m_nArgv0Size != 0);

            // The StartGet check is to handle this case: "foo"bar
            // which will parse into 2 different args. ArgS should point to bar.
            bool bFoundStartQuote = (m_nArgv0Size > nStartGet) && (m_pArgSBuffer[m_nArgv0Size - 1] == '\"');
            Assert(bFoundEndQuote == bFoundStartQuote);
            if (bFoundStartQuote)
            {
                --m_nArgv0Size;
            }
        }

        m_ppArgv[m_nArgc++] = pArgvBuf;
        if (m_nArgc >= COMMAND_MAX_ARGC)
        {
            Warning("CCommand::Tokenize: Encountered command which overflows "
                    "the argument buffer.. Clamped!\n");
        }

        nArgvBufferSize += nSize + 1;
        Assert(nArgvBufferSize <= COMMAND_MAX_LENGTH);
    }

    return true;
}

//-----------------------------------------------------------------------------
// Helper function to parse arguments to commands.
//-----------------------------------------------------------------------------
const char *CCommand::FindArg(const char *pName) const
{
    int nArgC = ArgC();
    for (int i = 1; i < nArgC; i++)
    {
        if (!Q_stricmp(Arg(i), pName))
            return (i + 1) < nArgC ? Arg(i + 1) : "";
    }
    return 0;
}

int CCommand::FindArgInt(const char *pName, int nDefaultVal) const
{
    const char *pVal = FindArg(pName);
    if (pVal)
        return atoi(pVal);
    else
        return nDefaultVal;
}

//-----------------------------------------------------------------------------
// Default console command autocompletion function
//-----------------------------------------------------------------------------
int DefaultCompletionFunc(const char *partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH])
{
    return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Constructs a console command
//-----------------------------------------------------------------------------
// ConCommand::ConCommand()
//{
//	m_bIsNewConCommand = true;
//}

ConCommand::ConCommand(const char *pName, FnCommandCallbackVoid_t callback, const char *pHelpString /*= 0*/, int flags /*= 0*/, FnCommandCompletionCallback completionFunc /*= 0*/)
{
    // Set the callback
    m_fnCommandCallbackV1            = callback;
    m_bUsingNewCommandCallback       = false;
    m_bUsingCommandCallbackInterface = false;
    m_fnCompletionCallback           = completionFunc ? completionFunc : DefaultCompletionFunc;
    m_bHasCompletionCallback         = completionFunc != 0 ? true : false;

    // Setup the rest
    BaseClass::CreateBase(pName, pHelpString, flags);
}

ConCommand::ConCommand(const char *pName, FnCommandCallback_t callback, const char *pHelpString /*= 0*/, int flags /*= 0*/, FnCommandCompletionCallback completionFunc /*= 0*/)
{
    // Set the callback
    m_fnCommandCallback              = callback;
    m_bUsingNewCommandCallback       = true;
    m_fnCompletionCallback           = completionFunc ? completionFunc : DefaultCompletionFunc;
    m_bHasCompletionCallback         = completionFunc != 0 ? true : false;
    m_bUsingCommandCallbackInterface = false;

    // Setup the rest
    BaseClass::CreateBase(pName, pHelpString, flags);
}

ConCommand::ConCommand(const char *pName, ICommandCallback *pCallback, const char *pHelpString /*= 0*/, int flags /*= 0*/, ICommandCompletionCallback *pCompletionCallback /*= 0*/)
{
    // Set the callback
    m_pCommandCallback               = pCallback;
    m_bUsingNewCommandCallback       = false;
    m_pCommandCompletionCallback     = pCompletionCallback;
    m_bHasCompletionCallback         = (pCompletionCallback != 0);
    m_bUsingCommandCallbackInterface = true;

    // Setup the rest
    BaseClass::CreateBase(pName, pHelpString, flags);
}

//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
ConCommand::~ConCommand(void)
{
}

//-----------------------------------------------------------------------------
// Purpose: Returns true if this is a command
//-----------------------------------------------------------------------------
bool ConCommand::IsCommand(void) const
{
    return true;
}

//-----------------------------------------------------------------------------
// Purpose: Invoke the function if there is one
//-----------------------------------------------------------------------------
void ConCommand::Dispatch(const CCommand &command)
{
    if (m_bUsingNewCommandCallback)
    {
        if (m_fnCommandCallback)
        {
            (*m_fnCommandCallback)(command);
            return;
        }
    }
    else if (m_bUsingCommandCallbackInterface)
    {
        if (m_pCommandCallback)
        {
            m_pCommandCallback->CommandCallback(command);
            return;
        }
    }
    else
    {
        if (m_fnCommandCallbackV1)
        {
            (*m_fnCommandCallbackV1)();
            return;
        }
    }

    // Command without callback!!!
    AssertMsg(0, "Encountered ConCommand '%s' without a callback!\n", GetName());
}

//-----------------------------------------------------------------------------
// Purpose: Calls the autocompletion method to get autocompletion suggestions
//-----------------------------------------------------------------------------
int ConCommand::AutoCompleteSuggest(const char *partial, CUtlVector<CUtlString> &commands)
{
    if (m_bUsingCommandCallbackInterface)
    {
        if (!m_pCommandCompletionCallback)
            return 0;
        return m_pCommandCompletionCallback->CommandCompletionCallback(partial, commands);
    }

    Assert(m_fnCompletionCallback);
    if (!m_fnCompletionCallback)
        return 0;

    char rgpchCommands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH];
    int iret = (m_fnCompletionCallback)(partial, rgpchCommands);
    for (int i = 0; i < iret; ++i)
    {
        CUtlString str = rgpchCommands[i];
        commands.AddToTail(str);
    }
    return iret;
}

//-----------------------------------------------------------------------------
// Returns true if the console command can autocomplete
//-----------------------------------------------------------------------------
bool ConCommand::CanAutoComplete(void)
{
    return m_bHasCompletionCallback;
}

//-----------------------------------------------------------------------------
//
// Console Variables
//
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Various constructors
//-----------------------------------------------------------------------------
ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags /* = 0 */)
{
    Create(pName, pDefaultValue, flags);
}

ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, const char *pHelpString)
{
    Create(pName, pDefaultValue, flags, pHelpString);
}

ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax)
{
    Create(pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax);
}

ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, FnChangeCallback_t callback)
{
    Create(pName, pDefaultValue, flags, pHelpString, false, 0.0, false, 0.0, callback);
}

ConVar::ConVar(const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax, FnChangeCallback_t callback)
{
    Create(pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax, callback);
}

//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
ConVar::~ConVar(void)
{
    if (m_pszString)
    {
        delete[] m_pszString;
        m_pszString = NULL;
    }
}

//-----------------------------------------------------------------------------
// Install a change callback (there shouldn't already be one....)
//-----------------------------------------------------------------------------
void ConVar::InstallChangeCallback(FnChangeCallback_t callback)
{
    Assert(!m_pParent->m_fnChangeCallback || !callback);
    m_pParent->m_fnChangeCallback = callback;

    if (m_pParent->m_fnChangeCallback)
    {
        // Call it immediately to set the initial value...
        m_pParent->m_fnChangeCallback(this, m_pszString, m_fValue);
    }
}

bool ConVar::IsFlagSet(int flag) const
{
    return (flag & m_pParent->m_nFlags) ? true : false;
}

const char *ConVar::GetHelpText(void) const
{
    return m_pParent->m_pszHelpString;
}

void ConVar::AddFlags(int flags)
{
    m_pParent->m_nFlags |= flags;

#ifdef ALLOW_DEVELOPMENT_CVARS
    m_pParent->m_nFlags &= ~FCVAR_DEVELOPMENTONLY;
#endif
}

bool ConVar::IsRegistered(void) const
{
    return m_pParent->m_bRegistered;
}

const char *ConVar::GetName(void) const
{
    return m_pParent->m_pszName;
}

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

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
//-----------------------------------------------------------------------------
void ConVar::Init()
{
    BaseClass::Init();
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : *value -
//-----------------------------------------------------------------------------
void ConVar::InternalSetValue(const char *value)
{
    if (IsFlagSet(FCVAR_MATERIAL_THREAD_MASK))
    {
        if (g_pCVar && !g_pCVar->IsMaterialThreadSetAllowed())
        {
            g_pCVar->QueueMaterialThreadSetValue(this, value);
            return;
        }
    }

    float fNewValue;
    char tempVal[32];
    char *val;

    Assert(m_pParent == this); // Only valid for root convars.

    float flOldValue = m_fValue;

    val = (char *) value;
    if (!value)
        fNewValue = 0.0f;
    else
        fNewValue = (float) atof(value);

    if (ClampValue(fNewValue))
    {
        Q_snprintf(tempVal, sizeof(tempVal), "%f", fNewValue);
        val = tempVal;
    }

    // Redetermine value
    m_fValue = fNewValue;
    m_nValue = (int) (fNewValue);

    if (!(m_nFlags & FCVAR_NEVER_AS_STRING))
    {
        ChangeStringValue(val, flOldValue);
    }
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : *tempVal -
//-----------------------------------------------------------------------------
void ConVar::ChangeStringValue(const char *tempVal, float flOldValue)
{
    Assert(!(m_nFlags & FCVAR_NEVER_AS_STRING));

    char *pszOldValue = (char *) stackalloc(m_StringLength);
    memcpy(pszOldValue, m_pszString, m_StringLength);

    if (tempVal)
    {
        int len = Q_strlen(tempVal) + 1;

        if (len > m_StringLength)
        {
            if (m_pszString)
            {
                delete[] m_pszString;
            }

            m_pszString    = new char[len];
            m_StringLength = len;
        }

        memcpy(m_pszString, tempVal, len);
    }
    else
    {
        *m_pszString = 0;
    }

    // If nothing has changed, don't do the callbacks.
    if (V_strcmp(pszOldValue, m_pszString) != 0)
    {
        // Invoke any necessary callback function
        if (m_fnChangeCallback)
        {
            m_fnChangeCallback(this, pszOldValue, flOldValue);
        }
        /* URAN */
        // logging::Info("Calling 0x%08x callbacks", g_pCVar);
        g_pCVar->CallGlobalChangeCallbacks(this, pszOldValue, flOldValue);
    }

    stackfree(pszOldValue);
}

//-----------------------------------------------------------------------------
// Purpose: Check whether to clamp and then perform clamp
// Input  : value -
// Output : Returns true if value changed
//-----------------------------------------------------------------------------
bool ConVar::ClampValue(float &value)
{
    if (m_bHasMin && (value < m_fMinVal))
    {
        value = m_fMinVal;
        return true;
    }

    if (m_bHasMax && (value > m_fMaxVal))
    {
        value = m_fMaxVal;
        return true;
    }

    return false;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : *value -
//-----------------------------------------------------------------------------
void ConVar::InternalSetFloatValue(float fNewValue)
{
    if (fNewValue == m_fValue)
        return;

    if (IsFlagSet(FCVAR_MATERIAL_THREAD_MASK))
    {
        if (g_pCVar && !g_pCVar->IsMaterialThreadSetAllowed())
        {
            g_pCVar->QueueMaterialThreadSetValue(this, fNewValue);
            return;
        }
    }

    Assert(m_pParent == this); // Only valid for root convars.

    // Check bounds
    ClampValue(fNewValue);

    // Redetermine value
    float flOldValue = m_fValue;
    m_fValue         = fNewValue;
    m_nValue         = (int) m_fValue;

    if (!(m_nFlags & FCVAR_NEVER_AS_STRING))
    {
        char tempVal[32];
        snprintf(tempVal, sizeof(tempVal), "%f", m_fValue);
        ChangeStringValue(tempVal, flOldValue);
    }
    else
    {
        Assert(!m_fnChangeCallback);
    }
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : *value -
//-----------------------------------------------------------------------------
void ConVar::InternalSetIntValue(int nValue)
{
    if (nValue == m_nValue)
        return;

    if (IsFlagSet(FCVAR_MATERIAL_THREAD_MASK))
    {
        if (g_pCVar && !g_pCVar->IsMaterialThreadSetAllowed())
        {
            g_pCVar->QueueMaterialThreadSetValue(this, nValue);
            return;
        }
    }

    Assert(m_pParent == this); // Only valid for root convars.

    float fValue = (float) nValue;
    if (ClampValue(fValue))
    {
        nValue = (int) (fValue);
    }

    // Redetermine value
    float flOldValue = m_fValue;
    m_fValue         = fValue;
    m_nValue         = nValue;

    if (!(m_nFlags & FCVAR_NEVER_AS_STRING))
    {
        char tempVal[32];
        snprintf(tempVal, sizeof(tempVal), "%d", m_nValue);
        ChangeStringValue(tempVal, flOldValue);
    }
    else
    {
        Assert(!m_fnChangeCallback);
    }
}

//-----------------------------------------------------------------------------
// Purpose: Private creation
//-----------------------------------------------------------------------------
void ConVar::Create(const char *pName, const char *pDefaultValue, int flags /*= 0*/, const char *pHelpString /*= NULL*/, bool bMin /*= false*/, float fMin /*= 0.0*/, bool bMax /*= false*/, float fMax /*= false*/, FnChangeCallback_t callback /*= NULL*/)
{
    m_pParent = this;

    // Name should be static data
    SetDefault(pDefaultValue);

    m_StringLength = V_strlen(m_pszDefaultValue) + 1;
    m_pszString    = new char[m_StringLength];
    memcpy(m_pszString, m_pszDefaultValue, m_StringLength);

    m_bHasMin = bMin;
    m_fMinVal = fMin;
    m_bHasMax = bMax;
    m_fMaxVal = fMax;

    m_fnChangeCallback = callback;

    m_fValue = (float) atof(m_pszString);
    m_nValue = atoi(m_pszString); // dont convert from float to int and lose bits

    // Bounds Check, should never happen, if it does, no big deal
    if (m_bHasMin && (m_fValue < m_fMinVal))
    {
        Assert(0);
    }

    if (m_bHasMax && (m_fValue > m_fMaxVal))
    {
        Assert(0);
    }

    BaseClass::CreateBase(pName, pHelpString, flags);
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : *value -
//-----------------------------------------------------------------------------
void ConVar::SetValue(const char *value)
{
    ConVar *var = (ConVar *) m_pParent;
    var->InternalSetValue(value);
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : value -
//-----------------------------------------------------------------------------
void ConVar::SetValue(float value)
{
    ConVar *var = (ConVar *) m_pParent;
    var->InternalSetFloatValue(value);
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : value -
//-----------------------------------------------------------------------------
void ConVar::SetValue(int value)
{
    ConVar *var = (ConVar *) m_pParent;
    var->InternalSetIntValue(value);
}

//-----------------------------------------------------------------------------
// Purpose: Reset to default value
//-----------------------------------------------------------------------------
void ConVar::Revert(void)
{
    // Force default value again
    ConVar *var = (ConVar *) m_pParent;
    var->SetValue(var->m_pszDefaultValue);
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : minVal -
// Output : true if there is a min set
//-----------------------------------------------------------------------------
bool ConVar::GetMin(float &minVal) const
{
    minVal = m_pParent->m_fMinVal;
    return m_pParent->m_bHasMin;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  : maxVal -
//-----------------------------------------------------------------------------
bool ConVar::GetMax(float &maxVal) const
{
    maxVal = m_pParent->m_fMaxVal;
    return m_pParent->m_bHasMax;
}

//-----------------------------------------------------------------------------
// Purpose:
// Output : const char
//-----------------------------------------------------------------------------
const char *ConVar::GetDefault(void) const
{
    return m_pParent->m_pszDefaultValue;
}

void ConVar::SetDefault(const char *pszDefault)
{
    m_pszDefaultValue = pszDefault ? pszDefault : "";
    Assert(m_pszDefaultValue);
}

//-----------------------------------------------------------------------------
// This version is simply used to make reading convars simpler.
// Writing convars isn't allowed in this mode
//-----------------------------------------------------------------------------
class CEmptyConVar : public ConVar
{
public:
    CEmptyConVar() : ConVar("", "0")
    {
    }
    // Used for optimal read access
    virtual void SetValue(const char *pValue)
    {
    }
    virtual void SetValue(float flValue)
    {
    }
    virtual void SetValue(int nValue)
    {
    }
    virtual const char *GetName(void) const
    {
        return "";
    }
    virtual bool IsFlagSet(int nFlags) const
    {
        return false;
    }
};

static CEmptyConVar s_EmptyConVar;

ConVarRef::ConVarRef(const char *pName)
{
    Init(pName, false);
}

ConVarRef::ConVarRef(const char *pName, bool bIgnoreMissing)
{
    Init(pName, bIgnoreMissing);
}

void ConVarRef::Init(const char *pName, bool bIgnoreMissing)
{
    m_pConVar = g_pCVar ? g_pCVar->FindVar(pName) : &s_EmptyConVar;
    if (!m_pConVar)
    {
        m_pConVar = &s_EmptyConVar;
    }
    m_pConVarState = static_cast<ConVar *>(m_pConVar);
    if (!IsValid())
    {
        static bool bFirst = true;
        if (g_pCVar || bFirst)
        {
            if (!bIgnoreMissing)
            {
                Warning("ConVarRef %s doesn't point to an existing ConVar\n", pName);
            }
            bFirst = false;
        }
    }
}

ConVarRef::ConVarRef(IConVar *pConVar)
{
    m_pConVar      = pConVar ? pConVar : &s_EmptyConVar;
    m_pConVarState = static_cast<ConVar *>(m_pConVar);
}

bool ConVarRef::IsValid() const
{
    return m_pConVar != &s_EmptyConVar;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ConVar_PrintFlags(const ConCommandBase *var)
{
    bool any = false;
    if (var->IsFlagSet(FCVAR_GAMEDLL))
    {
        ConMsg(" game");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_CLIENTDLL))
    {
        ConMsg(" client");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_ARCHIVE))
    {
        ConMsg(" archive");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_NOTIFY))
    {
        ConMsg(" notify");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_SPONLY))
    {
        ConMsg(" singleplayer");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_NOT_CONNECTED))
    {
        ConMsg(" notconnected");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_CHEAT))
    {
        ConMsg(" cheat");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_REPLICATED))
    {
        ConMsg(" replicated");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_SERVER_CAN_EXECUTE))
    {
        ConMsg(" server_can_execute");
        any = true;
    }

    if (var->IsFlagSet(FCVAR_CLIENTCMD_CAN_EXECUTE))
    {
        ConMsg(" clientcmd_can_execute");
        any = true;
    }

    if (any)
    {
        ConMsg("\n");
    }
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ConVar_PrintDescription(const ConCommandBase *pVar)
{
    bool bMin, bMax;
    float fMin, fMax;
    const char *pStr;

    assert(pVar);

    Color clr;
    clr.SetColor(255, 100, 100, 255);

    if (!pVar->IsCommand())
    {
        ConVar *var                          = (ConVar *) pVar;
        const ConVar_ServerBounded *pBounded = dynamic_cast<const ConVar_ServerBounded *>(var);

        bMin = var->GetMin(fMin);
        bMax = var->GetMax(fMax);

        const char *value = NULL;
        char tempVal[32];

        if (pBounded || var->IsFlagSet(FCVAR_NEVER_AS_STRING))
        {
            value = tempVal;

            int intVal     = pBounded ? pBounded->GetInt() : var->GetInt();
            float floatVal = pBounded ? pBounded->GetFloat() : var->GetFloat();

            if (fabs((float) intVal - floatVal) < 0.000001)
            {
                Q_snprintf(tempVal, sizeof(tempVal), "%d", intVal);
            }
            else
            {
                Q_snprintf(tempVal, sizeof(tempVal), "%f", floatVal);
            }
        }
        else
        {
            value = var->GetString();
        }

        if (value)
        {
            ConColorMsg(clr, "\"%s\" = \"%s\"", var->GetName(), value);

            if (stricmp(value, var->GetDefault()))
            {
                ConMsg(" ( def. \"%s\" )", var->GetDefault());
            }
        }

        if (bMin)
        {
            ConMsg(" min. %f", fMin);
        }
        if (bMax)
        {
            ConMsg(" max. %f", fMax);
        }

        ConMsg("\n");

        // Handled virtualized cvars.
        if (pBounded && fabs(pBounded->GetFloat() - var->GetFloat()) > 0.0001f)
        {
            ConColorMsg(clr,
                        "** NOTE: The real value is %.3f but the server has "
                        "temporarily restricted it to %.3f **\n",
                        var->GetFloat(), pBounded->GetFloat());
        }
    }
    else
    {
        ConCommand *var = (ConCommand *) pVar;

        ConColorMsg(clr, "\"%s\"\n", var->GetName());
    }

    ConVar_PrintFlags(pVar);

    pStr = pVar->GetHelpText();
    if (pStr && pStr[0])
    {
        ConMsg(" - %s\n", pStr);
    }
}
