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

namespace LabelerCore.Model.Math
{
    static public class Stats
    {
        /// <summary>
        /// Return the minimum along an axis.
        /// </summary>
        /// <param name="X"></param>
        /// <param name="axis"></param>
        /// <returns></returns>
        static public double[] Min(double[][] X, short axis=0)
        {
            if(axis == 1)
            {
                double[] ret = new double[X.Length];
                for (int i = 0; i < X.Length; i++)
                    ret[i] = X[i].Min();
                return ret;
            }
            else
            {
                double[] ret = new double[X[0].Length];
                for(int j = 0; j < X[0].Length; j++)
                {
                    double min = double.MaxValue;
                    for(int i = 0; i < X.Length; i++)
                    {
                        if (X[i][j] < min)
                            min = X[i][j];
                    }
                    ret[j] = min;
                }
                return ret;
            }
        }

        /// <summary>
        /// Return the minimum along an axis.
        /// </summary>
        /// <param name="X"></param>
        /// <param name="axis"></param>
        /// <returns></returns>
        static public double[] Max(double[][] X, short axis = 0)
        {
            if (axis == 1)
            {
                double[] ret = new double[X.Length];
                for (int i = 0; i < X.Length; i++)
                    ret[i] = X[i].Max();
                return ret;
            }
            else
            {
                double[] ret = new double[X[0].Length];
                for (int j = 0; j < X[0].Length; j++)
                {
                    double max = double.MinValue;
                    for (int i = 0; i < X.Length; i++)
                    {
                        if (X[i][j] > max)
                            max = X[i][j];
                    }
                    ret[j] = max;
                }
                return ret;
            }
        }

        //Returns the most frequent element in the input array
        static public int Mode(int[] arr)
        {
            if (arr.Length == 0)
            {
                throw new ArgumentException("Zero length is invalid");
            }
            var dict = new Dictionary<int, int>(arr.Length);
            foreach (UInt16 item in arr)
            {
                if (dict.ContainsKey(item))
                {
                    dict[item]++;
                }
                else
                {
                    dict.Add(item, 1);
                }
            }
            return dict.Where(x => dict.Values.Max() == (x.Value))
                       .Select(x => x.Key)
                       .ElementAt(0);
        }

        static public double Median(double[] arr)
        {
            Array.Sort(arr);
            //not 100 % proper way
            return arr[arr.Length / 2];
        }

        //Returns indices of the k smallest elements in the input array 
        static public int[] ArgKSmallest(double[] arr, UInt16 k)
        {
            int result_size = (k < arr.Length ? k : arr.Length);
            int[] result = new int[result_size];
            HashSet<int> found = new HashSet<int>();
            var enumerated = arr.Select((x, i) => new KeyValuePair<double, int>(x, i));
            for (int i = 0; i < result_size; i++)
            {
                double min = double.MaxValue;
                foreach (var item in enumerated)
                {
                    if (item.Key < min && !found.Contains(item.Value))
                    {
                        min = item.Key;
                        result[i] = item.Value;
                    }
                }
                found.Add(result[i]);
            }
            return result;
        }

        public static double Mean(double[] X)
        {
            double ret = 0.0;
            foreach (double v in X)
                ret += v;
            return ret / X.Length;
        }

        public static double[] Mean(double[][] X, short axis=0)
        {
            if (axis == 0)
            {
                double[] mean = new double[X[0].Length];
                for (int i = 0; i < X.Length; i++)
                    for (int j = 0; j < X[0].Length; j++)
                        mean[j] += X[i][j];
                for (int i = 0; i < X[0].Length; i++)
                    mean[i] /= X.Length;
                return mean;
            }
            else
            {
                double[] mean = new double[X.Length];
                for (int i = 0; i < X.Length; i++)
                    for (int j = 0; j < X[0].Length; j++)
                        mean[i] += X[i][j];
                for (int i = 0; i < X.Length; i++)
                    mean[i] /= X[0].Length;
                return mean;
            }
        }


        //deviation across first axis
        public static double[] Dev(double[][] X)
        {
            double[] mean = Mean(X);
            double[] dev = new double[X[0].Length];
            for(int j = 0; j < X[0].Length; j++)
            {
                for (int i = 0; i < X.Length; i++)
                    dev[j] += System.Math.Pow((X[i][j] - mean[j]), 2);
                dev[j] = System.Math.Sqrt(dev[j] / X.Length);
            }
            return dev;
        }

        //features average deviaton
        //AvgDev(X) = sum(Dev(X, axis=0)) / N
        public static double AvgDev(double[][] X)
        {
            double avgDev = 0.0;
            double[] dev = Dev(X);
            foreach (double v in dev)
                avgDev += v;
            avgDev /= X.Length;
            return avgDev;
        }

        public static double Cov(double[] X, double[] Y)
        {
            if (X.Length != Y.Length)
                throw new ArgumentException();
            double meanX = Mean(X);
            double meanY = Mean(Y);
            double ret = 0.0;
            for (int i = 0; i < X.Length; i++)
                ret += (X[i] - meanX) * (Y[i] - meanY);
            return ret / X.Length;
        }

        public static double[][] Transpose(double[][] X)
        {
            double[][] ret = new double[X[0].Length][];
            for(int i = 0; i < X[0].Length; i++)
            {
                ret[i] = new double[X.Length];
                for (int j = 0; j < X.Length; j++)
                    ret[i][j] = X[j][i];
            }
            return ret;
        }

        public static double Det(double[][] X)
        {
            //so dummy
            Matrix m = new Matrix(X.Length, X[0].Length);
            for (int i = 0; i < X.Length; i++)
                for (int j = 0; j < X[0].Length; j++)
                    m[i, j] = X[i][j];
            return m.Det();
        }

        public static double[][] Inverse(double[][] X)
        {
            //so dummy
            Matrix m = new Matrix(X.Length, X[0].Length);
            for (int i = 0; i < X.Length; i++)
                for (int j = 0; j < X[0].Length; j++)
                    m[i, j] = X[i][j];
            Matrix im = m.Invert();
            double[][] ret = new double[X.Length][];
            for (int i = 0; i < X.Length; i++)
            {
                ret[i] = new double[X[0].Length];
                for (int j = 0; j < X[0].Length; j++)
                    ret[i][j] = m[i, j];
            }
            return ret;
        }

        //if rowvar is true then each row of X - variable, each column - observation
        //else each column represents a variable, and each row observation of this variable
        public static double[][] CovMat(double[][] X, bool rowvar = true)
        {
            if(!rowvar)
                X = Transpose(X);

            double[][] ret = new double[X.Length][];
            for(int i = 0; i < X.Length; i++)
            {
                ret[i] = new double[X.Length];
                for (int j = 0; j < X.Length; j++)
                    ret[i][j] = Cov(X[i], X[j]);
            }
            return ret;
        }

        //sum of distances from each point to the centroid.
        public static double SumOfDists(double[][] X, double[] centroid = null, 
                                        Metrics.Metric metric = Metrics.Metric.SquaredEuclidean)
        {
            double ret = 0.0;
            Metrics.Distance dist = Metrics.SelectDistance(metric);
            if(centroid == null)
                centroid = Mean(X);
            for (int i = 0; i < X.Length; i++)
                ret += dist(centroid, X[i]);
            return ret;
        }


    }
}
