# This file is part of the IntervalArithmetic.jl package; MIT licensed

# methods with no rounding required
-(a::AbstractFloat, ::RoundingMode) = -a
abs(a::Float64, ::RoundingMode) = abs(a)

# tightly rounded methods from FastRounding.jl
for (f, ff) in zip((:+, :-, :*, :/), (:add, :sub, :mul, :div))
    ff = Symbol(ff, "_round")
    for T in (Float32, Float64)
        @eval $f(a::$T, b::$T, r::RoundingMode) = $ff(a, b, r)
    end
end

for f in (:sqrt, :inv), T in (Float32, Float64)
    ff = Symbol(f, "_round")
    @eval $f(a::$T, r::RoundingMode) = $ff(a, r)
end

# for these functions use CRlibm
for f in (:exp, :expm1, :log, :log1p, :log2, :log10, :sin, :cos, :tan, :asin,
          :acos, :atan, :sinh, :cosh, :sinpi, :cospi, :tanpi, :atanpi)
    @eval $f(a::Float64, r::RoundingMode) = CRlibm.$f(a, r)
end

atan_inner(y, x, r) = atan(/(y, x, r), r)

function atan(y::Float64, x::Float64, r::RoundingMode)
    if x > 0
        return atan_inner(y, x, r)
    elseif x < 0
        if ifelse(r == RoundUp, y >= 0, y > 0)
            p = pi_interval(Float64)
            return atan_inner(y, x, r) + ifelse(r == RoundUp, p.hi, p.lo)
        else
            p = pi_interval(Float64)
            return atan_inner(y, x, r) + ifelse(r == RoundUp, -p.lo, -p.hi)
        end
    else
        if y > 0
            p2 = half_pi(Float64)
            return ifelse(r == RoundUp, p2.hi, p2.lo)
        elseif y < 0
            p2 = half_pi(Float64)
            return ifelse(r == RoundUp, -p2.lo, -p2.hi)
        else
            return ifelse(r == RoundUp, zero(Float64), -zero(Float64))
        end
    end
end

function tan(x::Float64, ::RoundingMode{:FromZero})
    val_u = tan(x, RoundUp)
    val_d = tan(x, RoundDown)
    return ifelse(abs(val_u) < abs(val_d), val_d, val_u)
end

cot(x::Float64, r::RoundingMode{:Up}) = inv(tan(x, RoundToZero), r)
cot(x::Float64, r::RoundingMode{:Down}) = inv(tan(x, RoundFromZero), r)

# operations for BigFloat
for f in (:exp, :expm1, :log, :log1p, :log2, :log10, :sin, :cos, :tan, :asin,
          :acos, :atan, :sinh, :cosh, :sinpi, :cospi, :tanpi, :atanpi, :inv,
          :sqrt, :-, :tanh, :asinh, :acosh, :atanh, :cot, :cbrt, :exp2, :exp10)
    @eval function $f(a::BigFloat, r::RoundingMode)
        setrounding(BigFloat, r) do
            $f(a)
        end
    end
end

for f in (:+, :-, :*, :/, :atan, :^)
    @eval function $f(a::BigFloat, b::Real, r::RoundingMode)
        setrounding(BigFloat, r) do
            $f(a, b)
        end
    end
end
