using System.ComponentModel;

namespace omp
{
    public static class MatrixExtensions
    {
        public static void Print(this double[,] a)
        {
            int rows = a.GetLength(0);
            int columns = a.GetLength(1);

            Console.WriteLine("Matrix of size {0}x{1}:", rows, columns);
            for (int i = 0; i < rows; ++i)
            {
                for (int j = 0; j < columns; ++j)
                {
                    Console.Write("\t{0}", a[i, j]);
                }
                Console.WriteLine("");
            }
        }

        public static void SaveToFile(this double[,] a, string filePath)
        {
            int rows = a.GetLength(0);
            int cols = a.GetLength(0);

            using (var stream = new FileStream(filePath, FileMode.Create))
            using (var writer = new BinaryWriter(stream))
            {
                unsafe
                {
                    fixed (double* ptr = &a[0, 0])
                    {
                        byte* bytePtr = (byte*)ptr;
                        int byteCount = rows * cols * sizeof(double);

                        writer.Write(new ReadOnlySpan<byte>(bytePtr, byteCount));
                    }
                }
            }
        }

        public static double Adj(this double[,] a, int i, int j)
        {
            double sgn = ((i + j) % 2 == 0) ? 1 : -1;

            return sgn * Det(Minor(a, j, i));
        }

        public static double[,] Minor(this double[,] a, int i, int j)
        {
            int rows = a.GetLength(0);
            int columns = a.GetLength(1);

            double[,] minor = new double[rows - 1, columns - 1];

            int k = 0;
            int l;

            for (int r = 0; r < rows; ++r)
            {
                if (r == i)
                {
                    continue;
                }

                l = 0;

                for (int c = 0; c < columns; ++c)
                {
                    if (c == j)
                    {
                        continue;
                    }

                    minor[k, l] = a[r, c];

                    l++;
                }

                k++;
            }

            return minor;
        }

        public static double Det(this double[,] a)
        {
            int n = a.GetLength(0);

            switch (n)
            {
                case 1:
                    return a[0, 0];
                case 2:
                    return Det2(a);
                case 3:
                    return Det3(a);
                default:
                    double det = 0;

                    double sgn;
                    for (int j = 0; j < n; ++j)
                    {
                        sgn = (j % 2 == 0) ? 1 : -1;
                        det += sgn * a[0, j] * Det(Minor(a, 0, j));
                    }

                    return det;
            }
        }

        public static double Det2(this double[,] a)
        {
            return a[0, 0] * a[1, 1] - a[0, 1] * a[1, 0];
        }


        public static double Det3(double[,] a)
        {
            if (a.GetLength(0) != 3 || a.GetLength(1) != 3)
                throw new ArgumentException("Matrix must be 3x3");

            return
                a[0, 0] * a[1, 1] * a[2, 2]
                + a[0, 1] * a[1, 2] * a[2, 0]
                + a[0, 2] * a[1, 0] * a[2, 1]
                - a[0, 2] * a[1, 1] * a[2, 0]
                - a[0, 0] * a[1, 2] * a[2, 1]
                - a[2, 2] * a[0, 1] * a[1, 0];
        }
        public static double[,] MultTrans(this double[,] a)
        {
            int rows = a.GetLength(0);
            int columns = a.GetLength(1);

            double[,] r = new double[rows, rows];

            for (int i = 0; i < rows; ++i)
            {
                for (int j = 0; j < rows; ++j)
                {
                    for (int k = 0; k < columns; ++k)
                    {
                        r[i, j] += a[i, k] * a[j, k];
                    }
                }
            }

            return r;
        }

        public static (double Min, double Max) MinMax(this double[,] a)
        {
            double min = double.MaxValue;
            double max = double.MinValue;

            object MMLock = new();

            Parallel.For(0, a.GetLength(0), i =>
            {
                double localMin = double.MaxValue;
                double localMax = double.MinValue;

                for (int j = 0; j < a.GetLength(1); ++j)
                {
                    if (a[i, j] < localMin)
                    {
                        localMin = a[i, j];
                    }
                    if (a[i, j] > localMax)
                    {
                        localMax = a[i, j];
                    }
                }

                lock (MMLock)
                {
                    if (localMin < min)
                    {
                        min = localMin;
                    }

                    if (localMax > max)
                    {
                        max = localMax;
                    }

                }
            });

            return (min, max);
        }
    }
}
