using System.Diagnostics;

internal class Program
{
    private static string[] _resTableColumnsNames = new string[] { "m*m", "1 t", "2 t", "3 t", "4 t", "5 t", "6 t"};
    private static void Main(string[] args)
    {
        Console.WriteLine(string.Join("\t", _resTableColumnsNames));
        for (int i = 8; i < 14; i++)
        {
            var e = CreateMatrix(i, i);

            List<long> times = new() { i };

            for (int j = 1; j <= 6; j++)
            {
                var sw = new Stopwatch();
                sw.Start();

                CalculateDeterminant(e, j);

                sw.Stop();
                times.Add(sw.ElapsedMilliseconds);
            }

            Console.WriteLine(string.Join("\t", times));
        }
    }

    private static int[,] CreateMatrix(int x, int y)
    {
        var rnd = new Random();

        var res = new int[y, x];

        for (int i = 0; i < y; i++)
        {
            for (int j = 0; j < x; j++)
            {
                res[i, j] = rnd.Next(0, 100);
            }
        }
        return res;
    }

    private static void PritMatrix(int[,] mx)
    {
        for (int i = 0; i < mx.GetLength(0); i++)
        {
            for (int j = 0; j < mx.GetLength(1); j++)
            {
                Console.Write($"{mx[i, j].ToString("000")}\t");
            }
            Console.WriteLine();
        }
    }

    public static long CalculateDeterminant(int[,] matrix, int maxThreads)
        => CalculateDeterminantParalel(
                matrix, 
                Enumerable.Range(0, matrix.GetLength(0)).ToArray(), 
                Enumerable.Range(0, matrix.GetLength(1)).ToArray(), 
                maxThreads
            );

    private static long CalculateDeterminant(int[,] matrix, int[] rows, int[] columns)
    {
        if (rows.Length <= 3)
            return GetSimpleDeterminant(matrix, rows.ToList(), columns.ToList());

        (int fixIndex, bool isFixRow) = GetTheBestRowColumn(matrix, rows, columns);

        long res = 0;
        int row = isFixRow ? rows[fixIndex] : -1;
        int column = isFixRow ? -1 : columns[fixIndex];

        for (int index = 0; index < rows.Length; index++)
        {
            if (isFixRow)
                column = columns[index];
            else
                row = rows[index];

            res += ((index + fixIndex) % 2 != 0 ? -1 : 1) * matrix[column, row] *
                CalculateDeterminant(matrix, rows.Where(x => x != row).ToArray(), columns.Where(x => x != column).ToArray());
        }

        return res;
    }

    private static long CalculateDeterminantParalel(int[,] matrix, int[] rows, int[] columns, int maxThread)
    {
        if (rows.Length <= 3)
            return GetSimpleDeterminant(matrix, rows.ToList(), columns.ToList());

        (int fixIndex, bool isFixRow) = GetTheBestRowColumn(matrix, rows, columns);

        long res = 0;
        int row = isFixRow ? rows[fixIndex] : -1;
        int column = isFixRow ? -1 : columns[fixIndex];

        var semaphore = new SemaphoreSlim(maxThread, maxThread);

        for (int index = 0; index < rows.Length; index++)
        {
            if (isFixRow)
                column = columns[index];
            else
                row = rows[index];

            var c_column = column;
            var c_row = row;
            var c_index = fixIndex + index;
            semaphore.Wait();
            Task.Run(() =>
            {
                try
                {
                    res += (c_index % 2 != 0 ? -1 : 1) * matrix[c_column, c_row] *
                        CalculateDeterminant(
                                matrix,
                                rows.Where(x => x != c_row).ToArray(),
                                columns.Where(x => x != c_column).ToArray()
                             );
                }
                finally { semaphore.Release(); }
            });
        }

        semaphore.Wait(maxThread);
        return res;
    }

    private static (int fixIndex, bool isFixRow) GetTheBestRowColumn(int[,] matrix, int[] rows, int[] columns)
    {
        Dictionary<int, int> zerosCount = rows.Select(x => x + 1).Union(columns.Select(x => -x - 1)).ToDictionary(x => x, x => 0);

        foreach (var r in rows)
        {
            foreach (var c in columns)
            {
                if (matrix[r, c] == 0)
                {
                    zerosCount[r + 1] += 1;
                    zerosCount[-c - 1] += 1;
                }
            }
        }

        var best = zerosCount.ToList().OrderByDescending(x => x.Value).FirstOrDefault().Key;

        if (best > 0)
        {
            return (Array.IndexOf(rows, best - 1), true);
        }
        else
        {
            return (Array.IndexOf(columns, -best + 1), false);
        }
    }

    private static long GetSimpleDeterminant(int[,] matrix, List<int> rows, List<int> columns)
    {
        if (rows.Count == 1)
        {
            return matrix[columns[0], rows[0]];
        }
        else if (rows.Count == 2)
        {
            return matrix[columns[0], rows[0]] * matrix[columns[1], rows[1]] - matrix[columns[0], rows[1]] * matrix[columns[1], rows[0]];
        }
        else if (columns.Count == 3)
        {
            return matrix[columns[0], rows[0]] * matrix[columns[1], rows[1]] * matrix[columns[2], rows[2]] +
                    matrix[columns[0], rows[1]] * matrix[columns[1], rows[2]] * matrix[columns[2], rows[0]] +
                    matrix[columns[0], rows[2]] * matrix[columns[1], rows[0]] * matrix[columns[2], rows[1]] -

                    matrix[columns[0], rows[2]] * matrix[columns[1], rows[1]] * matrix[columns[2], rows[0]] -
                    matrix[columns[1], rows[0]] * matrix[columns[0], rows[1]] * matrix[columns[2], rows[2]] -
                    matrix[columns[0], rows[0]] * matrix[columns[1], rows[2]] * matrix[columns[2], rows[1]];
        }
        throw new Exception();
    }
}