/*
 * Constexpr / non-constexpr math.
 *
 * Author: Malte J. Ziebarth (malte.ziebarth@tum.de)
 *
 * Copyright (C) 2026 Technische Universität München
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *
 *    1. Redistributions of source code must retain the above copyright notice, this
 *       list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * SPDX-FileCopyrightText: 2026 Technical University of Munich
 * SPDX-License-Identifier: BSD-3-Clause
 * SPDX-FileContributor: Malte J. Ziebarth <malte.ziebarth@tum.de>
 */

#ifndef FUNKTIONSKLEMPNER_MATH_HPP
#define FUNKTIONSKLEMPNER_MATH_HPP

#include <cmath>
#include <gcem.hpp>
#include <boost/units/quantity.hpp>

namespace funktionsklempner::math {

/*
 * Type used for representing precision:
 */
namespace _resulttype {

template<typename Real1, typename Real2>
struct result_type
{
    using type = double;
};

template<typename Real2>
struct result_type<long double, Real2>
{
    using type = long double;
};

template<typename Real1>
struct result_type<Real1, long double>
{
    using type = long double;
};

template<>
struct result_type<float,float>
{
    using type = float;
};

} // namespace _resulttype

template<typename Real1, typename Real2>
using result_type
    = typename _resulttype::result_type<Real1,Real2>::type;




template<typename Real1, typename Real2>
constexpr result_type<Real1,Real2> pow(Real1 base, Real2 exponent)
{
    if (std::is_constant_evaluated()){
        return gcem::pow(base, exponent);
    } else {
        return std::pow(base, exponent);
    }
}


template<typename Real>
constexpr Real exp(Real x)
{
    if (std::is_constant_evaluated()){
        return gcem::exp(x);
    } else {
        return std::exp(x);
    }
}

/*
 * For comparison with zero (which is a bit tricky with quantities):
 */
template<typename Real>
constexpr Real ZERO = 0;

template<typename U>
constexpr boost::units::quantity<U> ZERO<boost::units::quantity<U>>
    = boost::units::quantity<U>::from_value(0.0);


template<typename Real>
constexpr Real abs(Real x)
{
    if (x > ZERO<Real>)
        return x;
    return -x;
}


/*
 * An implementation of 10^x with look-up table support.
 */
namespace _pow10 {
    /* Size of the look-up table (has to be odd!) */
    constexpr size_t N_LUP = 61;
        static_assert(
            N_LUP % 2 == 1, "Exponent to scale look-up table has to be of odd size."
        );
    constexpr static std::array<double, N_LUP> lup{
        1e-30, 1e-29, 1e-28, 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21,
        1e-20, 1e-19, 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11,
        1e-10,  1e-9,  1e-8,  1e-7,  1e-6,  1e-5,  1e-4,  1e-3,  1e-2,  1e-1,
        1.0,
         1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9, 1e10,
        1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20,
        1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30
    };
}

constexpr double pow10(int exponent)
{
    constexpr int max_lup_exp = (_pow10::N_LUP - 1) / 2;
    if (exponent >= -max_lup_exp && exponent <= max_lup_exp)
        return _pow10::lup[exponent + max_lup_exp];

    return pow(10.0, exponent);

}



}


#endif