If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// lib_config.cpp - abstract configuration

#include "library.h"

/*
==============================================================================

LConfigString IMPLEMENTATION

==============================================================================
*/

class LConfigString : public LConfig {
public:
    char        *m_pszValue;
    char        *m_pszDefault;

public:
    LConfigString(const char *pszName, const char *pszDefault, const char *pszValue = 0);
    virtual ~LConfigString();

    virtual void Parse(LParser *p);
    virtual void Store(LFileWrite *fw);
    virtual bool MustStore();

    virtual char *intGetString() const;
    virtual void intSetString(const char *value);
};

LConfigString::LConfigString(const char *pszName, const char *pszDefault,
                             const char *pszValue)
    : LConfig(pszName)
{
    if (pszDefault)
        m_pszDefault = L_Strdup(pszDefault, TAG_CONFIG);
    else
        m_pszDefault = 0;

    if (pszValue)
        m_pszValue = L_Strdup(pszDefault, TAG_CONFIG);
    else
        m_pszValue = 0;
}

LConfigString::~LConfigString()
{
    if (m_pszDefault)
        L_Free(m_pszDefault);
    if (m_pszValue)
        L_Free(m_pszValue);
}

void LConfigString::Parse(LParser *p)
{
    intSetString(p->AnyString());
}

void LConfigString::Store(LFileWrite *fw)
{
    fw->Printf("\"%s\"", m_pszValue ? m_pszValue : m_pszDefault);
}

bool LConfigString::MustStore()
{
    return m_pszValue ? true : false;
}

char *LConfigString::intGetString() const
{
    if (m_pszValue)
        return m_pszValue;
    if (m_pszDefault)
        return m_pszDefault;
    throw LError("no value given");
}

void LConfigString::intSetString(const char *value)
{
    lassert(value);

    if (m_pszValue) {
        L_Free(m_pszValue);
        m_pszValue = 0;
    }

    m_pszValue = L_Strdup(value, TAG_CONFIG);
}


/*
==============================================================================

LConfigInt IMPLEMENTATION

==============================================================================
*/

class LConfigInt : public LConfig {
public:
    bool    m_bValue;
    int     m_iValue;
    bool    m_bDefault;
    int     m_iDefault;

public:
    LConfigInt(const char *pszName, bool bDefault, int iDefault,
                bool bValue, int iValue);

    virtual void Parse(LParser *p);
    virtual void Store(LFileWrite *fw);
    virtual bool MustStore();

    virtual int intGetInt() const;
    virtual void intSetInt(int value);
};

LConfigInt::LConfigInt(const char *pszName, bool bDefault, int iDefault,
                       bool bValue, int iValue)
    : LConfig(pszName)
{
    m_bDefault = bDefault;
    m_iDefault = iDefault;
    m_bValue = bValue;
    m_iValue = iValue;
}

void LConfigInt::Parse(LParser *p)
{
    m_iValue = p->Integer();
    m_bValue = true;
}

void LConfigInt::Store(LFileWrite *fw)
{
    fw->Printf("%i", m_bValue ? m_iValue : m_iDefault);
}

bool LConfigInt::MustStore()
{
    return m_bValue;
}

int LConfigInt::intGetInt() const
{
    if (m_bValue)
        return m_iValue;
    if (m_bDefault)
        return m_iDefault;
    throw LError("no value given");
}

void LConfigInt::intSetInt(int value)
{
    m_iValue = value;
    m_bValue = true;
}

/*
==============================================================================

LConfigFloat IMPLEMENTATION

==============================================================================
*/

class LConfigFloat : public LConfig {
public:
    bool    m_bValue;
    float   m_flValue;
    bool    m_bDefault;
    float   m_flDefault;

public:
    LConfigFloat(const char *pszName, bool bDefault, float flDefault,
                bool bValue, float flValue);

    virtual void Parse(LParser *p);
    virtual void Store(LFileWrite *fw);
    virtual bool MustStore();

