#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "integration.h"
#include "models.h"
#include "logger.h"

#define DEFAULT_DT .01
#define COMPARE_DT 1.0

#define INTEGRATE(method, t0, t1, dt, N, FN) { \
    if( method(t0, t1, dt, y0, y1, N, &f_ ## FN, (void *)args_ ## FN) ) \
        puts("FAIL"); \
}

extern FILE *logfile;

int main(int argc, char **argv) {
    if( argc < 2 ) {
        printf("Usage: %s ODE\n", argv[0]);
        return EXIT_FAILURE;
    }

    double y0[3] = {.0, 5.0, .0},
           y1[3] = {.0, .0, .0};

    const double args_osc[1]   = {1.0},                        // {k}
           args_forced_osc[3]  = {1.0, 0.1, 0.5},              // {k, D, w}
           args_lottka_volt[4] = {.5, 1.0, .1, .1},            // {a, b, c, d}
           args_modified_pp[5] = {.5, 1.0, .1, .1, 100.0},     // {a, b, c, d, y_m}
           args_pid_control[5] = {-2.0, -1.0, .1, 10.0, .1}, // {P, D, a, M, d}
           *args_gilpin = NULL;

    switch( atoi(argv[1]) ) {
        case 0:
            y0[0] = 10.0;
            y0[1] = 20.0;
            logger_open("report/osc_euler");
            puts("Integrating Euler method...");
            INTEGRATE(Euler, .0, 100.0, COMPARE_DT, 2, osc);
            logger_close();
            logger_open("report/osc_rk2");
            puts("Integrating Runge-Kutta 2 method...");
            INTEGRATE(RungeKutta2, .0, 100.0, COMPARE_DT, 2, osc);
            logger_close();
            logger_open("report/osc_rk4");
            puts("Integrating Runge-Kutta 4 method...");
            INTEGRATE(RungeKutta4, .0, 100.0, COMPARE_DT, 2, osc);
        break;
        case 1: INTEGRATE(RungeKutta4, .0, 200.0, DEFAULT_DT, 2, forced_osc); break;
        case 2:
            y0[0] = 0.5;
            y0[1] = 1.0;
            //y0[0] = 50.0;
            //y0[1] = 100.0;
            INTEGRATE(RungeKutta4, .0, 400.0, DEFAULT_DT, 2, lottka_volt); break;
        case 3:
            y0[0] = 0.5;
            y0[1] = 1.0;
            //y0[0] = 50.0;
            //y0[1] = 100.0;
            INTEGRATE(RungeKutta4, .0, 100.0, DEFAULT_DT, 2, modified_pp); break;
        case 4:
            y0[0] = .0;  // = v(0)
            y0[1] = 3.0; // = s(0)
            buf_size = ceil(args_pid_control[4] / DEFAULT_DT);
            v_buf = malloc(buf_size * sizeof(double));
            s_buf = malloc(buf_size * sizeof(double));

            for( int i = 0; i < buf_size; i++ ) {
                v_buf[i] = y0[0];
                s_buf[i] = y0[1];
            }

            INTEGRATE(RungeKutta4, .0, 150.0, DEFAULT_DT, 2, pid_control);
            free(v_buf);
            free(s_buf);
        break;
        case 5:
            y0[0] = y0[1] = y0[2] = 10.0;
            INTEGRATE(RungeKutta4, .0, 1000.0, .1, 3, gilpin);
        break;
        default: puts("ODE does not exist!"); return EXIT_FAILURE;
    }

    logger_close();

    return EXIT_SUCCESS;
}
