package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// SequentialDeterminant рассчитывает детерминант матрицы методом разложения по первой строке
func SequentialDeterminant(matrix [][]float64) float64 {
	size := len(matrix)
	// Базовый случай: если матрица 1x1, то детерминант равен единственному элементу
	if size == 1 {
		return matrix[0][0]
	}
	// Рекурсивно вычисляем детерминант матрицы
	det := 0.0
	sign := 1.0
	for j := 0; j < size; j++ {
		// Разложение по первой строке
		submatrix := make([][]float64, size-1)
		for i := range submatrix {
			submatrix[i] = make([]float64, size-1)
		}
		for i := 1; i < size; i++ {
			for k := 0; k < size; k++ {
				if k < j {
					submatrix[i-1][k] = matrix[i][k]
				} else if k > j {
					submatrix[i-1][k-1] = matrix[i][k]
				}
			}
		}
		// Рекурсивный вызов для подматрицы
		det += sign * matrix[0][j] * SequentialDeterminant(submatrix)
		// Меняем знак для следующей итерации
		sign *= -1
	}
	return det
}

// ParallelDeterminant рассчитывает детерминант матрицы параллельно с использованием горутин
func ParallelDeterminant(matrix [][]float64, numWorkers int) float64 {
	size := len(matrix)
	// Базовый случай: если матрица 1x1, то детерминант равен единственному элементу
	if size == 1 {
		return matrix[0][0]
	}
	var wg sync.WaitGroup
	resultChan := make(chan float64, numWorkers)
	// Распределение работы между горутинами
	for j := 0; j < size; j++ {
		wg.Add(1)
		go func(col int) {
			defer wg.Done()
			// Разложение по первой строке
			submatrix := make([][]float64, size-1)
			for i := range submatrix {
				submatrix[i] = make([]float64, size-1)
			}
			for i := 1; i < size; i++ {
				for k := 0; k < size; k++ {
					if k < col {
						submatrix[i-1][k] = matrix[i][k]
					} else if k > col {
						submatrix[i-1][k-1] = matrix[i][k]
					}
				}
			}
			// Рекурсивный вызов для подматрицы и отправка результата в канал
			resultChan <- matrix[0][col] * ParallelDeterminant(submatrix, numWorkers)
		}(j)
	}
	// Закрываем канал после завершения всех горутин
	go func() {
		wg.Wait()
		close(resultChan)
	}()
	// Сбор результатов из канала и вычисление итогового детерминанта
	det := 0.0
	sign := 1.0
	for result := range resultChan {
		det += sign * result
		sign *= -1
	}
	return det
}

func runTest(matrixSize, numProcesses int) {
	matrix := generateRandomMatrix(matrixSize, matrixSize)

	startTime := time.Now()
	_ = SequentialDeterminant(matrix)
	sequentialTime := time.Since(startTime)
	fmt.Printf("Sequential matrix Determinant took (%dx%d): %s\n", matrixSize, matrixSize, sequentialTime)

	startTime = time.Now()
	_ = ParallelDeterminant(matrix, numProcesses)
	parallelTime := time.Since(startTime)
	fmt.Printf("Parallel matrix Determinant with %d threads took (%dx%d): %s\n", numProcesses, matrixSize, matrixSize, parallelTime)
}

func generateRandomMatrix(rows, cols int) [][]float64 {
	matrix := make([][]float64, rows)
	for i := range matrix {
		matrix[i] = make([]float64, cols)
		for j := range matrix[i] {
			matrix[i][j] = rand.Float64()
		}
	}
	return matrix
}

func main() {
	// Пример использования функций
	runTest(6, 2)
	runTest(6, 4)
	runTest(8, 2)
	runTest(8, 4)
	runTest(10, 2)
	runTest(10, 4)
}