#include "Matrix.h"

#include "IO.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <fstream>
#include <iostream>

Matrix::Matrix(std::size_t a_Raws, std::size_t a_Columns) : m_Raws(a_Raws), m_Columns(a_Columns) {
	m_Data.resize(m_Raws * m_Columns);
}

Matrix::Matrix(std::size_t a_Raws, std::size_t a_Columns, std::initializer_list<Element>&& a_Elements) :
	m_Raws(a_Raws), m_Columns(a_Columns) {
	m_Data = a_Elements;
	m_Data.resize(m_Raws * m_Columns);
}

Matrix Matrix::operator*(const Matrix& a_Other) const {
	assert(m_Columns == a_Other.m_Raws);

	Matrix result(m_Raws, a_Other.m_Columns);

	for (std::size_t i = 0; i < m_Raws; ++i) {
		for (std::size_t j = 0; j < a_Other.m_Columns; ++j) {
			Element sum = 0;
			for (std::size_t k = 0; k < m_Columns; k++) {
				sum += at(i, k) * a_Other.at(k, j);
			}
			result.at(i, j) = sum;
		}
	}
	return result;
}

void Matrix::Transpose() {
	Matrix result {m_Columns, m_Raws};
	for (std::size_t i = 0; i < m_Raws; i++) {
		for (std::size_t j = 0; j < m_Columns; j++) {
			result.at(j, i) = at(i, j);
		}
	}
	*this = result;
}

Matrix Matrix::Identity(std::size_t a_Size) {
	Matrix id {a_Size, a_Size};
	for (std::size_t i = 0; i < a_Size; i++) {
		for (std::size_t j = i; j < a_Size; j++) {
			id.at(i, j) = (i == j);
		}
	}
	return id;
}

Matrix Matrix::ColumnVector(std::initializer_list<Element>&& a_Elements) {
	Matrix result {a_Elements.size(), 1};

	result.m_Data = a_Elements;

	return result;
}

Matrix Matrix::RawVector(std::initializer_list<Element>&& a_Elements) {
	Matrix result {1, a_Elements.size()};

	result.m_Data = a_Elements;

	return result;
}

void Matrix::Fill(Element a_Element) {
	std::fill(GetLineIterator(0), GetLineIterator(m_Raws), a_Element);
}

void Matrix::Augment(const Matrix& a_Right) {
	assert(a_Right.m_Raws == m_Raws);
	Matrix temp {m_Raws, m_Columns + a_Right.m_Columns};

	for (std::size_t i = 0; i < m_Raws; i++) {
		for (std::size_t j = 0; j < m_Columns; j++) {
			temp.at(i, j) = at(i, j);
		}
	}

	for (std::size_t i = 0; i < m_Raws; i++) {
		for (std::size_t j = 0; j < a_Right.m_Columns; j++) {
			temp.at(i, j + m_Columns) = a_Right.at(i, j);
		}
	}

	*this = temp;
}

void Matrix::AugmentBottom(const Matrix& a_Bottom) {
	assert(a_Bottom.m_Columns == m_Columns);
	Matrix temp {m_Raws + a_Bottom.GetRawCount(), m_Columns};

	for (std::size_t i = 0; i < m_Raws; i++) {
		for (std::size_t j = 0; j < m_Columns; j++) {
			temp.at(i, j) = at(i, j);
		}
	}

	for (std::size_t i = 0; i < a_Bottom.GetRawCount(); i++) {
		for (std::size_t j = 0; j < GetColumnCount(); j++) {
			temp.at(i + GetRawCount(), j) = a_Bottom.at(i, j);
		}
	}

	*this = temp;
}

Matrix Matrix::operator+(const Matrix& a_Other) const {
	assert(GetColumnCount() == a_Other.GetColumnCount() && GetRawCount() == a_Other.GetRawCount());

	Matrix result = *this;

	for (std::size_t i = 0; i < GetRawCount(); i++) {
		for (std::size_t j = 0; j < GetColumnCount(); j++) {
			result.at(i, j) += a_Other.at(i, j);
		}
	}

	return result;
}

Matrix Matrix::operator-(const Matrix& a_Other) const {
	assert(GetColumnCount() == a_Other.GetColumnCount() && GetRawCount() == a_Other.GetRawCount());

	Matrix result = *this;

	for (std::size_t i = 0; i < GetRawCount(); i++) {
		for (std::size_t j = 0; j < GetColumnCount(); j++) {
			result.at(i, j) -= a_Other.at(i, j);
		}
	}

	return result;
}

bool Matrix::operator==(const Matrix& a_Other) const {
	assert(m_Raws == a_Other.m_Raws && m_Columns == a_Other.m_Columns);

	for (std::size_t i = 0; i < m_Raws; i++) {
		for (std::size_t j = 0; j < m_Columns; j++) {
			if (!IsEqualZero(at(i, j) - a_Other.at(i, j)))
				return false;
		}
	}

	return true;
}

Matrix::Element& Matrix::at(std::size_t a_Raw, std::size_t a_Column) {
	assert(a_Raw < m_Raws && a_Column < m_Columns);
	return m_Data[a_Raw * m_Columns + a_Column];
}

Matrix::Element Matrix::at(std::size_t a_Raw, std::size_t a_Column) const {
	assert(a_Raw < m_Raws && a_Column < m_Columns);
	return m_Data[a_Raw * m_Columns + a_Column];
}

std::size_t Matrix::GetRawCount() const {
	return m_Raws;
}

std::size_t Matrix::GetColumnCount() const {
	return m_Columns;
}

Matrix Matrix::SubMatrix(
	std::size_t a_RawOrigin, std::size_t a_ColumnOrigin, std::size_t a_RawCount, std::size_t a_ColumnCount) const {
	assert(m_Raws >= a_RawOrigin + a_RawCount && m_Columns >= a_ColumnOrigin + a_ColumnCount);

	Matrix result {a_RawCount, a_ColumnCount};

	for (std::size_t i = 0; i < result.GetRawCount(); i++) {
		for (std::size_t j = 0; j < result.GetColumnCount(); j++) {
			result.at(i, j) = at(i + a_RawOrigin, j + a_ColumnOrigin);
		}
	}

	return result;
}

Matrix::iterator Matrix::begin() {
	return m_Data.begin();
}

Matrix::iterator Matrix::end() {
	return m_Data.end();
}

Matrix::iterator Matrix::GetLineIterator(std::size_t a_Raw) {
	return m_Data.begin() + a_Raw * GetColumnCount();
}