#include <iostream>
#include <cstdlib>
#include <ctime>
#include <fstream>

using namespace std;

#include <iostream>
#include <cassert>

using namespace std;

class Matrix
{
private:
    class CountingReference
    {
    private:
        int * countingReference;
    public:
        int wiersz;
        int kolumna;
        double **wsk;
        CountingReference();
        CountingReference(int, int);
        CountingReference(const CountingReference &);
        ~CountingReference();
        CountingReference& operator=(CountingReference rhs);
    };
    CountingReference dane;

public:

    friend ostream& operator<<(ostream& o, const Matrix&);
    friend ostream& operator<<(const Matrix&, ostream& o);
    Matrix();
    Matrix(int, int);
    static Matrix clone(const Matrix &);
    Matrix operator+(const Matrix &) const;
    Matrix operator-(const Matrix &) const;
    Matrix operator*(const Matrix &) const;
    Matrix operator+=(const Matrix&);
    Matrix& operator-=(const Matrix&);
    Matrix& operator*=(const Matrix&);
    bool operator ==(const Matrix &);
    friend Matrix& Random(Matrix&);
};
ostream& operator<<(ostream& o, const Matrix& Object)
{
    for (int i = 0; i < Object.dane.wiersz; i++)
    {
        for (int j = 0; j < Object.dane.kolumna; j++)
        {
            o << Object.dane.wsk[i][j] << " ";
        }
        o << endl;
    }
    return o;
}

Matrix::Matrix() :
        dane()
{
}

Matrix::Matrix(int wiersz, int col) :
        dane(wiersz, col)
{
}

Matrix Matrix::clone(const Matrix &Object)
{
    Matrix result(Object.dane.wiersz,
                  Object.dane.kolumna);
    for (int i = 0; i < result.dane.wiersz; i++)
    {
        for (int j = 0; j < result.dane.kolumna; j++)
        {
            result.dane.wsk[i][j] = Object.dane.wsk[i][j];
        }
    }
    return result;
}

Matrix Matrix::operator+(const Matrix &Object) const
{
    return Matrix::clone(*this) +=Object;
}

Matrix Matrix::operator-(const Matrix &Object) const
{
    return Matrix::clone(*this) -=Object;
}

Matrix Matrix::operator*(const Matrix &Object) const
{
    return Matrix::clone(*this) *=Object;
}

Matrix Matrix::operator+=(const Matrix& Object)
{
    if (dane.wiersz != Object.dane.wiersz
            || dane.kolumna != Object.dane.kolumna)
    {
        string wyjatek = "Nie sa rowne.";
        throw wyjatek;
    }
    else
    {
        for (int i = 0; i < dane.wiersz; i++)
        {
            for (int j = 0; j < dane.kolumna; j++)
            {
                dane.wsk[i][j] += Object.dane.wsk[i][j];
            }
        }
    }

    return *this;
}

Matrix& Matrix::operator-=(const Matrix& Object)
{
    if (dane.wiersz != Object.dane.wiersz
            || dane.kolumna != Object.dane.kolumna)
    {
        string wyjatek = "Nie sa rowne.";
        throw wyjatek;
    }
    else
    {
        for (int i = 0; i < dane.wiersz; i++)
        {
            for (int j = 0; j < dane.kolumna; j++)
            {
                dane.wsk[i][j] -= Object.dane.wsk[i][j];
            }
        }
    }
    return *this;
}

Matrix& Matrix::operator*=(const Matrix& Object)
{
    if (dane.kolumna != Object.dane.wiersz)
    {
        string wyjatek = "Nie sa rowne.";
        throw wyjatek;
    }
    else
    {
        int m = 0, n = 0;
        double temp = 0;
        m = dane.wiersz;
        n = Object.dane.kolumna;
        Matrix A(m, n);
        for (int i = 0; i < dane.wiersz; i++)
        {
            for (int j = 0; j < Object.dane.kolumna; j++)
            {
                for (int k = 0; k < dane.kolumna; k++)
                {
                    temp += dane.wsk[i][k] * Object.dane.wsk[k][j];
                }

                A.dane.wsk[i][j] = temp;
                temp = 0;
            }
        }

        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                dane.wsk[i][j] = A.dane.wsk[i][j];
            }
        }
    }
    return *this;
}

bool Matrix::operator ==(const Matrix& Object)
{
    int i, j;
    for (i = 0; i < dane.wiersz; i++)
    {
        for (j = 0; j < dane.kolumna; j++)
        {
            if (dane.wsk[i][j] != Object.dane.wsk[i][j])
            {
                return false;
            }
        }
    }
    return true;
}

Matrix& Random(Matrix& Object)
{
    for (int i = 0; i < Object.dane.wiersz; i++)
    {
        for (int j = 0; j < Object.dane.kolumna; j++)
        {
            Object.dane.wsk[i][j] = rand() % 100 + 1;
        }
    }

    return Object;
}

Matrix::CountingReference::CountingReference() :
        countingReference(new int(1)), wiersz(0), kolumna(0), wsk(NULL)

{
}

Matrix::CountingReference::CountingReference(int wier, int kol) :
        countingReference(new int(1)), wiersz(wier), kolumna(kol), wsk(new double*[wier])
{
    for (int i = 0; i < wier; i++)
    {
        wsk[i] = new double[kol];
    }
    cout << countingReference << " was created \n";
}

Matrix::CountingReference::CountingReference(const CountingReference & src) :
        countingReference(src.countingReference), wiersz(src.wiersz), kolumna(src.kolumna), wsk(src.wsk)

{
    ++(*countingReference);
    cout << countingReference << " was copied (" << *countingReference << ")\n";
}

Matrix::CountingReference::~CountingReference()
{
    --(*countingReference);
    cout << countingReference << " was decremented (" << *countingReference << ")\n";
    if (!*countingReference)
    {
        cout << countingReference << " was destroyed\n";
        for (int i = 0; i < wiersz; i++)
        {
            delete[] wsk[i];
        }
        delete[] wsk;
        delete countingReference;
    }
}
Matrix::CountingReference & Matrix::CountingReference::operator=(CountingReference rhs)
{
    cout << countingReference << " was replaced by " << rhs.countingReference << "\n";
    swap(countingReference, rhs.countingReference);
    swap(wsk, rhs.wsk);
    swap(wiersz, rhs.wiersz);
    swap(kolumna, rhs.kolumna);
    return *this;
}

int main()
{
    cout << "Create A\n";
    Matrix A(3, 3);
    cout << "Create B\n";
    Matrix B(3, 3);
    cout << "Create C\n";
    Matrix C(3, 3);
    cout << "Create D\n";
    Matrix D(3, 3);
    Matrix F(); // declares function F. See Most Vexing Parse

    Random(B);
    Random(C);
    Random(D);

    cout << "B: " << endl << B << endl;
    cout << "C: " << endl << C << endl;
    cout << "D: " << endl << D << endl;

    cout << "A = B + C\n";
    A = B + C;
    cout << "A = B\n";
    A = B;
    cout << "B = C\n";
    B = C;
    cout << "C = D\n";
    C = D;
    cout << "A = B\n";
    A = B;
    cout << "B = C\n";
    B = C;
    cout << "A = B\n";
    A = B;
    cout << "Done. Cleaning up\n";
    return 0;
}