// Created with MonoDevelop
//
//    Copyright (C) 2023 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Linq;
using System.Collections.Generic;

using Hiperscan.Unix;

using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.LinearAlgebra.Factorization;


namespace Hiperscan.Spectroscopy.Math
{
    public partial class LinearTransformation
    {
        // RW 2D/Matrix
        public static double[,] ReadQuadraticArrayFromCsv(string filename)
        {
            return LinearTransformation.ReadQuadraticArrayFromCsv(Hiperscan.Spectroscopy.IO.PlainCSV.Read(filename));
        }

        public static double[,] ReadQuadraticArrayFromCsv(Dictionary<int,List<string>> csv_data)
        {
            var tmp_csv_data = csv_data.Values.AsEnumerable();

            int line_count = tmp_csv_data.Count();

            double[,] array2d = new double[line_count, line_count];

            int i = 0;
            int j = 0;

            foreach (var line in tmp_csv_data)
            {
                if (line_count != line.Count())
                    throw new Exception(Catalog.GetString("The array defined in the CSV file is not quadratic."));

                j = 0;
                foreach (var cell in line)
                {
                    array2d[i, j++] = double.Parse(cell, System.Globalization.CultureInfo.InvariantCulture);
                }
                i++;
            }

            return array2d;
        }

        public static double[][] ReadDiagonalArrayFromCsv(string filename)
        {
            return LinearTransformation.ReadDiagonalArrayFromCsv(Hiperscan.Spectroscopy.IO.PlainCSV.Read(filename));
        }

        public static double[][] ReadDiagonalArrayFromCsv(Dictionary<int, List<string>> csv_data)
        {
            var tmp_csv_data = csv_data.Values.AsEnumerable();

            double[][] array2d = new double[tmp_csv_data.Count()][];

            int i = 0;
            foreach (var line in tmp_csv_data)
                array2d[i++] = line.Select(x => double.Parse(x, System.Globalization.CultureInfo.InvariantCulture)).ToArray();
                
            return array2d;
        }

        public static void WriteQuadraticMatrixToCsv(Matrix matrix, string filename, bool append = false)
        {
            if (matrix.RowCount != matrix.ColumnCount)
                throw new Exception("Matrix is not quadratic.");

            Dictionary<int, List<string>> data = new Dictionary<int, List<string>>();

            for (int i = 0; i < matrix.RowCount; i++)
            {
                List<string> line = new List<string>(matrix.Row(0).Select(x => x.ToString(System.Globalization.CultureInfo.InvariantCulture)));
                data.Add(++i, line);
            }

            Hiperscan.Spectroscopy.IO.PlainCSV.Write(data, filename, append, false);
        }

        public static void WriteDiagonalMatrixToCsv(Matrix matrix, string filename, bool append = false)
        {
            if (matrix.RowCount != matrix.ColumnCount)
                throw new Exception("Matrix is not quadratic.");

            List<IEnumerable<double>> diagonals = new List<IEnumerable<double>>();

            diagonals.Add(matrix.Diagonal());

            for (int i = 1; i < matrix.ColumnCount / 2 - 1; i++)
            {
                List<double> upper_diag = new List<double>();
                List<double> lower_diag = new List<double>();

                for (int j = 0; j < matrix.RowCount - i; j++)
                {
                    upper_diag.Add(matrix[j, j + i]);
                    lower_diag.Add(matrix[j + i, j]);
                }

                if (upper_diag.Any(x => System.Math.Abs(x) >= double.Epsilon) == false &&
                    lower_diag.Any(x => System.Math.Abs(x) >= double.Epsilon) == false)
                    break;

                diagonals.Insert(0, upper_diag);
                diagonals.Add(lower_diag);
            }

            Dictionary<int, List<string>> data = new Dictionary<int, List<string>>();

            for (int i = 0; i < diagonals.Count(); i++)
                data.Add(i, diagonals[i].Select(x => x.ToString(System.Globalization.CultureInfo.InvariantCulture)).ToList());

            Hiperscan.Spectroscopy.IO.PlainCSV.Write(data, filename, append, false);
        }

        // RW 1D/Vector
        public static double[] ReadArrayFromCsv(string filename)
        {
            return LinearTransformation.ReadArrayFromCsv(Hiperscan.Spectroscopy.IO.PlainCSV.Read(filename));
        }

        public static double[] ReadArrayFromCsv(Dictionary<int,List<string>> csv_data)
        {
            var tmp_csv_data = csv_data.Values.AsEnumerable();

            if (tmp_csv_data.Count() > 1)
                throw new Exception(Catalog.GetString("The array defined in the CSV file may not have more than one row"));

            return tmp_csv_data.ElementAt(0).Select(x => double.Parse(x, System.Globalization.CultureInfo.InvariantCulture)).ToArray();
        }

