/*
 * Statically test the parsing physical units.
 *
 * Author: Malte J. Ziebarth (malte.ziebarth@tum.de)
 *
 * Copyright (C) 2026 Technische Universität München
 *
 * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 *
 * SPDX-FileCopyrightText: 2026 Technical University of Munich
 * SPDX-License-Identifier: EUPL-1.2
 * SPDX-FileContributor: Malte J. Ziebarth <malte.ziebarth@tum.de>
 */

#include <funktionsklempner/units.hpp>
#include <funktionsklempner/default_units.hpp>
#include <boost/units/io.hpp>


/*
 * Definitions and constants.
 */
using namespace funktionsklempner;
namespace si = boost::units::si;
namespace bu = boost::units;

/* From oeis.org/A000796, accessed on 2026-02-20 */
constexpr double pi = 3.1415926535897932384626433832795028841971694;


/*
 * Static tests.
 *
 * 1. constexpr integer parsing
 */
static_assert(funktionsklempner::unit::parse_int(u8"-31") == -31);
static_assert(funktionsklempner::unit::parse_int(u8"82981") == 82981);

/* 2. constexpr SI unit parsing. */
constexpr bu::quantity<si::length> cm
    = parse_si_unit<si::length>(u8"cm").value();
static_assert(
    math::abs((cm - 0.01 * si::meter) / si::meter) < 1e-16,
    "cm not equal to 0.01 m."
);

constexpr bu::quantity<si::force> dyne
    = parse_si_unit<si::force>(u8"dyn").value();

constexpr bu::quantity<si::force> newton
    = parse_si_unit<si::force>(u8"kg m s^-2").value();
static_assert(
    math::abs((dyne - 1e-5 * newton) / si::newtons) < 1e-16,
    "dyne not equal to 1e-5 N."
);


/*
 * Check equalities of units:
 */
template<typename D>
constexpr bool is_close(
    const bu::quantity<D>& q0,
    const char8_t* u1
)
{
    bu::quantity<D> q1 = parse_si_unit<D>(u1).value();
    return math::abs((q0 - q1) / std::max(math::abs(q0), math::abs(q1))) < 2e-16;
}

/* Check base unit identities: */
static_assert(is_close(1.0 * si::second,    u8"s"));
static_assert(is_close(1.0 * si::meter,     u8"m"));
static_assert(is_close(1.0 * si::kilogram,  u8"kg"));
static_assert(is_close(1.0 * si::ampere,    u8"A"));
static_assert(is_close(1.0 * si::kelvin,    u8"K"));
static_assert(is_close(1.0 * si::mole,      u8"mol"));
static_assert(is_close(1.0 * si::candela,   u8"cd"));
static_assert(is_close(1.0 * si::radian,    u8"rad"));
static_assert(is_close(1.0 * si::steradian, u8"sr"));


/* Derived units (table 4): */
static_assert(is_close(1.0 * si::hertz,     u8"Hz"));
static_assert(is_close(1.0 * si::newton,    u8"N"));
static_assert(is_close(1.0 * si::pascals,   u8"Pa"));
static_assert(is_close(1.0 * si::joule,     u8"J"));
static_assert(is_close(1.0 * si::watt,      u8"W"));
static_assert(is_close(1.0 * si::coulomb,   u8"C"));
static_assert(is_close(1.0 * si::volt,      u8"V"));
static_assert(is_close(1.0 * si::ohm,       u8"Ω"));
static_assert(is_close(1.0 * si::siemens,   u8"S"));
static_assert(is_close(1.0 * si::farad,     u8"F"));
static_assert(is_close(1.0 * si::henry,     u8"H"));
static_assert(is_close(1.0 * si::tesla,     u8"T"));
static_assert(is_close(1.0 * si::weber,     u8"Wb"));
static_assert(is_close(1.0 * si::lumen,     u8"lm"));
static_assert(is_close(1.0 * si::lux,       u8"lx"));
static_assert(is_close(1.0 * si::becquerel, u8"Bq"));
static_assert(is_close(1.0 * si::gray,      u8"Gy"));
static_assert(is_close(1.0 * si::sievert,   u8"Sv"));
static_assert(is_close(1.0 * si::katal,     u8"kat"));


/* Check parsing of unit components: */
static_assert(is_close(1.0 * si::newtons, u8"kg m s^-2"));
static_assert(is_close(1.0 * si::siemens, u8"Ω^-1"));


