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

// TODO Matrix-Funktionen implementieren

Matrix createMatrix(unsigned int rows, unsigned int cols)
{
    if(rows && cols){
    Matrix matrix;
    matrix.rows = rows;
    matrix.cols = cols;
    matrix.buffer = malloc(rows * cols * sizeof(MatrixType));

    matrix.buffer = malloc(rows * cols * sizeof(MatrixType));
    if (!matrix.buffer) {
    matrix.rows = 0;
    matrix.cols = 0;
    return matrix; // malloc ist fehlgeschlagen
    }


    for (int i = 0; i < (rows * cols); i++)
        matrix.buffer[i] = 0;

    return matrix;
    }
    Matrix matrix;
    matrix.rows = 0;
    matrix.cols = 0;
    matrix.buffer = 0;
    return matrix;
}


void clearMatrix(Matrix *matrix)
{
  free(matrix->buffer);
  matrix->buffer = NULL;
  matrix->rows = 0;
  matrix->cols = 0;
}

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)
{
  if(rowIdx >= matrix.rows || colIdx >= matrix.cols){
    return UNDEFINED_MATRIX_VALUE;
  }
  return matrix.buffer[rowIdx * matrix.cols + colIdx];
}

Matrix add(const Matrix matrix1, const Matrix matrix2)
{
    int rows1 = rows(matrix1);
    int rows2 = rows(matrix2);
    int cols1 = cols(matrix1);
    int cols2 = cols(matrix2);
    if((cols1 == 1 || cols2 == 1) && rows1 == rows2) //Broadcasting
    {
        if(cols1 == 1)      //Wenn die erste Matrix der Vektor ist
          {
            return broadcasting(matrix1, matrix2);
          }
        else
          {
            return broadcasting(matrix2, matrix1);
          }
    }
    else if(rows1 == rows2 && cols1 == cols2) //Addition nur moeglich, wenn beide Matrizen gleiche ANzahl an Zeilen und Spalten haben
    {
      Matrix addition = createMatrix(rows1, cols2); //Matrix erstellt, in die die addierten Werte geschrieben werden
      MatrixType wert1;
      MatrixType wert2;
      for(int i = 0; i < rows1; i++){ //soll fuer jedes Element durchlaufen, sodass alle Werte von beiden Matrizen aufaddiert werden.
        for(int j = 0; j < cols1; j++){
        wert1 = getMatrixAt(matrix1, i, j);
        wert2 = getMatrixAt(matrix2, i, j);
        MatrixType addierterWert = wert1 + wert2;
        setMatrixAt(addierterWert, addition, i, j);
        }
      }
      return addition;
    }
    else{
      return createMatrix(0,0);
    }
}

Matrix broadcasting(const Matrix vektor, const Matrix matrix)
  {
      int rowsM = rows(matrix);
      int colsM = cols(matrix);
      Matrix result = createMatrix(rowsM, colsM);
      MatrixType wert;
      MatrixType koordinate;
      for(int i = 0; i < rowsM; i++)
        {
        for(int j = 0; j < colsM; j++){
          wert = getMatrixAt(matrix, i, j);
          koordinate = getMatrixAt(vektor, i, 0);
          setMatrixAt((koordinate + wert), result, i, j);
          }
        }
      return result;
  }

Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{
     int rows1 = rows(matrix1);
     int cols1 = cols(matrix1);
     int rows2 = rows(matrix2);
     int cols2 = cols(matrix2);
     if(cols1 == rows2) //Bedingung fuer Multiplikation: Spalten der ersten Matrix gleich Zeilen 2. Matrix
     {
         Matrix result = createMatrix(rows1, cols2);
         MatrixType wert1;
         MatrixType wert2;
         MatrixType mul;
         MatrixType add = 0;
         for(int i = 0; i < rows1; i++)
         {
           for(int k = 0; k < cols2; k++)
           {
            for(int j = 0; j < cols1; j++)
            {
              wert1 = getMatrixAt(matrix1, i, j);
              wert2 = getMatrixAt(matrix2, j, k);
              mul = wert1 * wert2;
              add += mul;
            }
            setMatrixAt(add, result, i, k);
            add = 0;
           }
         }
         return result;
     }
     return createMatrix(0,0);
}

int rows(const Matrix matrix)
{
   return matrix.rows;
}

int cols(const Matrix matrix)
{
  return matrix.cols;
}