const std = @import("../std.zig");
const math = std.math;
const expect = std.testing.expect;
pub fn atan(x: anytype) @TypeOf(x) {
    const T = @TypeOf(x);
    return switch (T) {
        f32 => atan32(x),
        f64 => atan64(x),
        else => @compileError("atan not implemented for " ++ @typeName(T)),
    };
}
fn atan32(x_: f32) f32 {
    const atanhi = [_]f32{
        4.6364760399e-01, 
        7.8539812565e-01, 
        9.8279368877e-01, 
        1.5707962513e+00, 
    };
    const atanlo = [_]f32{
        5.0121582440e-09, 
        3.7748947079e-08, 
        3.4473217170e-08, 
        7.5497894159e-08, 
    };
    const aT = [_]f32{
        3.3333328366e-01,
        -1.9999158382e-01,
        1.4253635705e-01,
        -1.0648017377e-01,
        6.1687607318e-02,
    };
    var x = x_;
    var ix: u32 = @bitCast(u32, x);
    const sign = ix >> 31;
    ix &= 0x7FFFFFFF;
    
    if (ix >= 0x4C800000) {
        if (math.isNan(x)) {
            return x;
        } else {
            const z = atanhi[3] + 0x1.0p-120;
            return if (sign != 0) -z else z;
        }
    }
    var id: ?usize = undefined;
    
    if (ix < 0x3EE00000) {
        
        if (ix < 0x39800000) {
            if (ix < 0x00800000) {
                math.doNotOptimizeAway(x * x);
            }
            return x;
        }
        id = null;
    } else {
        x = @fabs(x);
        
        if (ix < 0x3F980000) {
            
            if (ix < 0x3F300000) {
                id = 0;
                x = (2.0 * x - 1.0) / (2.0 + x);
            }
            
            else {
                id = 1;
                x = (x - 1.0) / (x + 1.0);
            }
        } else {
            
            if (ix < 0x401C0000) {
                id = 2;
                x = (x - 1.5) / (1.0 + 1.5 * x);
            }
            
            else {
                id = 3;
                x = -1.0 / x;
            }
        }
    }
    const z = x * x;
    const w = z * z;
    const s1 = z * (aT[0] + w * (aT[2] + w * aT[4]));
    const s2 = w * (aT[1] + w * aT[3]);
    if (id) |id_value| {
        const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x);
        return if (sign != 0) -zz else zz;
    } else {
        return x - x * (s1 + s2);
    }
}
fn atan64(x_: f64) f64 {
    const atanhi = [_]f64{
        4.63647609000806093515e-01, 
        7.85398163397448278999e-01, 
        9.82793723247329054082e-01, 
        1.57079632679489655800e+00, 
    };
    const atanlo = [_]f64{
        2.26987774529616870924e-17, 
        3.06161699786838301793e-17, 
        1.39033110312309984516e-17, 
        6.12323399573676603587e-17, 
    };
    const aT = [_]f64{
        3.33333333333329318027e-01,
        -1.99999999998764832476e-01,
        1.42857142725034663711e-01,
        -1.11111104054623557880e-01,
        9.09088713343650656196e-02,
        -7.69187620504482999495e-02,
        6.66107313738753120669e-02,
        -5.83357013379057348645e-02,
        4.97687799461593236017e-02,
        -3.65315727442169155270e-02,
        1.62858201153657823623e-02,
    };
    var x = x_;
    var ux = @bitCast(u64, x);
    var ix = @intCast(u32, ux >> 32);
    const sign = ix >> 31;
    ix &= 0x7FFFFFFF;
    
    if (ix >= 0x44100000) {
        if (math.isNan(x)) {
            return x;
        } else {
            const z = atanhi[3] + 0x1.0p-120;
            return if (sign != 0) -z else z;
        }
    }
    var id: ?usize = undefined;
    
    if (ix < 0x3DFC0000) {
        
        if (ix < 0x3E400000) {
            if (ix < 0x00100000) {
                math.doNotOptimizeAway(@floatCast(f32, x));
            }
            return x;
        }
        id = null;
    } else {
        x = @fabs(x);
        
        if (ix < 0x3FF30000) {
            
            if (ix < 0x3FE60000) {
                id = 0;
                x = (2.0 * x - 1.0) / (2.0 + x);
            }
            
            else {
                id = 1;
                x = (x - 1.0) / (x + 1.0);
            }
        } else {
            
            if (ix < 0x40038000) {
                id = 2;
                x = (x - 1.5) / (1.0 + 1.5 * x);
            }
            
            else {
                id = 3;
                x = -1.0 / x;
            }
        }
    }
    const z = x * x;
    const w = z * z;
    const s1 = z * (aT[0] + w * (aT[2] + w * (aT[4] + w * (aT[6] + w * (aT[8] + w * aT[10])))));
    const s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * aT[9]))));
    if (id) |id_value| {
        const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x);
        return if (sign != 0) -zz else zz;
    } else {
        return x - x * (s1 + s2);
    }
}
test "math.atan" {
    try expect(@bitCast(u32, atan(@as(f32, 0.2))) == @bitCast(u32, atan32(0.2)));
    try expect(atan(@as(f64, 0.2)) == atan64(0.2));
}
test "math.atan32" {
    const epsilon = 0.000001;
    try expect(math.approxEqAbs(f32, atan32(0.2), 0.197396, epsilon));
    try expect(math.approxEqAbs(f32, atan32(-0.2), -0.197396, epsilon));
    try expect(math.approxEqAbs(f32, atan32(0.3434), 0.330783, epsilon));
    try expect(math.approxEqAbs(f32, atan32(0.8923), 0.728545, epsilon));
    try expect(math.approxEqAbs(f32, atan32(1.5), 0.982794, epsilon));
}
test "math.atan64" {
    const epsilon = 0.000001;
    try expect(math.approxEqAbs(f64, atan64(0.2), 0.197396, epsilon));
    try expect(math.approxEqAbs(f64, atan64(-0.2), -0.197396, epsilon));
    try expect(math.approxEqAbs(f64, atan64(0.3434), 0.330783, epsilon));
    try expect(math.approxEqAbs(f64, atan64(0.8923), 0.728545, epsilon));
    try expect(math.approxEqAbs(f64, atan64(1.5), 0.982794, epsilon));
}
test "math.atan32.special" {
    const epsilon = 0.000001;
    try expect(atan32(0.0) == 0.0);
    try expect(atan32(-0.0) == -0.0);
    try expect(math.approxEqAbs(f32, atan32(math.inf(f32)), math.pi / 2.0, epsilon));
    try expect(math.approxEqAbs(f32, atan32(-math.inf(f32)), -math.pi / 2.0, epsilon));
}
test "math.atan64.special" {
    const epsilon = 0.000001;
    try expect(atan64(0.0) == 0.0);
    try expect(atan64(-0.0) == -0.0);
    try expect(math.approxEqAbs(f64, atan64(math.inf(f64)), math.pi / 2.0, epsilon));
    try expect(math.approxEqAbs(f64, atan64(-math.inf(f64)), -math.pi / 2.0, epsilon));
}