/*
 * Compile-time output formatting of default units.
 *
 * 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_NO_BOOST

#ifndef FUNKTIONSKLEMPNER_DEFAULT_UNITS_HPP
#define FUNKTIONSKLEMPNER_DEFAULT_UNITS_HPP

#include <funktionsklempner/units.hpp>

namespace funktionsklempner {


namespace _default_unit {
/*
 * Express default unit.
 */
constexpr std::u8string
to_u8string(int i)
{
    std::u8string res;
    if (i < 0){
        res.push_back(u8'-');
        i = -i;
    }
    constexpr std::array<char8_t, 10> digits {
        u8'0', u8'1', u8'2', u8'3', u8'4',
        u8'5', u8'6', u8'7', u8'8', u8'9'
    };
    while (i > 0){
        res.push_back(digits[i % 10]);
        i /= 10;
    }
    return res;
}



template<Unit U, typename F>
constexpr size_t compute_default_unit(F&& fun)
{
    /* The array representation of the unit: */
    std::array<int, unit::N_UNIT> exponents = unit::array_representation<U>();

    /* Nonzero unit with the corresponding exponent sign: */
    std::vector<std::pair<size_t, signed char>> nonzero;
    for (size_t i=0; i<unit::N_UNIT; ++i){
        int e = exponents[i];
        if (e > 0){
            nonzero.push_back(
                std::make_pair(
                    i,
                    static_cast<signed char>(1)
                )
            );
        } else if (e < 0){
            nonzero.push_back(
                std::make_pair(
                    i,
                    static_cast<signed char>(-1)
                )
            );
        }
    }

    if (nonzero.empty()){
        fun(u8"1");
        return 1;
    }

    /* Sort the nonzero units as follows:
     * - first all positive exponents,
     * - then according to SI order. */
    std::sort(
        nonzero.begin(),
        nonzero.end(),
        [](auto& l, auto& r) -> bool
        {
            bool rpos = r.second < 0;
            if (rpos != (l.second < 0))
                return rpos;
            return l.first < r.first;
        }
    );

    /* Unit index to base string: */
    using unit::_array_representation::dimension_index;
    std::array<const char8_t*, unit::N_UNIT> u2s;
    u2s[dimension_index<boost::units::time_base_dimension>] = u8"s";
    u2s[dimension_index<boost::units::length_base_dimension>] = u8"m";
    u2s[dimension_index<boost::units::mass_base_dimension>] = u8"kg";
    u2s[dimension_index<boost::units::current_base_dimension>] = u8"A";
    u2s[dimension_index<boost::units::temperature_base_dimension>] = u8"K";
    u2s[dimension_index<boost::units::amount_base_dimension>] = u8"mol";
    u2s[dimension_index<boost::units::luminous_intensity_base_dimension>] = u8"cd";
    u2s[dimension_index<boost::units::plane_angle_base_dimension>] = u8"rad";
    u2s[dimension_index<boost::units::solid_angle_base_dimension>] = u8"sr";

    /* The string we will fill up: */
    std::u8string res;

    for (const auto& nz : nonzero){
        if (!res.empty())
            res += u8" ";
        res += u2s[nz.first];
        if (exponents[nz.first] != 1){
            res += u8"^";
            res += to_u8string(exponents[nz.first]);
        }
    }

    fun(res);

    return res.size();
}


template<Unit U>
struct UnitString
{
    constexpr static size_t unit_size
        = compute_default_unit<U>([](const std::u8string&){});

    constexpr static std::array<char8_t, unit_size+1> compute_unit_array()
    {
        std::array<char8_t, unit_size+1> res{};
        compute_default_unit<U>(
            [&res](const std::u8string& s)
            {
                for (size_t i=0; i<unit_size; ++i)
                    res[i] = s[i];
            }
        );

        return res;
    }

    constexpr static std::array<char8_t, unit_size+1> unit_array = compute_unit_array();
    constexpr static const char8_t* unit = unit_array.data();
};



} // namespace _default_unit


/*
 * Default units.
 */
template<Unit U>
constexpr inline const char8_t* default_unit = _default_unit::UnitString<U>::unit;

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::time
> = u8"s";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::length
> = u8"m";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::mass
> = u8"kg";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::current
> = u8"A";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::temperature
> = u8"K";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::amount
> = u8"mol";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::luminous_intensity
> = u8"cd";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::plane_angle
> = u8"rad";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::solid_angle
> = u8"sr";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::frequency
> = u8"Hz";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::force
> = u8"N";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::pressure
> = u8"Pa";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::energy
> = u8"J";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::power
> = u8"W";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::electric_charge
> = u8"C";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::electric_potential
> = u8"V";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::resistance
> = u8"Ω";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::conductance
> = u8"S";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::capacitance
> = u8"F";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::inductance
> = u8"H";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::magnetic_flux_density
> = u8"T";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::magnetic_flux
> = u8"Wb";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::luminous_flux
> = u8"lm";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::illuminance
> = u8"lx";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::dose_equivalent
> = u8"Sv";

template<>
constexpr inline const char8_t* default_unit<
    boost::units::si::catalytic_activity
> = u8"kat";


} // end namespace


#endif
#endif