#include "Formula.h"

#include <stdexcept>

#include "tinyexpr.h"

namespace
{
    // tinyexpr has no built-in min/max; expose them so config formulas can
    // clamp (e.g. a floored overclaim penalty "max(0.5, 1 - 0.1*x)").
    double formulaMin(double a, double b) { return a < b ? a : b; }
    double formulaMax(double a, double b) { return a > b ? a : b; }
}

Formula::Formula(Formula&& other) noexcept
    : m_source(std::move(other.m_source))
    , m_x(std::move(other.m_x))
    , m_expr(other.m_expr)
{
    other.m_expr = nullptr;
}

Formula& Formula::operator=(Formula&& other) noexcept
{
    if (this != &other)
    {
        te_free(m_expr);
        m_source = std::move(other.m_source);
        m_x = std::move(other.m_x);
        m_expr = other.m_expr;
        other.m_expr = nullptr;
    }
    return *this;
}

Formula::~Formula()
{
    te_free(m_expr);
}

Formula Formula::compile(const std::string& source)
{
    Formula result;
    result.m_source = source;
    result.m_x = std::make_unique<double>(0.0);

    const te_variable variables[] = {
        { "x",   result.m_x.get(),                            TE_VARIABLE,                 nullptr },
        { "min", reinterpret_cast<const void*>(&formulaMin),  TE_FUNCTION2 | TE_FLAG_PURE, nullptr },
        { "max", reinterpret_cast<const void*>(&formulaMax),  TE_FUNCTION2 | TE_FLAG_PURE, nullptr },
    };

    int errorPos = 0;
    const int variableCount = static_cast<int>(sizeof(variables) / sizeof(variables[0]));
    result.m_expr = te_compile(result.m_source.c_str(), variables, variableCount, &errorPos);

    if (result.m_expr == nullptr)
    {
        throw std::runtime_error(
            "Formula parse error at position " + std::to_string(errorPos)
            + " in \"" + source + "\"");
    }

    return result;
}

double Formula::evaluate(double x) const
{
    if (m_expr == nullptr)
    {
        throw std::runtime_error("Formula::evaluate called on uninitialized formula");
    }

    // The variable at *m_x is what the compiled tree dereferences during
    // te_eval. Mutating it here does not affect logical const-ness of the
    // formula — the formula itself is unchanged.
    *m_x = x;
    return te_eval(m_expr);
}

