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

Matrix createMatrix(unsigned int rows, unsigned int cols)
{
    Matrix mat = {.rows = rows, .cols = cols};
    // If one dimension is 0, return both dimensions as 0 and don't init the array/buffer.
    if (rows == 0 || cols == 0)
    {
        mat.rows = 0;
        mat.cols = 0;
        return mat;
    }

    // allocate contiguous and 0 initialized memory
    mat.buffer = calloc(rows * cols, sizeof(MatrixType));

    // check if calloc failed
    if (mat.buffer == NULL)
    {
        clearMatrix(&mat);
    }

    return mat;
}

// reduce the dimensions to (0, 0) and free the memory
void clearMatrix(Matrix *matrix)
{
    free(matrix->buffer);
    matrix->buffer = NULL;
    matrix->cols = 0;
    matrix->rows = 0;
}

void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{
    // do nothing if idx is not in array or matrix buffer is NULL
    if (!(rowIdx < matrix.rows) || !(colIdx < matrix.cols) || matrix.buffer == NULL)
    {
        return;
    }

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

MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
{
    // return UNDEFINED_MATRIX_VALUE if idx is not in array or matrix buffer is NULL
    if (!(rowIdx < matrix.rows) || !(colIdx < matrix.cols) || matrix.buffer == NULL)
    {
        return UNDEFINED_MATRIX_VALUE;
    }

    return matrix.buffer[rowIdx * matrix.cols + colIdx];
};

Matrix add(const Matrix matrix1, const Matrix matrix2)
{
    Matrix resMat = (matrix1.cols > matrix2.cols) ? createMatrix(matrix1.rows, matrix1.cols) : createMatrix(matrix2.rows, matrix2.cols);

    if (matrix1.cols != matrix2.cols)
    {
        if (matrix1.rows != matrix2.rows)
        {
            clearMatrix(&resMat);
            return resMat;
        }
        else if (matrix1.cols == 1)
        {
            // broadcast vector
            for (size_t m = 0; m < matrix2.rows; m++)
            {
                for (size_t n = 0; n < matrix2.cols; n++)
                {
                    setMatrixAt(getMatrixAt(matrix2, m, n) + getMatrixAt(matrix1, m, 0), resMat, m, n);
                }
            }
            return resMat;
        }
        else if (matrix2.cols == 1)
        {
            // broadcast vector
            for (size_t m = 0; m < matrix1.rows; m++)
            {
                for (size_t n = 0; n < matrix1.cols; n++)
                {
                    setMatrixAt(getMatrixAt(matrix1, m, n) + getMatrixAt(matrix2, m, 0), resMat, m, n);
                }
            }
            return resMat;
        }
        else
        {
            clearMatrix(&resMat);
            return resMat;
        }
    }

    for (size_t m = 0; m < matrix1.rows; m++)
    {
        for (size_t n = 0; n < matrix1.cols; n++)
        {
            // this is unnecessarily complicated because at this point we already know that the matrices are compatible
            setMatrixAt(getMatrixAt(matrix1, m, n) + getMatrixAt(matrix2, m, n), resMat, m, n);
        }
    }

    return resMat;
}

// TODO implement
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
{
}