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

#define _STR(X) #X
#define STR(X) _STR(X)

#define MAX_FILE_PATH_LENGTH 256

#define MAX_MATRIX_SIZE 500
#define MAX_MATRIX_IN_FILE_SIZE (MAX_MATRIX_SIZE + 2)

#define MAX_ITERATION_STEPS 100

typedef struct {
	int n;
	double** data;
} Matrix;
Matrix* createMatrix(void);
void freeMatrix(Matrix* matrix);
void createMatrixRows(Matrix* matrix, int rows);
void printMatrix(Matrix* matrix);

typedef struct {
	int n;
	double* data;
} Vector;
Vector* createVector(void);
void initVector(Vector* vector, int size);
void freeVector(Vector* vector);
void printVector(Vector* vector);

typedef enum {
	JACOBI = 0, GAUSS_SEIDEL = 1
} Method;

void flushStdin();

bool load(const char* filename, Matrix* A, Vector* b, Vector* x);
Vector* solve(Method method, Matrix* A, Vector* b, Vector* x, double e);

int readMatrixLine(FILE* file, double* matrixLine, int maxCols);

int main(int argc, char* argv[]) {
	char filePath[MAX_FILE_PATH_LENGTH];

	if(argc >= 2) {
		strncpy(filePath, argv[1], MAX_FILE_PATH_LENGTH);
	} else {
		puts("Please enter the path of the file you'd like to open");
		int result = scanf("%" STR(MAX_FILE_PATH_LENGTH) "[^\n]", filePath);
		if(result == EOF || result == 0 || filePath[0] == 0) {
			return 0;
		}
		if(result != 1) {
			puts("Couldn't read file path - stopping");
			return 1;
		}
		flushStdin();
	}

	Matrix* matrix = createMatrix();
	Vector* b = createVector();
	Vector* x = createVector();

	int returnCode = 0;

	while(true) {
		if(load(filePath, matrix, b, x)) {
			puts("Data successfully loaded\nMatrix A:");
			printMatrix(matrix);
			puts("Vector b:");
			printVector(b);
			puts("Vector x:");
			printVector(x);

			Vector* result = solve(JACOBI, matrix, b, x, 0.00001);
			free(result);
			break;
		} else {
			puts("Failed to load data from file.\nEnter new file path or press ctrl-c to exit");

			int result = scanf("%" STR(MAX_FILE_PATH_LENGTH) "[^\n]", filePath);
			if(result == EOF || result == 0 || filePath[0] == 0) {
				goto end;
			}
			if(result != 1) {
				puts("Couldn't read file path - stopping");
				returnCode = 1;
				goto end;
			}
			flushStdin();
		}
	}

end:
	freeMatrix(matrix);
	freeVector(b);
	freeVector(x);

	printf("\nReturning with code %i\n", returnCode);
	return returnCode;
}

