#include "nr.h"
#include "nrutil.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#include "matutil.h"

void try_gaussj(JMatrixData *data) {
    printf("gauss-jordan method:\n");
    fflush(stdout);
    fflush(stderr);
    gaussj(data->a, data->m, data->b, 1);
    print_matrix(data->m, 1, data->b);
}

void try_lu(JMatrixData *data) {
    printf("LU Decomposition:\n");

    int *indx = calloc(data->m + 1, sizeof(int));
    float *d = calloc(data->m + 1, sizeof(float));
    ludcmp(data->a, data->m, indx, d);

    print_matrix(data->m, data->n, data->a);

    printf("index: ");
    print_vector_int(data->m, indx);

    printf("solution x: ");
    float *b = calloc(data->m + 1, sizeof(float));
    int i;
    for (i = 1; i <= data->m; i++) {
        b[i] = data->b[i][1];
    }
    lubksb(data->a, data->m, indx, b);
    print_vector_float(data->m, b);
    free(indx);
    free(d);
    free(b);
}

void try_svd(JMatrixData *data) {
    int i;
    printf("Singular Value Decompoisition:\n");
    fflush(stdout);
    fflush(stderr);
    float *w = calloc(data->m + 1, sizeof(float));
    float **v = new_matrix(data->m, data->n);
    svdcmp(data->a, data->m, data->n, w, v);
    printf("U:\n");
    print_matrix(data->m, data->n, data->a);
    printf("w: ");
    print_vector_float(data->m, w);
    printf("S:\n");
    float **s = new_diagonal(data->m, w);
    print_matrix(data->m, data->n, s);
    printf("V:\n");
    print_matrix(data->m, data->n, v);
    printf("\n");

    float **t1 = matmul(data->m, data->m, data->m, data->a, s);
    float **vt = mattranspose(data->m, data->n, v);
    float **t2 = matmul(data->m, data->n, data->m, t1, vt);

    printf("Orig = U * S * V^T\n");
    print_matrix(data->m, data->n, t2);

    printf("we want to get x by using x = V * (S^-1 * (U^T * b)):\n");
    printf("x:\n");

    float **invS = new_matrix(data->m, data->m);
    for (i = 1; i <= data->m; i++) {
        invS[i][i] = 1 / w[i];
    }

    float **UT = mattranspose(data->m, data->n, data->a);
    float **l1 = matmul(data->m, data->m, 1, UT, data->b);
    float **l2 = matmul(data->m, data->m, 1, invS, l1);
    float **l3 = matmul(data->m, data->m, 1, v, l2);

    print_matrix(data->m, 1, l3);

    simple_free_matrix(data->m, data->n, v);
    simple_free_matrix(data->m, data->m, vt);
    simple_free_matrix(data->m, data->m, s);
    simple_free_matrix(data->m, data->n, t1);
    simple_free_matrix(data->m, data->n, t2);
    simple_free_matrix(data->m, data->m, invS);
    simple_free_matrix(data->m, data->m, UT);
    simple_free_matrix(data->m, 1, l1);
    simple_free_matrix(data->m, 1, l2);
    simple_free_matrix(data->m, 1, l3);

    free(w);
}

void try_det_inv(JMatrixData *data) {
    int m = data->m;

    float det = matdet(m, data->a);
    printf("det: %f\n", det);

    if (det < 1e-6) {
        printf("There is no inverse matrix, because det ~ 0.0f\n");
    } else {
        float **inv = matinv(m, data->a);
        printf("inv:\n");
        print_matrix(m, m, inv);
        simple_free_matrix(m, m, inv);
    }
}

void try_mprove(JMatrixData *data) {
    printf("mprove method:\n");
    fflush(stdout);
    fflush(stderr);
    float **a = new_matrix(data->m, data->m);
    copy_matrix(data->m, data->m, data->a, a);

    int *indx = calloc(data->m + 1, sizeof(int));
    float *d = malloc(sizeof(float));
    ludcmp(a, data->m, indx, d);
    float *x_init = calloc(data->m + 1, sizeof(float));
    float *b = calloc(data->m + 1, sizeof(float));
    int i;
    for (i = 1; i <= data->m; i++) {
        x_init[i] = data->b[i][1];
        b[i] = data->b[i][1];
    }

    lubksb(a, data->m, indx, x_init);
    float *x = calloc(data->m + 1, sizeof(float));
    for (i = 1; i <= data->m; i++) {
        x[i] = x_init[i];
    }
    printf("x_init: ");
    print_vector_float(data->m, x_init);

    mprove(data->a, a, data->m, indx, b, x);
    printf("x using mprove: ");
    print_vector_float(data->m, x);

    free(x);
    free(x_init);
    free(indx);
    free(d);
    simple_free_matrix(data->m, data->m, a);
}

