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

// TODO Matrix-Funktionen implementieren (alle!)

Matrix createMatrix(unsigned int rows, unsigned int cols)
{
 Matrix m= {NULL,0,0}; //erstellen eine leere Matrix (NULL kann durch 0 ersetzt werden -> kein Speicher reserviert)

 if(rows ==0 || cols == 0)  //wenn Dimensionen nicht passen -> leere Matrix zurückgeben
   return m;

 m.buffer = calloc(rows * cols, sizeof(MatrixType));   //calloc reserviert speicher und füllt mit nullen
 if(m.buffer == NULL)  //Speicher reservieren hat nicht geklappt-> leere Matrix zurückgeben
   return m;

 m.rows = rows;   //Dimensionen zuweisen
 m.cols = cols;
 return m;   //passende Matrix übergeben
    
}



void clearMatrix(Matrix *matrix)
{
   if(matrix == NULL)  //leere Matrix -> abbruch
     return ;
   free(matrix->buffer);  //speicher freigeben
   matrix->buffer = NULL;  //pionter auf null setzen
   matrix->rows = 0;       //matrix auf null setzen
   matrix->cols = 0;        //auch möglich statt pfeil: (*matrix).rows = 0;
    }




void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx) //setzt einen bestimmten wert in die Matrix
{
    if(matrix.buffer == NULL || matrix.rows ==0 || matrix.cols ==0)  //leere Matrix -> abbruch
     return;

    if(rowIdx >= matrix.rows|| colIdx >= matrix.cols)  //prüfen ob zu setzender Wert innerhalb der Matrix liegt
      return; 

    matrix.buffer[rowIdx * matrix.cols + colIdx] = value; //setzt Wert an stelle im Speicher

}






MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{
  if(matrix.buffer == NULL || matrix.rows ==0 || matrix.cols ==0)  //leere Matrix -> abbruch
     return UNDEFINED_MATRIX_VALUE;

  if(rowIdx >= matrix.rows|| colIdx >= matrix.cols) //Wert außerhalb?
    return UNDEFINED_MATRIX_VALUE;

  return matrix.buffer[rowIdx * matrix.cols + colIdx]; //gibt Wert der Matrix an Stelle zurück
}



Matrix add(const Matrix matrix1, const Matrix matrix2)
{
    Matrix result = {NULL,0,0};  //erstellen einer leeren Matrix

    if(matrix1.buffer == 0 || matrix2.buffer == 0) //wenn Matrix 1 oder 2 leer ist -> leere Matrix zurückgeben
      return result;
    if(matrix1.rows !=matrix2.rows && matrix1.rows != 1 && matrix2.rows != 1) // broadcasting check (Zeilen nicht gleich und keiner der beiden Vektor -> leer)
      return result;
    if(matrix1.cols !=matrix2.cols && matrix1.cols != 1 && matrix2.cols != 1) // broadcasting check (Spalten nicht gleich und kein Vektor -> leer)
      return result;


    unsigned int rows;               //Matrix mit mehr Zeilen -> Zeilen neuer Matrix
    if (matrix1.rows > matrix2.rows)
      rows = matrix1.rows;
    else
      rows = matrix2.rows;



     unsigned int cols;             //Matrix mit mehr Spalten -> Spalten neuer Matrix
    if (matrix1.cols > matrix2.cols)
      cols = matrix1.cols;
    else
      cols = matrix2.cols;



    result = createMatrix(rows, cols); //fkt. von oben gleich nutzen, speicher reservieren
    if(result.buffer == NULL)
      return result;



    for(unsigned int i = 0; i < rows; i++)
      {
      unsigned int r1 = (matrix1.rows == 1) ? 0 : i;             //wenn zeilen 1 sind, wird immer erstes Element der Zeile verwenndet!
      unsigned int r2 = (matrix2.rows == 1) ? 0 : i;

        	 for(unsigned int j = 0; j < cols; j++)
                   {
                   unsigned int c1 = (matrix1.cols == 1) ? 0 : j;   //wenn spalten 1 sind, wird immer erstes Element der Spalte verwenndet!
                   unsigned int c2 = (matrix2.cols == 1) ? 0 : j;

                   MatrixType v1 = matrix1.buffer[r1 * matrix1.cols + c1];    //Werte werden aus den Matrizen geholt
                   MatrixType v2 = matrix2.buffer[r2 * matrix2.cols + c2];     //MatrixType ist als float definiert in .h, kann so schnell geändert werden
                   result.buffer[i*cols+j] = v1 + v2;             //in die Matrix result speichern
	               }
}
return result;
}





Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{
Matrix result = {NULL,0,0};

if(matrix1.buffer == NULL || matrix2.buffer == NULL) //Matrix leer?
  return result;

if (matrix1.cols != matrix2.rows)  //Anzahl Spalten von 1. Matrix muss mit Anzahl Zeilen 2. Matrix übereinstimmen
  return result;

 //unsigned entfernt?
int rows = matrix1.rows;  // mxn nxp -> neu: mxp
int cols = matrix2.cols;
int inner = matrix1.cols;

result = createMatrix(rows, cols); //Funktion nutzen, speicher reservieren
if(result.buffer == NULL)
  return result;

for(unsigned int i = 0; i < rows; i++)
  {
  for(unsigned int j = 0; j < cols; j++)
    {
    MatrixType sum = 0;  //MatrixType, damit man die art der varianle automatisch an die Matrix anpasst in .h

    for(unsigned int k = 0; k < inner; k++)
      {
      MatrixType v1 = matrix1.buffer[i * matrix1.cols + k];
      MatrixType v2 = matrix2.buffer[k * matrix2.cols + j];
      sum += v1 * v2;
      }

      result.buffer[i * cols + j] = sum;
    }
 }
 return result;
}