/* Check parsing of CGS units: */
static_assert(is_close(1e-2 * si::meter_per_second_squared,     u8"Gal"));
static_assert(is_close(1e-5 * si::newton,                       u8"dyn"));
static_assert(is_close(1e-7 * si::joule,                        u8"erg"));
static_assert(is_close(1e-1 * si::pascals,                      u8"Ba"));
static_assert(is_close(1e-1 * si::pascals * si::seconds,        u8"P"));
static_assert(is_close(1e-4 * si::meter_per_second * si::meter, u8"St"));


/* Non-SI units accepted for use with SI (table 8): */
static_assert(is_close(60.0 * si::seconds,               u8"min"));
static_assert(is_close(3600.0 * si::seconds,             u8"h"));
static_assert(is_close(86400.0 * si::seconds,            u8"d"));
static_assert(is_close(149597870700. * si::meter,        u8"au"));
static_assert(is_close(pi/180 * si::radian,              u8"°"));
static_assert(is_close(pi/(180*60) * si::radian,         u8"'"));
static_assert(is_close(pi/(180*3600) * si::radian,       u8"\""));
static_assert(is_close(1e4*si::meter * si::meter,        u8"ha"));
static_assert(is_close(1e-3 * si::cubic_meter,           u8"l"));
static_assert(is_close(1e-3 * si::cubic_meter,           u8"L"));
static_assert(is_close(1e3 * si::kilogram,               u8"t"));
static_assert(is_close(1.66053906892e-27 * si::kilogram, u8"Da"));
static_assert(is_close(1.602176634e-19 * si::joule,      u8"eV"));


/* Imperial & US units */
static_assert(is_close(0.9144 * si::meter,           u8"yd"));
static_assert(is_close(304.8e-3 * si::meter,         u8"ft"));
static_assert(is_close(25.4e-3 * si::meter,          u8"in"));
static_assert(is_close(0.45359237 * si::kilogram,    u8"lb"));
static_assert(is_close(0.45359237/16 * si::kilogram, u8"oz"));



/* Prefixes: */
static_assert(is_close(1e30 * si::meters,  u8"Qm"));
static_assert(is_close(1e27 * si::meters,  u8"Rm"));
static_assert(is_close(1e24 * si::meters,  u8"Ym"));
static_assert(is_close(1e21 * si::meters,  u8"Zm"));
static_assert(is_close(1e18 * si::meters,  u8"Em"));
static_assert(is_close(1e15 * si::meters,  u8"Pm"));
static_assert(is_close(1e12 * si::meters,  u8"Tm"));
static_assert(is_close(1e9 * si::meters,   u8"Gm"));
static_assert(is_close(1e6 * si::meters,   u8"Mm"));
static_assert(is_close(1e3 * si::meters,   u8"km"));
static_assert(is_close(1e2 * si::meters,   u8"hm"));
static_assert(is_close(1e1 * si::meters,   u8"dam"));
static_assert(is_close(1e-1 * si::meters,  u8"dm"));
static_assert(is_close(1e-2 * si::meters,  u8"cm"));
static_assert(is_close(1e-3 * si::meters,  u8"mm"));
static_assert(is_close(1e-6 * si::meters,  u8"µm"));
static_assert(is_close(1e-9 * si::meters,  u8"nm"));
static_assert(is_close(1e-12 * si::meters, u8"pm"));
static_assert(is_close(1e-15 * si::meters, u8"fm"));
static_assert(is_close(1e-18 * si::meters, u8"am"));
static_assert(is_close(1e-21 * si::meters, u8"zm"));
static_assert(is_close(1e-24 * si::meters, u8"ym"));
static_assert(is_close(1e-27 * si::meters, u8"rm"));
static_assert(is_close(1e-30 * si::meters, u8"qm"));

/* Here we check that the 'µ' prefix for the non-prefixable 'oz' throws
 * an error. Also make sure that 'µ' and 'oz' work independently.  */
static_assert(!parse_si_unit<si::mass>(u8"µoz"));
static_assert( parse_si_unit<si::mass>(u8"oz"));
static_assert( parse_si_unit<si::length>(u8"µm"));


/*
 * Here we check some compile-time generated unit strings:
 */
static_assert(std::u8string(default_unit<si::area>) == u8"m^2");
static_assert(
     std::u8string(default_unit<si::permeability>) == u8"m kg s^-2 A^-2"
);