#include "catch.hpp"

#include <stdexcept>

#include "Formula.h"

TEST_CASE("Formula compiles a constant expression", "[formula]")
{
    const Formula f = Formula::compile("42");

    REQUIRE(f.isValid());
    REQUIRE(f.evaluate(0.0)   == Approx(42.0));
    REQUIRE(f.evaluate(100.0) == Approx(42.0));
}

TEST_CASE("Formula evaluates a linear expression in x", "[formula]")
{
    // Matches world.toml [waves].threat_rate_formula default.
    const Formula f = Formula::compile("1*x - 30");

    REQUIRE(f.evaluate(0.0)  == Approx(-30.0));
    REQUIRE(f.evaluate(30.0) == Approx(0.0));
    REQUIRE(f.evaluate(60.0) == Approx(30.0));
}

TEST_CASE("Formula evaluates a polynomial in x", "[formula]")
{
    const Formula f = Formula::compile("2 + 3*x*x");

    REQUIRE(f.evaluate(0.0) == Approx(2.0));
    REQUIRE(f.evaluate(2.0) == Approx(14.0));
    REQUIRE(f.evaluate(4.0) == Approx(50.0));
}

TEST_CASE("Formula retains its source string", "[formula]")
{
    const std::string source = "10 + x / 5";
    const Formula f = Formula::compile(source);

    REQUIRE(f.getSource() == source);
}

TEST_CASE("Formula throws on malformed source", "[formula]")
{
    REQUIRE_THROWS_AS(Formula::compile("1 +"),        std::runtime_error);
    REQUIRE_THROWS_AS(Formula::compile(""),          std::runtime_error);
    REQUIRE_THROWS_AS(Formula::compile("unknown_var"), std::runtime_error);
}

TEST_CASE("Formula survives move construction", "[formula]")
{
    // tinyexpr bakes the bound variable's address into the compiled tree.
    // Formula keeps x on the heap so that address stays valid across moves.
    Formula original = Formula::compile("x * 2");
    Formula moved(std::move(original));

    REQUIRE(moved.evaluate(21.0) == Approx(42.0));
}

TEST_CASE("Formula survives move assignment", "[formula]")
{
    Formula a = Formula::compile("x + 1");
    Formula b = Formula::compile("x + 100");

    b = std::move(a);

    REQUIRE(b.evaluate(5.0) == Approx(6.0));
}

TEST_CASE("Default-constructed Formula is invalid and throws on evaluate", "[formula]")
{
    const Formula f;
    REQUIRE_FALSE(f.isValid());
    REQUIRE_THROWS_AS(f.evaluate(0.0), std::runtime_error);
}
