const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;

pub fn atan(z: var) @typeOf(z) {
    const T = @typeOf(z.re);
    return switch (T) {
        f32 => atan32(z),
        f64 => atan64(z),
        else => @compileError("atan not implemented for " ++ @typeName(z)),
    };
}

fn redupif32(x: f32) f32 {
    const DP1 = 3.140625;
    const DP2 = 9.67502593994140625e-4;
    const DP3 = 1.509957990978376432e-7;

    var t = x / math.pi;
    if (t >= 0.0) {
        t += 0.5;
    } else {
        t -= 0.5;
    }

    const u = @intToFloat(f32, @floatToInt(i32, t));
    return ((x - u * DP1) - u * DP2) - t * DP3;
}

fn atan32(z: Complex(f32)) Complex(f32) {
    const maxnum = 1.0e38;

    const x = z.re;
    const y = z.im;

    if ((x == 0.0) and (y > 1.0)) {
        // overflow
        return Complex(f32).new(maxnum, maxnum);
    }

    const x2 = x * x;
    var a = 1.0 - x2 - (y * y);
    if (a == 0.0) {
        // overflow
        return Complex(f32).new(maxnum, maxnum);
    }

    var t = 0.5 * math.atan2(f32, 2.0 * x, a);
    var w = redupif32(t);

    t = y - 1.0;
    a = x2 + t * t;
    if (a == 0.0) {
        // overflow
        return Complex(f32).new(maxnum, maxnum);
    }

    t = y + 1.0;
    a = (x2 + (t * t)) / a;
    return Complex(f32).new(w, 0.25 * math.ln(a));
}

fn redupif64(x: f64) f64 {
    const DP1 = 3.14159265160560607910;
    const DP2 = 1.98418714791870343106e-9;
    const DP3 = 1.14423774522196636802e-17;

    var t = x / math.pi;
    if (t >= 0.0) {
        t += 0.5;
    } else {
        t -= 0.5;
    }

    const u = @intToFloat(f64, @floatToInt(i64, t));
    return ((x - u * DP1) - u * DP2) - t * DP3;
}

fn atan64(z: Complex(f64)) Complex(f64) {
    const maxnum = 1.0e308;

    const x = z.re;
    const y = z.im;

    if ((x == 0.0) and (y > 1.0)) {
        // overflow
        return Complex(f64).new(maxnum, maxnum);
    }

    const x2 = x * x;
    var a = 1.0 - x2 - (y * y);
    if (a == 0.0) {
        // overflow
        return Complex(f64).new(maxnum, maxnum);
    }

    var t = 0.5 * math.atan2(f64, 2.0 * x, a);
    var w = redupif64(t);

    t = y - 1.0;
    a = x2 + t * t;
    if (a == 0.0) {
        // overflow
        return Complex(f64).new(maxnum, maxnum);
    }

    t = y + 1.0;
    a = (x2 + (t * t)) / a;
    return Complex(f64).new(w, 0.25 * math.ln(a));
}

const epsilon = 0.0001;

test "complex.catan32" {
    const a = Complex(f32).new(5, 3);
    const c = atan(a);

    debug.assert(math.approxEq(f32, c.re, 1.423679, epsilon));
    debug.assert(math.approxEq(f32, c.im, 0.086569, epsilon));
}

test "complex.catan64" {
    const a = Complex(f64).new(5, 3);
    const c = atan(a);

    debug.assert(math.approxEq(f64, c.re, 1.423679, epsilon));
    debug.assert(math.approxEq(f64, c.im, 0.086569, epsilon));
}
