/* bzflag
 * Copyright (c) 1993-2025 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "common.h"

// implementation header
#include "BZDBLocal.h"

// system headers
#include <stdio.h>
#include <string.h>
#include <string>

// common headers
#include "StateDatabase.h"
#include "ParseColor.h"


// Function Prototypes
// -------------------
static void safeSetInt(const std::string& name, int value);
static void safeSetFloat(const std::string& name, float value);
static void safeSetString(const std::string& name, const std::string&value);


/******************************************************************************/
//
// BZDBbool
//

BZDBbool::BZDBbool(const std::string& _name, bool defVal, bool save)
    : BZDBLocal(_name, save), data(defVal)
{
    BZDBLocalManager::manager.addEntry(this);
    logDebugMessage(3,"Added BZDBbool(%s) callback\n", name.c_str());
    return;
}


BZDBbool::~BZDBbool()
{
    return;
}


void BZDBbool::callback()
{
    data = BZDB.isTrue(name);
    logDebugMessage(4,"BZDBbool(%s) = %s\n", name.c_str(), data ? "true" : "false");
    return;
}


void BZDBbool::staticCallback(const std::string& UNUSED(name), void* data)
{
    ((BZDBbool*)data)->callback();
    return;
}


void BZDBbool::addCallbacks()
{
    if (BZDB.isSet(name))
    {
        fprintf(stderr, "BZDBbool duplicate \"%s\".\n", name.c_str());
        exit(1);
    }
    BZDB.setBool(name, data);
    BZDB.setDefault(name, BZDB.get(name));
    BZDB.setPersistent(name, saveOnExit);
    BZDB.addCallback(name, staticCallback, this);
    return;
}


void BZDBbool::removeCallbacks()
{
    BZDB.removeCallback(name, staticCallback, this);
    return;
}


/******************************************************************************/
//
// BZDBint
//

BZDBint::BZDBint(const std::string& _name, int defVal,
                 int _min, int _max, bool _neverZero, bool save)
    : BZDBLocal(_name, save), data(defVal),
      min(_min), max(_max), neverZero(_neverZero)
{
    BZDBLocalManager::manager.addEntry(this);
    logDebugMessage(3,"Added BZDBint(%s) callback\n", name.c_str());
    return;
}


BZDBint::~BZDBint()
{
    return;
}


void BZDBint::callback()
{
    int tmp = BZDB.evalInt(name);

    if (tmp < min)
    {
        logDebugMessage(3,"BZDBint(%s) min: %i < %i\n", name.c_str(), tmp, min);
        tmp = min; // clamp to min
        safeSetInt(name, tmp);
    }

    if (tmp > max)
    {
        logDebugMessage(3,"BZDBint(%s) max: %i > %i\n", name.c_str(), tmp, max);
        tmp = max; // clamp to max
        safeSetInt(name, tmp);
    }

    if (neverZero && (tmp == 0))
    {
        logDebugMessage(3,"BZDBint(%s) neverZero\n", name.c_str());
        return; // bail out
    }

    data = tmp; // set the new value

    logDebugMessage(4,"BZDBint(%s) = %i\n", name.c_str(), data);

    return;
}


void BZDBint::staticCallback(const std::string& UNUSED(name), void* data)
{
    ((BZDBint*)data)->callback();
    return;
}


void BZDBint::addCallbacks()
{
    if (BZDB.isSet(name))
    {
        fprintf(stderr, "BZDBint duplicate \"%s\".\n", name.c_str());
        exit(1);
    }
    BZDB.setInt(name, data);
    BZDB.setDefault(name, BZDB.get(name));
    BZDB.setPersistent(name, saveOnExit);
    BZDB.addCallback(name, staticCallback, this);
    return;
}


void BZDBint::removeCallbacks()
{
    BZDB.removeCallback(name, staticCallback, this);
    return;
}


/******************************************************************************/
//
// BZDBfloat
//

