#include <stdlib.h>
#include <string.h>
#include "matrix.h"

// TODO Matrix-Funktionen implementieren
// Matrix erstellen
Matrix createMatrix(unsigned int rows, unsigned int cols)
{
    // Falls eine Matrix eine Null Dimension hat leere Matrix ausgeben
   if (rows ==0 || cols == 0){
       Matrix empty = {0, 0, NULL};
            return empty;
        }

   Matrix matrix; 
   matrix.rows = rows;
   matrix.cols = cols;
    // Speicher erstellen und nullsetzen
   matrix.buffer = (MatrixType *)calloc(rows * cols, sizeof(MatrixType));

   return matrix;
}
// Matrix Speicher freigeben
void clearMatrix(Matrix *matrix)
{
  if (matrix != NULL && matrix->buffer != NULL) {
        free(matrix->buffer);
        matrix->buffer = NULL;
        matrix->rows = 0;
        matrix->cols = 0;
    }  
}

// Matrix muss von 2D in 1D umgewandelt werden deswegen (Zeile* Anzahl Spalten + Spalte)
// um an entsprechende Speicheradresse zu kommen
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{
    matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
}

MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{
    // Prüfen ob Indizes im gültigen Bereich sind
    if (rowIdx >= matrix.rows || colIdx >= matrix.cols) {
        return 0;
    }
    
    // Prüfen ob was im Buffer ist
    if (matrix.buffer == NULL) {
        return 0;
    }
    
    return matrix.buffer[rowIdx * matrix.cols + colIdx];
}

Matrix add(const Matrix matrix1, const Matrix matrix2)
{
    // Broadcasting Fall 1: matrix1 ist ein Spaltenvektor
    if (matrix1.cols == 1 && matrix1.rows == matrix2.rows) {
        // Neue Matrix mit passender Dimension erstellen
        Matrix result = createMatrix(matrix2.rows, matrix2.cols);
    
        // Vektor zu jeder Spalte addieren
        for (unsigned int i = 0; i < result.rows; i++) {
            for (unsigned int j = 0; j < result.cols; j++) {
                MatrixType vectorValue = getMatrixAt(matrix1, i, 0);
                MatrixType matrixValue = getMatrixAt(matrix2, i, j);
                setMatrixAt(vectorValue + matrixValue, result, i, j);
            }
        }
        return result;
    }

    // Broadcasting Fall 2: matrix2 ist ein Spaltenvektor
    else if (matrix2.cols == 1 && matrix2.rows == matrix1.rows) {
        // Neue Matrix mit passender Dimension erstellen
        Matrix result = createMatrix(matrix1.rows, matrix1.cols);

        // Vektor zu jeder Spalte addieren
        for (unsigned int i = 0; i < result.rows; i++) {
            for (unsigned int j = 0; j < result.cols; j++) {
                MatrixType matrixValue = getMatrixAt(matrix1, i, j);
                MatrixType vectorValue = getMatrixAt(matrix2, i, 0);
                setMatrixAt(matrixValue + vectorValue, result, i, j);
            }
        }
        return result;
    }
    
    // Normale elementweise Addition: Dimensionen müssen übereinstimmen
    else if (matrix1.rows == matrix2.rows && matrix1.cols == matrix2.cols) {
        // Neue Matrix mit passender Dimension erstellen
        Matrix result = createMatrix(matrix1.rows, matrix1.cols);
        
        // Alle Elemente einzeln addieren
        for (unsigned int i = 0; i < matrix1.rows * matrix1.cols; i++) {
            result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
        }
        
        return result;
    }
    
    // Fehlerfall: Dimensionen passen nicht
    else {
        Matrix empty = {0, 0, NULL};
        return empty;
    }
}


Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{
        // Prüfen ob Matrizen multipiziert werden können
        if (matrix1.cols != matrix2.rows) {
            // Fehlerfall: Leere Matrix zurückgeben
            Matrix empty = {0, 0, NULL};
            return empty;
        }
        // Neue Matrix mit passender Dimension erstellen
        Matrix result = createMatrix(matrix1.rows, matrix2.cols);
        
         // Matrix-Multiplikation durchführen
        for (unsigned int i = 0; i < result.rows; i++) {
            for (unsigned int j = 0; j < result.cols; j++) {
                MatrixType sum = 0.0;
                
                // Skalarprodukt der i-ten Zeile mit j-ter Spalte
                for (unsigned int k = 0; k < matrix1.cols; k++) {
                    MatrixType a = getMatrixAt(matrix1, i, k);
                    MatrixType b = getMatrixAt(matrix2, k, j);
                    sum += a * b;
                }
                
                setMatrixAt(sum, result, i, j);
            }
        }
        return result;
    
}