func Multiply(A *matrix.Matrix, B *matrix.Matrix) *matrix.Matrix {
	n := A.CountRows()
	bigN := scaleSize(n)

	bigA := matrix.MakeMatrix(make([]float64, bigN*bigN), bigN, bigN)
	bigB := matrix.MakeMatrix(make([]float64, bigN*bigN), bigN, bigN)

	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			bigA.SetElm(i, j, A.GetElm(i, j))
			bigB.SetElm(i, j, B.GetElm(i, j))
		}
	}

	bigC := recurse(bigA, bigB)

	C := matrix.MakeMatrix(make([]float64, n*n), n, n)
	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			C.SetElm(i, j, bigC.GetElm(i, j))
		}
	}

	return C
}
func BenchmarkStrassen(b *testing.B) {
	a := make([]float64, 100000000, 100000000)
	c := make([]float64, 100000000, 100000000)
	for i := 0; i < 100000000; i++ {
		a[i] = float64(i)
		c[i] = float64(100000000 - i)
	}
	A := matrix.MakeMatrix(a, 10000, 10000)
	B := matrix.MakeMatrix(c, 10000, 10000)

	for i := 0; i < b.N; i++ {
		Multiply(A, B)
	}
}
func TestStrassen(t *testing.T) {
	A := matrix.MakeMatrix([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 4, 4)
	B := matrix.MakeMatrix([]float64{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, 4, 4)

	C := Multiply(A, B)
	D := matrix.Multiply(A, B)
	if !FloatArrayEquals(C.Elements, D.Elements) {
		t.Error()
	}

	A = matrix.MakeMatrix([]float64{1, 1, 1, 1}, 2, 2)
	B = matrix.MakeMatrix([]float64{1, 1, 1, 1}, 2, 2)

	C = Multiply(A, B)
	D = matrix.Multiply(A, B)
	if !FloatArrayEquals(C.Elements, D.Elements) {
		t.Error()
	}

	A = matrix.MakeMatrix([]float64{1, 1, 1, 1, 1, 1, 1, 1, 1}, 3, 3)
	B = matrix.MakeMatrix([]float64{1, 1, 1, 1, 1, 1, 1, 1, 1}, 3, 3)

	C = Multiply(A, B)
	D = matrix.Multiply(A, B)
	if !FloatArrayEquals(C.Elements, D.Elements) {
		t.Error()
	}
}
func recurse(A *matrix.Matrix, B *matrix.Matrix) *matrix.Matrix {
	n := A.CountRows()

	newSize := n / 2

	if n < 2 {
		return matrix.MakeMatrix([]float64{A.GetElm(0, 0) * B.GetElm(0, 0)}, 1, 1)
	}

	A11 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)
	A12 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)
	A21 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)
	A22 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)

	B11 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)
	B12 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)
	B21 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)
	B22 := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)

	for i := 0; i < newSize; i++ {
		for j := 0; j < newSize; j++ {
			A11.SetElm(i, j, A.GetElm(i, j))
			A12.SetElm(i, j, A.GetElm(i, j+newSize))
			A21.SetElm(i, j, A.GetElm(i+newSize, j))
			A22.SetElm(i, j, A.GetElm(i+newSize, j+newSize))

			B11.SetElm(i, j, B.GetElm(i, j))
			B12.SetElm(i, j, B.GetElm(i, j+newSize))
			B21.SetElm(i, j, B.GetElm(i+newSize, j))
			B22.SetElm(i, j, B.GetElm(i+newSize, j+newSize))
		}
	}

	a := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)
	b := matrix.MakeMatrix(make([]float64, newSize*newSize), newSize, newSize)

	// P1 = (A11 + A22) * (B11 + B22)
	a = matrix.Add(A11, A22)
	b = matrix.Add(B11, B22)
	P1 := recurse(a, b)

	// P2 = (A21 + A22) * (B11)
	a = matrix.Add(A21, A22)
	P2 := recurse(a, B11)

	// P3 = (A11) * (B12 - B11)
	b = matrix.Substract(B12, B22)
	P3 := recurse(A11, b)

	// P4 = A22 * (B21 - B22)
	b = matrix.Substract(B21, B11)
	P4 := recurse(A22, b)

	// P5 = (A11 + A12) * B22
	a = matrix.Add(A11, A12)
	P5 := recurse(a, B22)

	// P6 = (A21 - A11) * (B11 + B12)
	a = matrix.Substract(A21, A11)
	b = matrix.Add(B11, B12)
	P6 := recurse(a, b)

	// P7 = (A12 - A22) * (B21 + B22)
	a = matrix.Substract(A12, A22)
	b = matrix.Add(B21, B22)
	P7 := recurse(a, b)

	// Aggregates the result into C
	C12 := matrix.Add(P3, P5)
	C21 := matrix.Add(P2, P4)

	a = matrix.Add(P1, P4)
	b = matrix.Add(a, P7)
	C11 := matrix.Substract(b, P5)

	a = matrix.Add(P1, P3)
	b = matrix.Add(a, P6)
	C22 := matrix.Substract(b, P2)

	C := matrix.MakeMatrix(make([]float64, n*n), n, n)

	for i := 0; i < newSize; i++ {
		for j := 0; j < newSize; j++ {
			C.SetElm(i, j, C11.GetElm(i, j))
			C.SetElm(i, j+newSize, C12.GetElm(i, j))
			C.SetElm(i+newSize, j, C21.GetElm(i, j))
			C.SetElm(i+newSize, j+newSize, C22.GetElm(i, j))
		}
	}

	return C
}