BZDBfloat::BZDBfloat(const std::string& _name, float defVal,
                     float _min, float _max,
                     bool _neverZero, bool save)
    : BZDBLocal(_name, save), data(defVal),
      min(_min), max(_max), neverZero(_neverZero)
{
    BZDBLocalManager::manager.addEntry(this);
    logDebugMessage(3,"Added BZDBfloat(%s) callback\n", name.c_str());
    return;
}


BZDBfloat::~BZDBfloat()
{
    return;
}


void BZDBfloat::callback()
{
    float tmp = BZDB.eval(name);

    if (tmp < min)
    {
        logDebugMessage(3,"BZDBfloat(%s) min: %f < %f\n", name.c_str(), tmp, min);
        tmp = min; // clamp to min
        safeSetFloat(name, tmp);
    }

    if (tmp > max)
    {
        logDebugMessage(3,"BZDBfloat(%s) max: %f > %f\n", name.c_str(), tmp, max);
        tmp = max; // clamp to max
        safeSetFloat(name, tmp);
    }

    if (neverZero && (tmp == 0.0f))
    {
        logDebugMessage(3,"BZDBfloat(%s) neverZero\n", name.c_str());
        return; // bail out
    }

    data = tmp; // set the new value

    logDebugMessage(4,"BZDBfloat(%s) = %f\n", name.c_str(), data);

    return;
}


void BZDBfloat::staticCallback(const std::string& UNUSED(name), void* data)
{
    ((BZDBfloat*)data)->callback();
    return;
}


void BZDBfloat::addCallbacks()
{
    if (BZDB.isSet(name))
    {
        fprintf(stderr, "BZDBfloat duplicate \"%s\".\n", name.c_str());
        exit(1);
    }
    BZDB.setFloat(name, data);
    BZDB.setDefault(name, BZDB.get(name));
    BZDB.setPersistent(name, saveOnExit);
    BZDB.addCallback(name, staticCallback, this);
    return;
}


void BZDBfloat::removeCallbacks()
{
    BZDB.removeCallback(name, staticCallback, this);
    return;
}


/******************************************************************************/
//
// BZDBcolor
//

BZDBcolor::BZDBcolor(const std::string& _name,
                     float r, float g, float b, float a,
                     bool _neverAlpha, bool save)
    : BZDBLocal(_name, save), neverAlpha(_neverAlpha)
{
    data[0] = r;
    data[1] = g;
    data[2] = b;
    data[3] = a;
    BZDBLocalManager::manager.addEntry(this);
    logDebugMessage(3,"Added BZDBcolor(%s) callback\n", name.c_str());
    return;
}


BZDBcolor::~BZDBcolor()
{
    return;
}


void BZDBcolor::callback()
{
    const std::string& expr = BZDB.get(name);
    float color[4];

    if (!parseColorString(expr, color))
    {
        logDebugMessage(3,"BZDBcolor(%s) bad string: %s\n", name.c_str(), expr.c_str());
        return;
    }

    if (neverAlpha && (color[3] < 1.0f))
    {
        logDebugMessage(3,"BZDBcolor(%s) made opaque: %f\n", name.c_str(), color[3]);
        color[3] = 1.0f;
        char buf[256];
        snprintf(buf, 256, " %f %f %f %f", color[0], color[1], color[2], color[3]);
        safeSetString(name, buf);
    }

    // set the new value
    memcpy(data, color, sizeof(float[4]));

    logDebugMessage(4,"BZDBcolor(%s) = %f, %f, %f, %f\n", name.c_str(),
                    data[0], data[1], data[2], data[3]);

    return;
}


void BZDBcolor::staticCallback(const std::string& UNUSED(name), void* data)
{
    ((BZDBcolor*)data)->callback();
    return;
}


