/*
 *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
#pragma once

#include <utility> // std::swap
#include <cstdlib> // std::abs
#include <iostream>
#include <cmath>      // std::fmod
#include <functional> // std::modulus

#include <fmt/format.h>

extern "C" {
#include <libavutil/rational.h> // specify conversions for AVRational
}

namespace jami {

/**
 * Naive implementation of the boost::rational interface, described here:
 * https://www.boost.org/doc/libs/latest/libs/rational/rational.html
 */
template<typename I>
class rational
{
public:
    // Zero
    constexpr rational() {}

    // Equal to n/1
    constexpr rational(I n)
        : num_(n)
    {}

    // General case (n/d)
    constexpr rational(I n, I d)
        : num_(n)
        , den_(d)
    {
        reduce();
    }

    // Define conversions to and from AVRational (equivalent)
    constexpr rational(AVRational r)
        : num_(r.num)
        , den_(r.den) {};
    constexpr operator AVRational() const { return AVRational {(int) num_, (int) den_}; }

    std::string to_string() const { return fmt::format("{}/{}", num_, den_); }

    // Normal copy constructors and assignment operators

    // Assignment from I
    rational& operator=(I n)
    {
        num_ = n;
        den_ = 1;
        return *this;
    }

    // Assign in place
    rational& assign(I n, I d)
    {
        num_ = n;
        den_ = d;
        reduce();
        return *this;
    }

    // Representation
    constexpr I numerator() const { return num_; };
    constexpr I denominator() const { return den_; };

    template<typename R = double>
    constexpr R real() const
    {
        return num_ / (R) den_;
    }

    // Arithmetic operators
    constexpr rational operator+(const rational& r) const { return {num_ * r.den_ + r.num_ * den_, den_ * r.den_}; }
    constexpr rational operator-(const rational& r) const { return {num_ * r.den_ - r.num_ * den_, den_ * r.den_}; }
    constexpr rational operator*(const rational& r) const { return {num_ * r.num_, den_ * r.den_}; }
    constexpr rational operator/(const rational& r) const { return {num_ * r.den_, den_ * r.num_}; }

    constexpr rational& operator+=(const rational& r)
    {
        std::swap(*this, *this + r);
        return *this;
    }
    constexpr rational& operator-=(const rational& r)
    {
        std::swap(*this, *this - r);
        return *this;
    }
    constexpr rational& operator*=(const rational& r)
    {
        num_ *= r.num_;
        den_ *= r.den_;
        reduce();
        return *this;
    }
    constexpr rational& operator/=(const rational& r)
    {
        num_ *= r.den_;
        den_ *= r.num_;
        reduce();
        return *this;
    }

    // Arithmetic with integers
    constexpr rational& operator+=(I i)
    {
        num_ += i * den_;
        return *this;
    }
    constexpr rational& operator-=(I i)
    {
        num_ -= i * den_;
        return *this;
    }
    constexpr rational& operator*=(I i)
    {
        num_ *= i;
        reduce();
        return *this;
    }
    constexpr rational& operator/=(I i)
    {
        den_ *= i;
        reduce();
        return *this;
    }

    // Increment and decrement
    constexpr const rational& operator++()
    {
        num_ += den_;
        return *this;
    }
    constexpr const rational& operator--()
    {
        num_ -= den_;
        return *this;
    }

    // Operator not
    constexpr bool operator!() const { return !num_; };

    // Boolean conversion
    explicit constexpr operator bool() const { return num_; }

    // Comparison operators
    constexpr bool operator<(const rational& r) const
    {
        bool inv = (den_ > 0) != (r.den_ > 0);
        return inv != (num_ * r.den_ < den_ * r.num_);
    }
    constexpr bool operator>(const rational& r) const
    {
        bool inv = (den_ > 0) != (r.den_ > 0);
        return inv != (num_ * r.den_ > den_ * r.num_);
    }
    constexpr bool operator==(const rational& r) const { return num_ * r.den_ == den_ * r.num_; }
    constexpr bool operator!=(const rational& r) const { return num_ * r.den_ != den_ * r.num_; }

    // Comparison with integers
    constexpr bool operator<(I i) const { return den_ < 0 ? (num_ > i * den_) : (num_ < i * den_); }
    constexpr bool operator>(I i) const { return den_ < 0 ? (num_ < i * den_) : (num_ > i * den_); }
    constexpr bool operator==(I i) const { return num_ == i * den_; }
    constexpr bool operator!=(I i) const { return num_ != i * den_; }

private:
    I num_ {0};
    I den_ {1};

    static constexpr I gcd(I a, I b) { return b == (I) 0 ? a : gcd(b, std::modulus<I>()(a, b)); }
    constexpr void reduce()
    {
        if constexpr (std::is_integral<I>::value) {
            if (num_ and den_) {
                auto g = gcd(num_ >= 0 ? num_ : -num_, den_ >= 0 ? den_ : -den_);
                if (g > (I) 1) {
                    num_ /= g;
                    den_ /= g;
                }
            }
        }
    }
};

// Unary operators
template<typename I>
rational<I>
operator+(const rational<I>& r)
{
    return r;
}
template<typename I>
rational<I>
operator-(const rational<I>& r)
{
    return {-r.numerator(), r.denominator()};
}

// Reversed order operators for - and / between (types convertible to) I and rational
template<typename I, typename II>
inline rational<I> operator-(II i, const rational<I>& r);
template<typename I, typename II>
inline rational<I>
operator/(II i, const rational<I>& r)
{
    return {i * r.denominator(), r.numerator()};
}

// Absolute value
template<typename I>
rational<I>
abs(const rational<I>& r)
{
    return {std::abs(r.numerator()), std::abs(r.denominator())};
}

// Input and output
template<typename I>
std::istream&
operator>>(std::istream& is, rational<I>& r)
{
    char sep;
    is >> r.num_ >> sep >> r.den_;
    return is;
}

template<typename I>
std::ostream&
operator<<(std::ostream& os, const rational<I>& r)
{
    os << r.numerator() << '/' << r.denominator();
    return os;
}

// Type conversion
template<typename T, typename I>
T rational_cast(const rational<I>& r);

} // namespace jami

namespace std {
template<>
struct modulus<double>
{
    double operator()(const double& lhs, const double& rhs) const { return std::fmod(lhs, rhs); }
};
} // namespace std

template<typename I>
struct fmt::formatter<jami::rational<I>> : fmt::formatter<std::string_view>
{
    template<typename FormatContext>
    auto format(const jami::rational<I>& r, FormatContext& ctx) const -> decltype(ctx.out())
    {
        return fmt::formatter<std::string_view>::format(r.to_string(), ctx);
    }
};