        public static void WriteVectorToCsv(Vector vector, string filename, bool append = false)
        {
            Dictionary<int, List<string>> data = new Dictionary<int, List<string>>();

            data.Add(0, vector.Select(x => x.ToString(System.Globalization.CultureInfo.InvariantCulture)).ToList());

            Hiperscan.Spectroscopy.IO.PlainCSV.Write(data, filename, append, false);
        }

        // Linear Transformation
        public static LinearTransformation FromCsvFile(Dictionary<int, List<string>> csv_data_b_vector,
                                                       Dictionary<int, List<string>> csv_data_f_matrix)
        {
            bool is_quadratic = csv_data_f_matrix.Count() == csv_data_f_matrix.First().Value.Count();

            return LinearTransformation.FromCsvFile(csv_data_b_vector, csv_data_f_matrix, is_quadratic == false);
        }

        public static LinearTransformation FromCsvFile(string filename_b_vector, string filename_f_matrix)
        {
            var b_vector = string.IsNullOrEmpty(filename_b_vector) == false ? IO.PlainCSV.Read(filename_b_vector) : null;
            var f_matrix = IO.PlainCSV.Read(filename_f_matrix);

            return LinearTransformation.FromCsvFile(b_vector, f_matrix);
        }

        public static LinearTransformation FromCsvFile(string filename_f_matrix)
        {
            return LinearTransformation.FromCsvFile(null, filename_f_matrix);
        }

        public static LinearTransformation FromCsvFile(Dictionary<int,List<string>> csv_data_b_vector, 
                                                       Dictionary<int, List<string>> csv_data_f_matrix,
                                                       bool diagonal_matrix)
        {
            Matrix f = null;

            if (diagonal_matrix)
                f = LinearTransformation.ConvertFromDiagonalArrayToMatrix(LinearTransformation.ReadDiagonalArrayFromCsv(csv_data_f_matrix));
            else
                f = LinearTransformation.ConvertFromArrayToMatrix(LinearTransformation.ReadQuadraticArrayFromCsv(csv_data_f_matrix));

            Vector b = null;
            if (csv_data_b_vector != null && csv_data_b_vector.Count > 0)
                b = LinearTransformation.ConvertFromArraytoVector(LinearTransformation.ReadArrayFromCsv(csv_data_b_vector));
            else
                b = new SparseVector(f.ColumnCount);

            return new LinearTransformation(b, f);
        }

        public static LinearTransformation FromCsvFile(string filename_b_vector, string filename_f_matrix, bool diagonal_matrix)
        {
            var b_vector = string.IsNullOrEmpty(filename_b_vector) == false ? IO.PlainCSV.Read(filename_b_vector) : null;
            var f_matrix = IO.PlainCSV.Read(filename_f_matrix);

            return LinearTransformation.FromCsvFile(b_vector, f_matrix, diagonal_matrix);
        }

        public static LinearTransformation FromCsvFile(string filename_f_matrix, bool diagonal_matrix)
        {
            return LinearTransformation.FromCsvFile(null, filename_f_matrix, diagonal_matrix);
        }

        public static void ToCsvFile(LinearTransformation linear_transformation, string filename_b_vector, string filename_f_matrix, bool diagonal_matrix = true)
        {
            if (string.IsNullOrEmpty(filename_b_vector) == false)
                LinearTransformation.WriteVectorToCsv(linear_transformation.B, filename_b_vector, false);

            if (diagonal_matrix)
                LinearTransformation.WriteDiagonalMatrixToCsv(linear_transformation.F, filename_f_matrix);
            else
                LinearTransformation.WriteQuadraticMatrixToCsv(linear_transformation.F, filename_f_matrix);
        }

        public static void ToCsvFile(LinearTransformation linear_transformation, string filename_f_matrix, bool diagonal_matrix)
        {
            LinearTransformation.ToCsvFile(linear_transformation, null, filename_f_matrix, diagonal_matrix);
        }

        public void ToCsvFile(string filename_b_vector, string filename_f_matrix, bool diagoinal_matrix = true)
        { 
            LinearTransformation.ToCsvFile(this, filename_b_vector, filename_f_matrix, diagoinal_matrix);
        }

        public void ToCsvFile(string filename_f_matrix, bool diagonal_matrix = true)
        {
            LinearTransformation.ToCsvFile(this, null, filename_f_matrix, diagonal_matrix);
        }
    }
}