void BZDBcolor::addCallbacks()
{
    if (BZDB.isSet(name))
    {
        fprintf(stderr, "BZDBcolor duplicate \"%s\".\n", name.c_str());
        exit(1);
    }

    char buf[256];
    snprintf(buf, 256, " %f %f %f %f", data[0], data[1], data[2], data[3]);
    BZDB.set(name, buf);
    BZDB.setDefault(name, BZDB.get(name));
    BZDB.setPersistent(name, saveOnExit);
    BZDB.addCallback(name, staticCallback, this);
    return;
}


void BZDBcolor::removeCallbacks()
{
    BZDB.removeCallback(name, staticCallback, this);
    return;
}


/******************************************************************************/
//
// BZDBstring
//

BZDBstring::BZDBstring(const std::string& _name, const std::string& defVal,
                       bool _neverEmpty, bool save)
    : BZDBLocal(_name, save),
      data(defVal), neverEmpty(_neverEmpty)
{
    BZDBLocalManager::manager.addEntry(this);
    logDebugMessage(3,"Added BZDBstring(%s) callback\n", name.c_str());
    return;
}


BZDBstring::~BZDBstring()
{
    return;
}


void BZDBstring::callback()
{
    const std::string& tmp = BZDB.get(name);

    if (neverEmpty && (tmp.size() <= 0))
    {
        logDebugMessage(3,"BZDBstring(%s) empty string: %s\n", name.c_str(), tmp.c_str());
        safeSetString(name, tmp);
        return;
    }

    data = tmp; // set the new value

    logDebugMessage(4,"BZDBstring(%s) = %s\n", name.c_str(), data.c_str());

    return;
}


void BZDBstring::staticCallback(const std::string& UNUSED(name), void* data)
{
    ((BZDBstring*)data)->callback();
    return;
}


void BZDBstring::addCallbacks()
{
    if (BZDB.isSet(name))
    {
        fprintf(stderr, "BZDBstring duplicate \"%s\".\n", name.c_str());
        exit(1);
    }

    BZDB.set(name, data);
    BZDB.setDefault(name, BZDB.get(name));
    BZDB.setPersistent(name, saveOnExit);
    BZDB.addCallback(name, staticCallback, this);
    return;
}


void BZDBstring::removeCallbacks()
{
    BZDB.removeCallback(name, staticCallback, this);
    return;
}


/******************************************************************************/

BZDBLocalManager BZDBLocalManager::manager;


static std::vector<BZDBLocal*>& getEntryVector()
{
    static std::vector<BZDBLocal*> vec;
    return vec;
}


BZDBLocalManager::BZDBLocalManager()
{
    return;
}


BZDBLocalManager::~BZDBLocalManager()
{
    return;
}


bool BZDBLocalManager::addEntry(BZDBLocal* entry)
{
    std::vector<BZDBLocal*>& vec = getEntryVector();
    vec.push_back(entry);
    return true;
}


void BZDBLocalManager::init()
{
    std::vector<BZDBLocal*>& entries = getEntryVector();
    for (unsigned int i = 0; i < entries.size(); i++)
        entries[i]->addCallbacks();
    return;
}


void BZDBLocalManager::kill()
{
    std::vector<BZDBLocal*>& entries = getEntryVector();
    for (unsigned int i = 0; i < entries.size(); i++)
        entries[i]->removeCallbacks();
    return;
}


/******************************************************************************/
/******************************************************************************/

static bool Inside = false;

static void safeSetInt(const std::string& name, int value)
{
    if (!Inside)
    {
        Inside = true;
        BZDB.setInt(name, value);
        Inside = false;
    }
    return;
}


static void safeSetFloat(const std::string& name, float value)
{
    if (!Inside)
    {
        Inside = true;
        BZDB.setFloat(name, value);
        Inside = false;
    }
    return;
}


static void safeSetString(const std::string& name, const std::string&value)
{
    if (!Inside)
    {
        Inside = true;
        BZDB.set(name, value);
        Inside = false;
    }
    return;
}


/******************************************************************************/

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
