#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "newton_raphson.h"
#include "bisection.h"
#include "regula_falsi.h"

#define EPSILON 1e-11
#define MAX_STEPS 100000

/*
 * Manually define the functions and their derivatives.
 */

double f1(double x) {
    return x * x - x + 2;
}

double df1(double x) {
    return 2 * x - 1;
}

double f2(double x) {
    return x * x * x - 3 * x - 2;
}

double df2(double x) {
    return 3 * x * x - 3;
}

double f3(double x) {
    return (x * x + 1) * (x - 4);
}

double df3(double x) {
    return (x * x + 1) + 2 * x * (x - 4);
}

#define PRINT_ZERO(f, df, x_start) { \
    steps = start_steps; \
    if( !isnan(root = newton_raphson(&f, &df, x_start, EPSILON, &steps, MAX_STEPS)) ) \
        printf(#f ": %.11f (%d steps from start point %.11f)\n", root, steps, (double)x_start); \
    else \
        printf(#f ": could not find a root after %d steps\n", steps); \
}

#define PRINT_ZERO_RANGE(f, df, method, lnbd, ubnd) { \
    start_steps = 0; \
    x_start = method(&f, lnbd, ubnd, EPSILON, &start_steps, max_start_steps); \
    printf("Did first %d steps using " #method "\n", start_steps); \
    PRINT_ZERO(f, df, x_start); \
}

int main(int argc, char *argv[]) {
    unsigned int steps, start_steps = 0, max_start_steps;
    double root, x_start;

    if( argc != 2 ) {
        printf("Usage: %s START_STEPS\n", argv[0]);
        return 1;
    }

    max_start_steps = atoi(argv[1]);

    PRINT_ZERO(f1, df1, 0);
    PRINT_ZERO(f2, df2, -10);
    PRINT_ZERO(f2, df2, 10);
    PRINT_ZERO(f3, df3, 0);

    printf("\nNow using Bisection to determine the first %d steps.\n\n", max_start_steps);

    PRINT_ZERO_RANGE(f2, df2, bisec, -10, 0);
    PRINT_ZERO_RANGE(f2, df2, bisec, 0, 10);
    PRINT_ZERO_RANGE(f3, df3, bisec, 0, 10);

    printf("\nNow using Regula Falsi to determine the first %d steps.\n\n", max_start_steps);

    PRINT_ZERO_RANGE(f2, df2, regula_falsi, -10, 0);
    PRINT_ZERO_RANGE(f2, df2, regula_falsi, 0, 10);
    PRINT_ZERO_RANGE(f3, df3, regula_falsi, 0, 10);

    return 0;
}
