// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// optimization_1d_impl.hpp — implementation of golden_section_search, brent_minimize

#ifndef SANGI_OPTIMIZATION_1D_IMPL_HPP
#define SANGI_OPTIMIZATION_1D_IMPL_HPP

#include <math/optimization/optimization_1d.hpp>

namespace sangi {

template<concepts::OrderedField T>
OptimizationResult1D<T> golden_section_search(
    const std::function<T(T)>& f,
    T a, T b,
    const OptimizationOptions<T>& options) {

    const T golden_ratio = (std::sqrt(T(5)) - 1) / 2;
    const T inv_golden_ratio = 1 - golden_ratio;

    // select two points within the initial interval
    T c = a + inv_golden_ratio * (b - a);
    T d = a + golden_ratio * (b - a);
    T fc = f(c);
    T fd = f(d);

    for (size_t i = 0; i < options.max_iterations; ++i) {
        if (std::abs(b - a) < options.tolerance) {
            // return the midpoint of the interval
            T x_min = (a + b) / 2;
            T f_min = f(x_min);
            return OptimizationResult1D<T>::makeSuccess(x_min, f_min, i);
        }

        if (fc < fd) {
            b = d;
            d = c;
            fd = fc;
            c = a + inv_golden_ratio * (b - a);
            fc = f(c);
        }
        else {
            a = c;
            c = d;
            fc = fd;
            d = a + golden_ratio * (b - a);
            fd = f(d);
        }
    }

    // if the maximum number of iterations is reached, return the current best point
    T x_min = (fc < fd) ? c : d;
    T f_min = (fc < fd) ? fc : fd;
    return OptimizationResult1D<T>::makeFailure(x_min, f_min, options.max_iterations);
}

template<concepts::OrderedField T>
OptimizationResult1D<T> brent_minimize(
    const std::function<T(T)>& f,
    T a, T b,
    const OptimizationOptions<T>& options) {

    const T golden_ratio = (3 - std::sqrt(T(5))) / 2;  // ≈ 0.3819...

    // initialization
    T x = (a + b) / 2;
    T v = x;
    T w = x;
    T fx = f(x);
    T fv = fx;
    T fw = fx;

    T d = 0, e = 0;

    for (size_t i = 0; i < options.max_iterations; ++i) {
        T midpoint = (a + b) / 2;
        T tol = std::sqrt(std::numeric_limits<T>::epsilon()) * std::abs(x) + options.tolerance;

        // convergence check
        if (std::abs(x - midpoint) <= 2 * tol - (b - a) / 2) {
            return OptimizationResult1D<T>::makeSuccess(x, fx, i);
        }

        // prepare for parabolic interpolation
        bool do_golden_section = true;

        if (std::abs(e) > tol) {
            // attempt parabolic interpolation
            T r = (x - w) * (fx - fv);
            T q = (x - v) * (fx - fw);
            T p = (x - v) * q - (x - w) * r;
            q = 2 * (q - r);

            if (q > 0) {
                p = -p;
            }
            else {
                q = -q;
            }

            T old_e = e;
            e = d;

            // check whether parabolic interpolation is valid
            if (std::abs(p) < std::abs(q * old_e / 2) && p > q * (a - x) && p < q * (b - x)) {
                // use parabolic interpolation
                d = p / q;
                T u = x + d;

                // ensure the update point is not too close to the boundary or the previous point
                if (u - a < 2 * tol || b - u < 2 * tol || std::abs(u - x) >= std::abs(old_e) / 2) {
                    d = (x < midpoint) ? tol : -tol;
                }
                else {
                    do_golden_section = false;
                }
            }
        }

        // use the golden-section search when parabolic interpolation cannot be used
        if (do_golden_section) {
            e = (x < midpoint) ? b - x : a - x;
            d = golden_ratio * e;
        }

        // compute the update point
        T u = x + ((std::abs(d) >= tol) ? d : ((d > 0) ? tol : -tol));
        T fu = f(u);

        // update the points
        if (fu <= fx) {
            if (u < x) {
                b = x;
            }
            else {
                a = x;
            }
            v = w;
            w = x;
            x = u;
            fv = fw;
            fw = fx;
            fx = fu;
        }
        else {
            if (u < x) {
                a = u;
            }
            else {
                b = u;
            }

            if (fu <= fw || w == x) {
                v = w;
                w = u;
                fv = fw;
                fw = fu;
            }
            else if (fu <= fv || v == x || v == w) {
                v = u;
                fv = fu;
            }
        }
    }

    // if the maximum number of iterations is reached
    return OptimizationResult1D<T>::makeFailure(x, fx, options.max_iterations);
}

} // namespace sangi

#endif // SANGI_OPTIMIZATION_1D_IMPL_HPP
