// Pi calculation using Machin's formula
// pi/4 = 4*arctan(1/5) - arctan(1/239)

#import <Foundation/Foundation.h>
#include <gmp.h>

// Calculate arctan(1/x) using Taylor series with arbitrary precision
// arctan(1/x) = 1/x - 1/(3*x^3) + 1/(5*x^5) - ...
void arctan(mpz_t result, unsigned long x, unsigned long decimals) {
    mpz_t scale, term, x_squared, temp;
    mpz_init(scale);
    mpz_init(term);
    mpz_init(x_squared);
    mpz_init(temp);
    
    // scale = 10^(decimals + 10)
    mpz_ui_pow_ui(scale, 10, decimals + 10);
    
    // x_squared = x * x
    mpz_ui_pow_ui(x_squared, x, 2);
    
    // term = scale / x
    mpz_fdiv_q_ui(term, scale, x);
    
    // result = 0
    mpz_set_ui(result, 0);
    
    int sign = 1;
    unsigned long n = 1;
    
    while (mpz_cmp_ui(term, 0) > 0) {
        // result += sign * term / n
        mpz_fdiv_q_ui(temp, term, n);
        if (sign > 0) {
            mpz_add(result, result, temp);
        } else {
            mpz_sub(result, result, temp);
        }
        
        // term = term / x²
        mpz_fdiv_q(term, term, x_squared);
        
        n += 2;
        sign = -sign;
    }
    
    mpz_clear(scale);
    mpz_clear(term);
    mpz_clear(x_squared);
    mpz_clear(temp);
}

// Calculate pi using Machin's formula
NSString* calculatePi(int decimals) {
    if (decimals < 1) {
        decimals = 100;
    }
    
    mpz_t arctan5, arctan239, pi;
    mpz_init(arctan5);
    mpz_init(arctan239);
    mpz_init(pi);
    
    // Machin's formula: pi/4 = 4*arctan(1/5) - arctan(1/239)
    arctan(arctan5, 5, decimals);
    arctan(arctan239, 239, decimals);
    
    // pi = 16*arctan(1/5) - 4*arctan(1/239)
    mpz_mul_ui(arctan5, arctan5, 16);
    mpz_mul_ui(arctan239, arctan239, 4);
    mpz_sub(pi, arctan5, arctan239);
    
    // Convert to string
    char* piStr = mpz_get_str(NULL, 10, pi);
    NSString* result = [NSString stringWithUTF8String:piStr];
    free(piStr);
    
    // Format output
    if (decimals == 0) {
        return @"3";
    }
    
    // Pad with zeros if needed
    NSString* padded = result;
    if ([result length] < decimals + 1) {
        NSUInteger zerosNeeded = decimals + 1 - [result length];
        padded = [@"0" stringByPaddingToLength:zerosNeeded withString:@"0" startingAtIndex:0];
        padded = [padded stringByAppendingString:result];
    }
    
    // Insert decimal point
    NSString* beforeDecimal = [padded substringWithRange:NSMakeRange(0, 1)];
    NSString* afterDecimal = [padded substringWithRange:NSMakeRange(1, [padded length] - 1)];
    
    // Pad or truncate to desired length
    if ([afterDecimal length] < decimals) {
        afterDecimal = [afterDecimal stringByPaddingToLength:decimals withString:@"0" startingAtIndex:0];
    } else {
        afterDecimal = [afterDecimal substringWithRange:NSMakeRange(0, decimals)];
    }
    
    mpz_clear(arctan5);
    mpz_clear(arctan239);
    mpz_clear(pi);
    
    return [NSString stringWithFormat:@"%@.%@", beforeDecimal, afterDecimal];
}

int main(int argc, const char* argv[]) {
    @autoreleasepool {
        int decimals = 100;
        
        if (argc > 1) {
            decimals = atoi(argv[1]);
            if (decimals < 1) {
                decimals = 100;
            }
        }
        
        NSString* pi = calculatePi(decimals);
        printf("%s\n", [pi UTF8String]);
    }
    return 0;
}