    virtual float intGetFloat() const;
    virtual void intSetFloat(float value);
};

LConfigFloat::LConfigFloat(const char *pszName, bool bDefault, float flDefault,
                           bool bValue, float flValue)
    : LConfig(pszName)
{
    m_bDefault = bDefault;
    m_flDefault = flDefault;
    m_bValue = bValue;
    m_flValue = flValue;
}

void LConfigFloat::Parse(LParser *p)
{
    m_flValue = p->Float();
    m_bValue = true;
}

void LConfigFloat::Store(LFileWrite *fw)
{
    fw->Printf("%f", m_bValue ? m_flValue : m_flDefault);
}

bool LConfigFloat::MustStore()
{
    return m_bValue;
}

float LConfigFloat::intGetFloat() const
{
    if (m_bValue)
        return m_flValue;
    if (m_bDefault)
        return m_flDefault;
    throw LError("no value given");
}

void LConfigFloat::intSetFloat(float value)
{
    m_flValue = value;
    m_bValue = true;
}

/*
==============================================================================

LConfigBool IMPLEMENTATION

==============================================================================
*/

class LConfigBool : public LConfig {
public:
    bool    m_bValue;
    bool    m_value;
    bool    m_bDefault;
    bool    m_default;

public:
    LConfigBool(const char *pszName, bool bDefault, bool thedefault,
                bool bValue, bool thevalue);

    virtual void Parse(LParser *p);
    virtual void Store(LFileWrite *fw);
    virtual bool MustStore();

    virtual bool intGetBool() const;
    virtual void intSetBool(bool value);
};

LConfigBool::LConfigBool(const char *pszName, bool bDefault, bool thedefault,
                           bool bValue, bool thevalue)
    : LConfig(pszName)
{
    m_bDefault = bDefault;
    m_default = thedefault;
    m_bValue = bValue;
    m_value = thevalue;
}

void LConfigBool::Parse(LParser *p)
{
    m_value = p->Integer() ? true : false;
    m_bValue = true;
}

void LConfigBool::Store(LFileWrite *fw)
{
    fw->Printf("%i", (m_bValue ? m_value : m_default) ? 1 : 0);
}

bool LConfigBool::MustStore()
{
    return m_bValue;
}

bool LConfigBool::intGetBool() const
{
    if (m_bValue)
        return m_value;
    if (m_bDefault)
        return m_default;
    throw LError("no value given");
}

void LConfigBool::intSetBool(bool value)
{
    m_value = value;
    m_bValue = true;
}

/*
==============================================================================

LConfigRecord IMPLEMENTATION

==============================================================================
*/

class LConfigRecord : public LConfig {
public:
    LConfig     *m_pItems;

public:
    LConfigRecord(const char *pszName, l_configrecord_t *definition);
    virtual ~LConfigRecord();

    virtual void Parse(LParser *p);
    virtual void Store(LFileWrite *fw);
    virtual bool MustStore();

    virtual LConfig *intGetItem(const char *name) const;
};

/*
==============
LConfigRecord::LConfigRecord

Completely build a configuration record, including all children
==============
*/
LConfigRecord::LConfigRecord(const char *pszName, l_configrecord_t *definition)
    : LConfig(pszName)
{
    l_configitem_t *it;
    LConfig *pItem;

    m_pItems = 0;

    for(it = definition->items; it->name; it++) {
        bool havedflt = it->dflt ? true : false;

        switch(it->type) {
        case CI_STRING:
            pItem = new LConfigString(it->name, it->dflt, 0);
            break;

        case CI_INT:
            pItem = new LConfigInt(it->name, havedflt, havedflt ? atoi(it->dflt) : 0,
                                    false, 0);
            break;

        case CI_FLOAT:
            pItem = new LConfigFloat(it->name, havedflt, havedflt ? atof(it->dflt) : 0,
                                    false, 0);
            break;

        case CI_BOOL:
            pItem = new LConfigBool(it->name,
                    havedflt, havedflt ? (atoi(it->dflt) ? true : false) : false,
                    false, false);
            break;

        case CI_RECORD:
            pItem = new LConfigRecord(it->name, it->record);
            break;

        default:
            lassert(0);
        }

        pItem->m_pNext = m_pItems;
        if (m_pItems)
            m_pItems->m_pPrev = pItem;
        pItem->m_pPrev = 0;
        m_pItems = pItem;
    }
}

