#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#define VERBOSE

double **y = NULL, dx;
int steps, time;

/*
 * Fill the string with a sinus shape.
 */
void init_sinus(double l, double n) {
    for( int i = 0; i < steps; i++ )
        y[0][i] = sin(n * M_PI * i * dx / l);

    // The string is attached at both ends
    y[2][0] = y[1][0] = y[0][0];
    y[2][steps - 1] = y[1][steps - 1] = y[0][steps - 1];
}

/*
 * Fill the string with a plucked shape.
 */
void init_plucked(double l, double xp) {
    double x;

    for( int i = 0; i < steps; i++ ) {
        x = i * dx;
        y[0][i] = x < xp ? x / xp : (l - x) / (l - xp);
    }

    // The string is attached at both ends
    y[2][0] = y[1][0] = y[0][0];
    y[2][steps - 1] = y[1][steps - 1] = y[0][steps - 1];
}

#ifdef VERBOSE
#define PRINT_FORMAT "%e"

/*
 * Print all calculated values in the string of one of the three currently
 * known time states.
 */
void print(int state) {
    for( int i = 0; i < steps; i++ ) {
        if( i )
            printf(" ");

        printf(PRINT_FORMAT, y[state][i]);
    }

    puts("");
}
#endif

/*
 * Calculate and optionally print the string states for the specified number of
 * time steps.
 */
void calculate_steps(int time, double tau) {
    int i, t, prev = 0, current = 1, next = 2;

    // Calculate the position over the entire string at time dt using the
    // position at t = 0 and the information that y(x, -dt) == y(x, dt)
    for( i = 1; i < steps - 1; i++ ) {
        y[1][i] = y[0][i] + .5 * tau * tau * (y[0][i - 1] - 2 * y[0][i]
                  + y[0][i + 1]);
    }

    #ifdef VERBOSE
    // Print x values (which are the same for every y-series)
    for( i = 0; i < steps; i++ ) {
        if( i )
            printf(" ");

        printf(PRINT_FORMAT, i * dx);
    }

    puts("");

    // Print init states
    print(prev);
    print(current);
    #endif

    // Iterate over the length of the string for each time interval step
    for( t = 2; t < time; t++ ) {
        for( i = 1; i < steps - 1; i++ ) {
            y[next][i] = 2 * y[current][i] - y[prev][i] + tau * tau
                * (y[current][i - 1] - 2 * y[current][i] + y[current][i + 1]);
        }

        #ifdef VERBOSE
        print(next);
        #endif

        prev = current;
        current = next;
        next = (next + 1) % 3;
    }
}

/*
 * Main method, initiates the program.
 */
int main(int argc, const char **argv) {
    int time, i;
    void (*init_method)(double, double);
    double l, tau, constant;

    // Parse arguments
    if( argc < 7 ) {
        printf("Usage: %s INIT_METHOD CALCULATION_STEPS LENGTH DX TAU N|XP\n",
               argv[0]);
        return EXIT_FAILURE;
    }

    if( !strcmp(argv[1], "sinus") ) {
        init_method = init_sinus;
    } else if( !strcmp(argv[1], "plucked") ) {
        init_method = init_plucked;
    } else {
        printf("Unknown initialization method '%s'.\n", argv[1]);
        return EXIT_FAILURE;
    }

    time = atoi(argv[2]);
    l = atof(argv[3]);
    dx = atof(argv[4]);
    tau = atof(argv[5]);
    constant = atof(argv[6]);

    // Allocate a 3xN two-dimensional area that acts as a cyclic buffer for
    // three states of the string (previous, current, next)
    steps = (int)ceil(l / dx) + 1;

    y = (double **)malloc(3 * sizeof(double *));

    for( i = 0; i < 3; i++ )
        y[i] = (double *)malloc(steps * sizeof(double));

    // Initialize the string values (sinus/plucked)
    (*init_method)(l, constant);

    // Calculate the specified number of steps
    calculate_steps(time, tau);

    // Free allocated memory
    for( i = 0; i < 3; i++ )
        free(y[i]);

    free(y);

    return EXIT_SUCCESS;
}
