#include "paint/brush.h"
#include "test_harness.h"

#include <cmath>

using pp::foundation::StatusCode;
using pp::paint::BrushParams;
using pp::paint::evaluate_brush_stamp;
using pp::paint::max_brush_size;
using pp::paint::min_brush_size;
using pp::paint::validate_brush_params;

namespace {

bool near(float a, float b)
{
    return std::fabs(a - b) < 0.0001F;
}

void accepts_default_and_boundary_params(pp::tests::Harness& h)
{
    BrushParams defaults;
    BrushParams minimums {
        .size = min_brush_size,
        .spacing = 0.01F,
        .opacity = 0.0F,
        .flow = 0.0F,
        .angle_degrees = -360.0F,
        .size_jitter = 0.0F,
        .opacity_jitter = 0.0F,
    };
    BrushParams maximums {
        .size = max_brush_size,
        .spacing = 16.0F,
        .opacity = 1.0F,
        .flow = 1.0F,
        .angle_degrees = 360.0F,
        .size_jitter = 1.0F,
        .opacity_jitter = 1.0F,
    };

    PP_EXPECT(h, validate_brush_params(defaults).ok());
    PP_EXPECT(h, validate_brush_params(minimums).ok());
    PP_EXPECT(h, validate_brush_params(maximums).ok());
}

void rejects_invalid_params(pp::tests::Harness& h)
{
    BrushParams params;

    params.size = 0.0F;
    PP_EXPECT(h, validate_brush_params(params).code == StatusCode::out_of_range);
    params = BrushParams {};
    params.spacing = 0.0F;
    PP_EXPECT(h, validate_brush_params(params).code == StatusCode::out_of_range);
    params = BrushParams {};
    params.opacity = -0.1F;
    PP_EXPECT(h, validate_brush_params(params).code == StatusCode::out_of_range);
    params = BrushParams {};
    params.flow = 1.1F;
    PP_EXPECT(h, validate_brush_params(params).code == StatusCode::out_of_range);
    params = BrushParams {};
    params.angle_degrees = 361.0F;
    PP_EXPECT(h, validate_brush_params(params).code == StatusCode::out_of_range);
    params = BrushParams {};
    params.size_jitter = std::nanf("");
    PP_EXPECT(h, validate_brush_params(params).code == StatusCode::out_of_range);
    params = BrushParams {};
    params.opacity_jitter = 2.0F;
    PP_EXPECT(h, validate_brush_params(params).code == StatusCode::out_of_range);
}

void evaluates_pressure_controlled_stamp(pp::tests::Harness& h)
{
    const BrushParams params {
        .size = 20.0F,
        .spacing = 0.5F,
        .opacity = 0.8F,
        .flow = 0.6F,
        .angle_degrees = 45.0F,
        .size_jitter = 0.0F,
        .opacity_jitter = 0.0F,
        .pressure_controls_size = true,
        .pressure_controls_opacity = true,
    };

    const auto stamp = evaluate_brush_stamp(params, 0.5F);
    PP_EXPECT(h, near(stamp.size, 10.0F));
    PP_EXPECT(h, near(stamp.opacity, 0.4F));
    PP_EXPECT(h, near(stamp.flow, 0.6F));
    PP_EXPECT(h, near(stamp.angle_degrees, 45.0F));
}

void clamps_bad_pressure_and_applies_deterministic_jitter_scale(pp::tests::Harness& h)
{
    const BrushParams params {
        .size = 20.0F,
        .spacing = 0.5F,
        .opacity = 0.8F,
        .flow = 0.6F,
        .angle_degrees = 0.0F,
        .size_jitter = 0.5F,
        .opacity_jitter = 1.0F,
        .pressure_controls_size = false,
        .pressure_controls_opacity = false,
    };

    const auto nan_pressure = evaluate_brush_stamp(params, std::nanf(""));
    const auto high_pressure = evaluate_brush_stamp(params, 2.0F);

    PP_EXPECT(h, near(nan_pressure.size, 15.0F));
    PP_EXPECT(h, near(nan_pressure.opacity, 0.4F));
    PP_EXPECT(h, near(high_pressure.size, 15.0F));
    PP_EXPECT(h, near(high_pressure.opacity, 0.4F));
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("accepts_default_and_boundary_params", accepts_default_and_boundary_params);
    harness.run("rejects_invalid_params", rejects_invalid_params);
    harness.run("evaluates_pressure_controlled_stamp", evaluates_pressure_controlled_stamp);
    harness.run("clamps_bad_pressure_and_applies_deterministic_jitter_scale", clamps_bad_pressure_and_applies_deterministic_jitter_scale);
    return harness.finish();
}
