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

// TODO Matrix-Funktionen implementieren
/*typedef struct {
    unsigned int rows;  //Zeilen
    unsigned int cols; //Spalten
    MatrixType *buffer; //Zeiger auf Speicherbereich Reihen*Spalten
} Matrix;*/
Matrix createMatrix(unsigned int rows, unsigned int cols) {

  Matrix errorMatrix = {0, 0, NULL};
  if (rows == 0 || cols == 0) {

    return errorMatrix;
  }
  MatrixType *buffer =
      malloc(rows * cols * sizeof(MatrixType)); // Speicher reservieren, malloc
                                                // liefert Zeiger auf Speicher
  Matrix newMatrix = {rows, cols, buffer};      // neue Matrix nach struct
  return newMatrix;
}
void clearMatrix(Matrix *matrix) {
  matrix->buffer = UNDEFINED_MATRIX_VALUE;
  matrix->rows = UNDEFINED_MATRIX_VALUE;
  matrix->cols = UNDEFINED_MATRIX_VALUE;
  free((*matrix).buffer); // Speicher freigeben
}

void setMatrixAt(const MatrixType value, Matrix matrix,
                 const unsigned int rowIdx, // Kopie der Matrix wird übergeben
                 const unsigned int colIdx) {

  matrix.buffer[rowIdx * matrix.cols + colIdx] =
      value; // rowIdx * matrix.cols -> Beginn der Zeile colIdx ->Spalte
             // innerhalb der Zeile
}
MatrixType getMatrixAt(const Matrix matrix,
                       unsigned int rowIdx, // Kopie der Matrix wird übergeben
                       unsigned int colIdx) {
  if (rowIdx >= matrix.rows ||
      colIdx >= matrix.cols) { // Speichergröße nicht überschreiten
    return 0;
  }

  MatrixType value = matrix.buffer[rowIdx * matrix.cols + colIdx];

  return value;
}

Matrix broadCastCols(const Matrix matrix, const unsigned int rows,
                     const unsigned int cols) {

  Matrix copy = createMatrix(
      rows, cols); // Matrix 1 Kopie erstellen mit Dimensionen von Matrix2
  for (int r = 0; r < rows; r++) {

    MatrixType value = getMatrixAt(matrix, r, 0);
    for (int c = 0; c < cols; c++) {

      setMatrixAt(value, copy, r, c);
    }
  }

  return copy;
}
Matrix broadCastRows(const Matrix matrix, const unsigned int rows,
                     const unsigned int cols) {

  Matrix copy = createMatrix(rows, cols);

  for (int c = 0; c < cols; c++) {
    MatrixType value = getMatrixAt(matrix, 0, c);

    for (int r = 0; r < rows; r++) {

      setMatrixAt(value, copy, r, c);
    }
  }

  return copy;
}

Matrix add(const Matrix matrix1, const Matrix matrix2) {

  // Broadcasting nur bei Vektor und Matrix, Fehlermeldung bei zwei unpassenden
  // Matrizen

  const unsigned int rows1 = matrix1.rows;
  const unsigned int rows2 = matrix2.rows;
  const unsigned int cols1 = matrix1.cols;
  const unsigned int cols2 = matrix2.cols;

  const int rowsEqual = ((rows1 == rows2) ? 1 : 0);

  const int colsEqual = ((cols1 == cols2) ? 1 : 0);

  if (rowsEqual && colsEqual) // addieren

  {
    Matrix result = createMatrix(rows1, cols1); // Speicher reservieren

    for (int i = 0; i < (rows1 * cols1); i++) { // addieren

      result.buffer[i] =
          (matrix1.buffer[i] +
           matrix2.buffer[i]); // buffer[i]  ⇔  *(buffer + i) Adresse =
                               // Startadresse + (i * sizeof(MatrixType))
    }

    return result; // zurückgeben
  }

  else if (rowsEqual && !colsEqual) {

    if (cols1 == 1) {

      Matrix result = createMatrix(rows2, cols2);

      Matrix copy1 = broadCastCols(matrix1, rows2, cols2);

      for (int i = 0; i < (rows2 * cols2); i++) { // addieren

        result.buffer[i] =
            (copy1.buffer[i] +
             matrix2.buffer[i]); // buffer[i]  ⇔  *(buffer + i) Adresse =
                                 // Startadresse + (i * sizeof(MatrixType))
      }
      return result;

      // add und return

    } else if (cols2 == 1) {

      Matrix result = createMatrix(rows1, cols1);

      Matrix copy2 = broadCastCols(matrix2, rows1, cols1);

      for (int i = 0; i < (rows1 * cols1); i++) { // addieren

        result.buffer[i] =
            (matrix1.buffer[i] +
             copy2.buffer[i]); // buffer[i]  ⇔  *(buffer + i) Adresse =
                               // Startadresse + (i * sizeof(MatrixType))
      }
      return result;
      // add und return

    }

    else {

      printf("Fehlermeldung"); // vielleicht Fehlermeldung ändern zu
                               // Programmabbruch
      Matrix error = {0, 0, NULL};
      return error;
    }

  }

  else if (!rowsEqual && colsEqual) {

    if (rows1 == 1) {

      Matrix result = createMatrix(rows2, cols2);

      Matrix copy1 = broadCastRows(matrix1, rows2, cols2);

      for (int i = 0; i < (rows2 * cols2); i++) { // addieren

        result.buffer[i] =
            (copy1.buffer[i] +
             matrix2.buffer[i]); // buffer[i]  ⇔  *(buffer + i) Adresse =
                                 // Startadresse + (i * sizeof(MatrixType))
      }
      return result;

      // add und return

    } else if (rows2 == 1) {

      Matrix result = createMatrix(rows1, cols1);

      Matrix copy2 = broadCastCols(matrix2, rows1, cols1);
      // add und return

      for (int i = 0; i < (rows1 * cols1); i++) { // addieren

        result.buffer[i] =
            (matrix1.buffer[i] +
             copy2.buffer[i]); // buffer[i]  ⇔  *(buffer + i) Adresse =
                               // Startadresse + (i * sizeof(MatrixType))
      }
      return result;

    }

    else {

      printf("Fehlermeldung"); // vielleicht Fehlermeldung ändern zu
                               // Programmabbruch
      Matrix error = {0, 0, NULL};
      return error;
    }
  }

  else {
    printf(
        "Fehlermeldung"); // vielleicht Fehlermeldung ändern zu Programmabbruch
    Matrix error = {0, 0, NULL};
    return error;
  }
}
Matrix multiply(const Matrix matrix1, const Matrix matrix2) { return matrix1; }