/*
==============
LConfigRecord::~LConfigRecord

Free all children
==============
*/
LConfigRecord::~LConfigRecord()
{
    LConfig *pItem;

    while(m_pItems) {
        pItem = m_pItems;
        m_pItems = m_pItems->m_pNext;

        delete pItem;
    }
}

/*
==============
LConfigRecord::Parse

Read a record from a buffer, including { and }
==============
*/
void LConfigRecord::Parse(LParser *p)
{
    LConfig *pItem;

    p->Expect("{");

    for(;;) {
        p->NextToken();
        if (p->tok.type == '}')
            break;
        if (p->tok.type != TOK_STRING)
            throw p->Error("} or string expected");

        pItem = GetItem(p->tok.string);
        if (!pItem)
            throw p->Error("unknown config item %s", p->tok.string);

        pItem->Parse(p);

        p->Expect(";");
    }
}

/*
==============
LConfigRecord::Store

Write all non-default children to the file
==============
*/
void LConfigRecord::Store(LFileWrite *fw)
{
    LConfig *pItem;

    fw->Printf("{\n");

    for(pItem = m_pItems; pItem; pItem = pItem->m_pNext) {
        if (!pItem->MustStore())
            continue;

        fw->Printf("%s ", pItem->m_pszName);
        pItem->Store(fw);
        fw->Printf(";\n");
    }

    fw->Printf("}");
}

/*
==============
LConfigRecord::MustStore

We must store a record when any of our children must be stored
==============
*/
bool LConfigRecord::MustStore()
{
    LConfig *pItem;

    for(pItem = m_pItems; pItem; pItem = pItem->m_pNext)
        if (pItem->MustStore())
            return true;

    return false;
}

/*
==============
LConfigRecord::intGetItem

Return one of the children.
Throws an exception when the child cannot be found.
==============
*/
LConfig *LConfigRecord::intGetItem(const char *name) const
{
    LConfig *pItem;

    for(pItem = m_pItems; pItem; pItem = pItem->m_pNext) {
        if (!strcmp(pItem->m_pszName, name))
            return pItem;
    }

    throw LError("%s: no such item", name);
}


/*
==============================================================================

LConfig IMPLEMENTATION

==============================================================================
*/

/*
==============
LConfig::LConfig
LConfig::~LConfig

Don't initialize m_pNext/m_pPrev, they are maintained by the parent
==============
*/
LConfig::LConfig(const char *pszName)
{
    if (pszName)
        m_pszName = L_Strdup(pszName, TAG_CONFIG);
    else
        m_pszName = 0;
}

LConfig::~LConfig()
{
    if (m_pszName)
        L_Free(m_pszName);
}

/*
==============
LConfig::TryLoad

Load the configuration through the parser
==============
*/
bool LConfig::TryLoad(const char *filename)
{
    try
    {
        LParser p;

        p.AddFile(filename);
        Parse(&p);

        return true;
    }
    catch(LError &)
    {
        return false;
    }
}

/*
==============
LConfig::TrySave

Open a file for writing and save the configuration
==============
*/
bool LConfig::TrySave(const char *filename)
{
    bool bSuccess;

    bSuccess = false;

    try
    {
        LFileWrite fw;

        Store(&fw);
        bSuccess = fw.TryWrite(filename);
    }
    catch(LError &err)
    {
        L_Printf("Error writing config to %s: %s\n", filename, err.Get());
    }

    return bSuccess;
}

