#include "paint/brush.h"

#include <algorithm>
#include <cmath>

namespace pp::paint {

namespace {

[[nodiscard]] bool finite_in_range(float value, float min, float max) noexcept
{
    return std::isfinite(value) && value >= min && value <= max;
}

[[nodiscard]] float clamp01(float value) noexcept
{
    return std::clamp(value, 0.0F, 1.0F);
}

}

pp::foundation::Status validate_brush_params(const BrushParams& params) noexcept
{
    if (!finite_in_range(params.size, min_brush_size, max_brush_size)) {
        return pp::foundation::Status::out_of_range("brush size is outside the configured range");
    }

    if (!finite_in_range(params.spacing, min_brush_spacing, max_brush_spacing)) {
        return pp::foundation::Status::out_of_range("brush spacing is outside the configured range");
    }

    if (!finite_in_range(params.opacity, 0.0F, 1.0F)) {
        return pp::foundation::Status::out_of_range("brush opacity must be finite and within 0..1");
    }

    if (!finite_in_range(params.flow, 0.0F, 1.0F)) {
        return pp::foundation::Status::out_of_range("brush flow must be finite and within 0..1");
    }

    if (!finite_in_range(params.angle_degrees, -max_brush_angle_degrees, max_brush_angle_degrees)) {
        return pp::foundation::Status::out_of_range("brush angle is outside the configured range");
    }

    if (!finite_in_range(params.size_jitter, 0.0F, 1.0F)) {
        return pp::foundation::Status::out_of_range("brush size jitter must be finite and within 0..1");
    }

    if (!finite_in_range(params.opacity_jitter, 0.0F, 1.0F)) {
        return pp::foundation::Status::out_of_range("brush opacity jitter must be finite and within 0..1");
    }

    return pp::foundation::Status::success();
}

BrushStamp evaluate_brush_stamp(const BrushParams& params, float pressure) noexcept
{
    const auto clamped_pressure = clamp01(std::isfinite(pressure) ? pressure : 0.0F);
    const auto size_pressure = params.pressure_controls_size ? clamped_pressure : 1.0F;
    const auto opacity_pressure = params.pressure_controls_opacity ? clamped_pressure : 1.0F;

    const auto jitter_size_scale = 1.0F - (params.size_jitter * 0.5F);
    const auto jitter_opacity_scale = 1.0F - (params.opacity_jitter * 0.5F);

    return BrushStamp {
        .size = std::max(min_brush_size, params.size * size_pressure * jitter_size_scale),
        .opacity = clamp01(params.opacity * opacity_pressure * jitter_opacity_scale),
        .flow = clamp01(params.flow),
        .angle_degrees = params.angle_degrees,
    };
}

}