void processMatrix(JMatrixData *data) {
    printf("-----------------------------\n");
    printf("matrix data A:\n");
    print_matrix(data->m, data->n, data->a);
    printf("matrix data b:\n");
    print_matrix(data->m, 1, data->b);
    printf("-----------------------------\n");
    fflush(stdout);
    fflush(stderr);
    pid_t pid = fork();
    if (pid < 0) {
        printf("process 실행 실패\n");
        return;
    } else if (pid == 0) {
        printf("created process for gauss-jordan method\n");
        printf("-----------------------------\n");
        JMatrixData *copied = new_jmatdata(data->m, data->n);
        copy_jmatdata(data, copied);
        try_gaussj(copied);
        free_jmatdata(copied);
        printf("-----------------------------\n");
        exit(0);
    }

    wait(NULL);

    fflush(stdout);
    fflush(stderr);
    pid = fork();
    if (pid < 0) {
        printf("process 실행 실패\n");
        return;
    } else if (pid == 0) {
        printf("created process for LU Decomposition\n");
        printf("-----------------------------\n");
        fflush(stdout);
        fflush(stderr);
        JMatrixData *copied = new_jmatdata(data->m, data->n);
        copy_jmatdata(data, copied);
        try_lu(copied);
        free_jmatdata(copied);
        printf("-----------------------------\n");
        exit(0);
    }
    wait(NULL);

    fflush(stdout);
    fflush(stderr);

    pid = fork();
    if (pid < 0) {
        printf("process 실행 실패\n");
        return;
    } else if (pid == 0) {
        printf("created process for Singular Value Decomposition\n");
        printf("-----------------------------\n");
        fflush(stdout);
        fflush(stderr);
        JMatrixData *copied = new_jmatdata(data->m, data->n);
        copy_jmatdata(data, copied);
        try_svd(copied);
        free_jmatdata(copied);
        printf("-----------------------------\n");
        exit(0);
    }

    wait(NULL);

    fflush(stdout);
    fflush(stderr);

    pid = fork();
    if (pid < 0) {
        printf("process 실행 실패\n");
        return;
    } else if (pid == 0) {
        printf("created process for LUDecomp with mprove\n");
        printf("-----------------------------\n");
        fflush(stdout);
        fflush(stderr);
        JMatrixData *copied = new_jmatdata(data->m, data->n);
        copy_jmatdata(data, copied);
        try_mprove(copied);
        free_jmatdata(copied);
        printf("-----------------------------\n");
        exit(0);
    }

    wait(NULL);

    fflush(stdout);
    fflush(stderr);

    printf("last we want to get det and inv of A:\n");
    pid = fork();
    if (pid < 0) {
        printf("process 실행 실패\n");
        return;
    } else if (pid == 0) {
        printf("created process for det and inv\n");
        printf("-----------------------------\n");
        fflush(stdout);
        fflush(stderr);
        JMatrixData *copied = new_jmatdata(data->m, data->n);
        copy_jmatdata(data, copied);
        try_det_inv(copied);
        free_jmatdata(copied);
        printf("-----------------------------\n");
        exit(0);
    }

    wait(NULL);
    fflush(stdout);
    fflush(stderr);
}

JMatrixData *readDataFrom(char *filename) {
    FILE *fp = fopen(filename, "r");

    if (fp == NULL) {
        printf("Error opening file");
        exit(1);
    }

    int m, n;
    fscanf(fp, "%d", &m);
    fscanf(fp, "%d", &n);

    JMatrixData *data = new_jmatdata(m, n);

    int i, j;
    for (i = 1; i <= data->m; i++) {
        for (j = 1; j <= data->n; j++) {
            fscanf(fp, "%f", &data->a[i][j]);
        }
    }

    for (i = 1; i <= data->m; i++) {
        fscanf(fp, "%f", &data->b[i][1]);
    }

    fclose(fp);
    return data;
}

int main() {
    int m, n;
    int i, j;

    const char *filenames[] = {
            "lineq1.dat", "lineq2.dat", "lineq3.dat"};
    pid_t pid;
    for (i = 0; i < 3; i++) {
        pid = fork();
        fflush(stdout);
        fflush(stderr);
        if (pid < 0) {
            printf("process 실행 실패\n");
            return 2;
        } else if (pid == 0) {
            printf("=================================\n");
            printf("data from %s:\n", filenames[i]);
            JMatrixData *data = readDataFrom(filenames[i]);
            processMatrix(data);
            printf("end for %s\n", filenames[i]);
            exit(0);
        }
        wait(NULL);
    }

    fflush(stdout);
    fflush(stderr);
    
    printf("=======================================\n");
    printf("===============< Report >==============\n");
    printf("=======================================\n");
    printf("# 1. discuss three method.\n");
    printf("In terms of the accuracy of the solution,\nthere is no noticable difference between three methods.(at 1e-6)\n");
    printf("But `gaussj` method is unstable when there are many solutions.\n");
    printf("In terms of stability, svdcmp is the most stable\nbecause the solution can always come out (because it is, by definition, expressed as matmul).\n");
    printf("I could not feel it in this example\nbut I think svd is the slowest for that; matmul is too expensive.\n");
    printf("so I think that LUdcmp is balanced:\nNot as unstable as gaussj, not as expensive as svdcmp.\n");
    printf("# 2. mprove\n");
    printf("`mprove` is a method of increasing the accuracy of the solution\nby using an iterative method using the alud and initial solution obtained by `ludcmp`.\n");
    printf("The accuracy of the LU can be compensated using this method,\nso the LUdcmp is getting more useful.\n");
    printf("# 3. det and inv\n");
    printf("Without first example, we can get inverse matrix with gaussj.\n");
    printf("We can also get determinants using cofactor expansion with recursive method.\n");
    printf("=======================================\n");
    printf("=============< End Report >============\n");
    printf("=======================================\n");

    return 0;
}