#include "integrate.h"

static inline int bad_args(integrand_f f, float *a, float *b, unsigned *n,
                           float *out)
{
    return (f == NULL || out == NULL || *n == 0u || !(*b > *a));
}

integ_status_t midpoint(integrand_f f, void *ctx, float a, float b, unsigned n,
                        float *out)
{
    /*check if all necessary parameters have been given */
    if (bad_args(f, &a, &b, &n, out))
        return INTEG_ERR_BAD_ARGS;

    float sum = 0.0f;
    float fx = 0.0f;

    /*intervall width h: */
    const float h = (b - a) / (float)n;
    float x = a + 0.5f * h;

    for (unsigned i = 0; i < n; ++i)
    {
        fx = f(x, ctx);
        sum = sum + fx;
        x = x + h;
    }

    *out = sum * h;
    return INTEG_OK;
}

integ_status_t trapezoid(integrand_f f, void *ctx, float a, float b, unsigned n,
                         float *out)
{
    /*check if all necessary parameters have been given */
    if (bad_args(f, &a, &b, &n, out))
    {
        return INTEG_ERR_BAD_ARGS;
    }

    /*intervall width h: */
    const float h = (b - a) / (float)n;
    float sum = 0.5f * (f(a, ctx) + f(b, ctx));

    float x = a + h;
    for (unsigned i = 1; i < n; ++i)
    {
        sum = sum + f(x, ctx);
        x = x + h;
    }

    *out = sum * h;
    return INTEG_OK;
}

integ_status_t simpson(integrand_f f, void *ctx, float a, float b, unsigned n,
                       float *out)
{
    /*check if all necessary parameters have been given */
    if (bad_args(f, &a, &b, &n, out))
    {
        return INTEG_ERR_BAD_ARGS;
    }
    if (n & 1u) /*n must be even*/
    {
        return INTEG_ERR_N_EVEN_REQUIRED;
    }

    float sum_odd = 0.0f;
    float sum_even = 0.0f;
    /*intervall width h: */
    const float h = (b - a) / (float)n;

    float x = a + h;
    for (unsigned i = 1; i < n; ++i)
    {
        const float fx = f(x, ctx);
        if (i & 1u)
            sum_odd = sum_odd + fx;
        else
            sum_even = sum_even + fx;
        x = x + h;
    }

    *out =
        (h / 3.0f) * (f(a, ctx) + 4.0f * sum_odd + 2.0f * sum_even + f(b, ctx));
    return INTEG_OK;
}