bool load(const char* filename, Matrix* matrix, Vector* b, Vector* x) {
	FILE* file = fopen(filename, "r");
	if(file == NULL) {
		printf("Failed to open file \"%s\"\n", filename);
		return false;
	} else {
		double* firstLineBuffer = malloc(sizeof(double) * (MAX_MATRIX_IN_FILE_SIZE));

		int colsInFile = readMatrixLine(file, firstLineBuffer, MAX_MATRIX_IN_FILE_SIZE);

		if(colsInFile == -1) {
			puts("Unexpected input on line 0");

			free(firstLineBuffer);
			goto failure;
		} else if(colsInFile == -2) {
			printf("Exceeded maximum matrix size of %i\n", MAX_MATRIX_SIZE);

			free(firstLineBuffer);
			goto failure;
		} else {
			// success
			int cols = colsInFile - 1;
			initVector(b, cols);
			initVector(x, cols);

			b->data[0] = firstLineBuffer[cols];
			createMatrixRows(matrix, cols);
			matrix->data[0] = firstLineBuffer;

			int colsInLine;
			for(int i = 1; i < cols; i++) {
				matrix->data[i] = malloc(sizeof(double) * (colsInFile));
				colsInLine = readMatrixLine(file, matrix->data[i], colsInFile);
				if(colsInLine < 0) {
					if(i == cols - 1) {
						printf("Optional parameters are being used\n");

						// Matrix is one smaller than assumed
						cols--;
						b->n--;
						x->n--;
						matrix->n--;
						free(matrix->data[i]);

						x->data[0] = 0.4;

						// Copy b to x
						memcpy(x->data, b->data, b->n * sizeof(double));

						// extract b
						for(int j = 0; j < cols; j++) {
							b->data[j] = matrix->data[j][cols];
						}
						goto success;
					} else {
						printf("Line %i contains illegal formatting - please fix!\n", i + 1);
						goto failure;
					}
				} else if(colsInLine != colsInFile) {
					printf("Illegal line length found in line %i\n", i + 1);
					goto failure;
				} else {
					b->data[i] = matrix->data[i][cols];
				}
			}
			// successful but with no optional parameter

			// initialize vector x with zeros
			memset(x->data, 0, cols * sizeof(double));
			
		}
	}
success:
	fclose(file);
	return true;

failure:
	fclose(file);
	return false;
}


int readMatrixLine(FILE* file, double* matrixLine, int maxCols) {
	char nextChar = 0;
	int col = 0;
	double buffer;

	while(true) {
		if(fscanf(file, "%lf", &buffer) != 1) {
			return -1;
		}
		
		matrixLine[col] = buffer;

		col++;
		if(col > maxCols) {
			return -2;
		}
		nextChar = fgetc(file);
		if(nextChar == EOF || nextChar == '\n') break;
	}
	return col;
}

void flushStdin() {
	int c;
	while((c = getchar()) != '\n' && c != EOF && c != 0);
}

Vector* solve(Method method, Matrix* A, Vector* b, Vector* x, double e) {
	Vector* vectors = malloc(sizeof(Vector) * (MAX_ITERATION_STEPS + 1)); 

	// MAX_ITERATION_STEPS enthält die maximal zulässige Anzahl an Iterationsschritten (100)
	// Die einzelnen Vektoren müssen noch mit initVector initialisiert werden


	// HIER kommt der Code hin ;)

	// on success
	// Sei x die Anzahl der durchgeführten Iterationschritte. Dann setzt vectors[x+1].n = 0. Damit weiß das folgende Programm wie viele Schritte getätigt wurden.
	return vectors;

	// on failure
	free(vectors);
	return NULL;
}

inline Matrix* createMatrix(void) {
	Matrix* matrix = malloc(sizeof(Matrix));
	matrix->n = 0;
	return matrix;
}

inline void createMatrixRows(Matrix* matrix, int rows) {
	matrix->n = rows;
	matrix->data = malloc(sizeof(double*) * rows);
}

inline void freeMatrix(Matrix* matrix) {
	for(int i = 0; i < matrix->n; i++) {
		free(matrix->data[i]);
	}
	free(matrix->data);
	free(matrix);
}

void printMatrix(Matrix* matrix) {
	for(int i = 0; i < matrix->n; i++) {
		for(int j = 0; j < matrix->n; j++) {
			printf("%lf, ", matrix->data[i][j]);
		}
		puts("");
	}
}

inline Vector* createVector(void) {
	Vector* vector = malloc(sizeof(Vector));
	vector->n = 0;
	return vector;
}

inline void initVector(Vector* vector, int size) {
	vector->n = size;
	vector->data = malloc(sizeof(double) * size);
}

inline void freeVector(Vector* vector) {
	free(vector->data);
	free(vector);
}

void printVector(Vector* vector) {
	for(int i = 0; i < vector->n; i++) {
		printf("%lf, ",  vector->data[i]);
	}
	puts("");
}