/*
==============
LConfig::GetString
LConfig::GetInt
LConfig::GetFloat

Wrapper functions for simplified access.
An exception is raised if the given configuration item doesn't
exist or it isn't possible to get a value for it.
==============
*/
char *LConfig::GetString(const char *name) const
{
    const LConfig *pItem;

    pItem = GetItem(name);
    return pItem->intGetString();
}

int LConfig::GetInt(const char *name) const
{
    const LConfig *pItem;

    pItem = GetItem(name);
    return pItem->intGetInt();
}

float LConfig::GetFloat(const char *name) const
{
    const LConfig *pItem;

    pItem = GetItem(name);
    return pItem->intGetFloat();
}

bool LConfig::GetBool(const char *name) const
{
    const LConfig *pItem;

    pItem = GetItem(name);
    return pItem->intGetBool();
}

/*
==============
LConfig::SetString
LConfig::SetInt
LConfig::SetFloat

Simple wrapper functions for setting configuration.
An exception is raised if the given configuration item doesn't
exist, or if it isn't of the expected type.
==============
*/
void LConfig::SetString(const char *name, const char *value)
{
    LConfig *pItem;

    pItem = GetItem(name);
    pItem->intSetString(value);
}

void LConfig::SetInt(const char *name, int value)
{
    LConfig *pItem;

    pItem = GetItem(name);
    pItem->intSetInt(value);
}

void LConfig::SetFloat(const char *name, float value)
{
    LConfig *pItem;

    pItem = GetItem(name);
    pItem->intSetFloat(value);
}

void LConfig::SetBool(const char *name, bool value)
{
    LConfig *pItem;

    pItem = GetItem(name);
    pItem->intSetBool(value);
}

/*
==============
LConfig::GetItem

Walk the given item path to retrieve the requested item.
Throws an exception if the item doesn't exist
==============
*/
const LConfig *LConfig::GetItem(const char *name) const
{
    LConfig *pItem;
    char buf[64];
    const char *p;
    int len;

    if (!name)
        return this;

    while(*name && *name == '.') name++;
    if (!*name)
        return this;

    p = name;
    while(*p && *p != '.') p++;
    len = p - name + 1;
    if (len > sizeof(buf))
        len = sizeof(buf);
    xstrcpy(buf, len, name);

    pItem = intGetItem(buf);
    return pItem->GetItem(p);
}

LConfig *LConfig::GetItem(const char *name)
{
    LConfig *pItem;
    char buf[64];
    const char *p;
    int len;

    if (!name)
        return this;

    while(*name && *name == '.') name++;
    if (!*name)
        return this;

    p = name;
    while(*p && *p != '.') p++;
    len = p - name + 1;
    if (len > sizeof(buf))
        len = sizeof(buf);
    xstrcpy(buf, len, name);

    pItem = intGetItem(buf);
    return pItem->GetItem(p);
}

/*
==============
LConfig::Get*
LConfig::Set*

Default implementations throw errors.
==============
*/
char *LConfig::intGetString() const
{
    throw LError("not a string");
}

int LConfig::intGetInt() const
{
    throw LError("not an integer");
}

float LConfig::intGetFloat() const
{
    throw LError("not a float");
}

bool LConfig::intGetBool() const
{
    throw LError("not a bool");
}

LConfig *LConfig::intGetItem(const char *name) const
{
    throw LError("not a record");
}

void LConfig::intSetString(const char *value)
{
    throw LError("not a string");
}

void LConfig::intSetInt(int value)
{
    throw LError("not an integer");
}

void LConfig::intSetFloat(float value)
{
    throw LError("not a float");
}

void LConfig::intSetBool(bool value)
{
    throw LError("not a bool");
}

/*
==============
LConfig::Create

Create a record object
==============
*/
LConfig *LConfig::Create(l_configrecord_t *definition)
{
    return new LConfigRecord(0, definition);
}