using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Shlukova_Analyza_BP
{
    class SAFuzzy
    {
        progressReporter progressor;
        public double[][] a, c, d, u0, u1, maticeVzdalenostiPrvky;
        List<HashSet<int>> crisp;
        public Siluety siluety;
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        public int k, n, p, iteraci;
        public double m, j;
        public double[,] prisluIntervaly;
        public SAFuzzy(double[][] datovaMatice, double[][] maticeVzdalenostiPrvky, int k, double m, progressReporter progressor)
        {
            this.maticeVzdalenostiPrvky = maticeVzdalenostiPrvky;
            this.progressor = progressor;
            a = datovaMatice;
            this.k = k;
            n = datovaMatice.Length;
            p = datovaMatice[0].Length;
            if (m > 1)
            {
                this.m = m;
            }
            else
            {
                this.m = 1 + (1418 / n + 22.05) * 1 / (p * p) + (12.33 / n + 0.243) * Math.Pow(p, -0.0406 * Math.Log(n) - 0.1134);
            }
            c = new double[k][];
            d = new double[n][];
            u0 = new double[n][];
            u1 = new double[n][];
            vygenerujCentroidy();
            for (int i = 0; i < n; i++)
            {
                d[i] = new double[k];
                u0[i] = new double[k];
                u1[i] = new double[k];
            }
        }
        protected virtual double norma(double[] v1, double[] v2) // počítá normu mezi dvěma vektory
        {
            double norma = 0;
            int p = v1.Length;
            for (int i = 0; i < p; i++)
            {
                norma += (v1[i] - v2[i]) * (v1[i] - v2[i]);
            }
            return Math.Sqrt(norma);
        }
        protected virtual void vzdalenost() // počítá matici vzdálenosti
        {
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < k; j++)
                {
                    d[i][j] = norma(a[i], c[j]);
                }
            }
        }
        protected virtual void clenstvi() // počítá matici příslušnosti
        {
            klon(); // uloží starou matici příslušnosti do u0
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < k; j++)
                {
                    double suma = 0;
                    for (int q = 0; q < k; q++)
                    {
                        suma += Math.Pow((d[i][j] / d[i][q]), 2 / (m - 1));
                    }
                    if (double.IsNaN(suma))
                    {
                        u1[i][j] = 1;
                    }
                    else if (double.IsInfinity(suma))
                    {
                        u1[i][j] = 0;
                    }
                    else
                    {
                        u1[i][j] = 1 / suma;
                    }
                }
            }
        }
        private void klon() // vytváří starou matici příslušnosti
        {
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < k; j++)
                {
                    u0[i][j] = u1[i][j];
                }
            }
        }
        protected virtual void vygenerujCentroidy()
        {
            int cislo = 0;
            //c = new double[k][];
            HashSet<int> mnozina = new HashSet<int>();
            Random r = new Random();
            while (mnozina.Count < k)
            {
                mnozina.Add(r.Next(n)); // náhodně se vybere k prvků z množiny dat
            }
            foreach (int item in mnozina)
            {
                c[cislo] = new double[p];
                for (int j = 0; j < p; j++)
                {
                    c[cislo][j] = a[item][j];
                }
                cislo++;
            }
            /*Random rnd = new Random();
            for (int i = 0; i < k; i++)
            {
                c.Add(new double[p]);
                for (int j = 0; j < p; j++)
                {
                    c[i][j] = 2 * rnd.NextDouble() - 1;
                }
            }*/
        }
        public double ucelFce() // počítá hodnoty účelové funkce
        {
            double J = 0;
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < k; j++)
                {
                    J += Math.Pow(u1[i][j], m) * normaSqr(a[i], c[j]);
                }
            }
            return J;
        }
        public void postproces()
        {
            prisluIntervaly = prislunostiIntervaly();
            crisp = jednoznacnaPrislusnost();
            siluety = new Siluety(crisp, maticeVzdalenostiPrvky);
        }
        private List<HashSet<int>> jednoznacnaPrislusnost()
        {
            crisp = new List<HashSet<int>>();
            for (int i = 0; i < k; i++)
            {
                crisp.Add(new HashSet<int>());
            }
            for (int i = 0; i < n; i++)
            {
                double max = u1[i][0];
                int kde = 0;
                for (int j = 1; j < k; j++)
                {
                    if (max < u1[i][j])
                    {
                        max = u1[i][j];
                        kde = j;
                    }
                }
                crisp[kde].Add(i);
            }
            return crisp;
        }
        private double[,] prislunostiIntervaly()
        {
            prisluIntervaly = new double[k, 9];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < k; j++)
                {
                    int sloupec = cisloIntervalu(u1[i][j]);
                    switch (sloupec)
                    {
                        case 8:
                            prisluIntervaly[j, 8]++;
                            break;
                        case 7:
                            prisluIntervaly[j, 7]++;
                            break;
                        case 6:
                            prisluIntervaly[j, 6]++;
                            break;
                        case 5:
                            prisluIntervaly[j, 5]++;
                            break;
                        case 4:
                            prisluIntervaly[j, 4]++;
                            break;
                        case 3:
                            prisluIntervaly[j, 3]++;
                            break;
                        case 2:
                            prisluIntervaly[j, 2]++;
                            break;
                        case 1:
                            prisluIntervaly[j, 1]++;
                            break;
                        case 0:
                            prisluIntervaly[j, 0]++;
                            break;
                        default:
                            break;
                    }
                }
            }
            return prisluIntervaly;
        }
        private int cisloIntervalu(double prislusnost)
        {
            if (prislusnost <= 0.1)
            {
                return -1;
            }
            else if (prislusnost <= 0.2)
            {
                return 8;
            }
            else if (prislusnost <= 0.3)
            {
                return 7;
            }
            else if (prislusnost <= 0.4)
            {
                return 6;
            }
            else if (prislusnost <= 0.5)
            {
                return 5;
            }
            else if (prislusnost <= 0.6)
            {
                return 4;
            }
            else if (prislusnost <= 0.7)
            {
                return 3;
            }
            else if (prislusnost <= 0.8)
            {
                return 2;
            }
            else if (prislusnost <= 0.9)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
        public void shlukuj(double eps)
        {
            for (int krok = 0; krok < 100; krok++)
            {
                vzdalenost();
                clenstvi();
                centroidy();
                if (konec(eps))
                {
                    progressor(Convert.ToInt32(j));
                    iteraci = krok;
                    break;
                }
                progressor(Convert.ToInt32(j));
            }
        }
        private void centroidy() // přepočet centroidů podle nové matice příslušnosti
        {
            for (int j = 0; j < k; j++) // přes centroidy 
            {
                double sumad = 0;
                for (int i = 0; i < n; i++) // přes řádky
                {
                    sumad += Math.Pow(u1[i][j], m);
                }
                for (int q = 0; q < p; q++) // přes sloupce
                {
                    double sumah = 0;
                    for (int i = 0; i < n; i++) // přes řádky
                    {
                        sumah += Math.Pow(u1[i][j], m) * a[i][q];
                    }
                    c[j][q] = sumah / sumad;
                }
            }
        }
        private bool konec(double eps) // na základě porovnání nové a staré hodnoty účelové funkce říká, zda se algoritmus ukončí
        {
            double J = ucelFce();
            if (Math.Abs(j - J) / j < eps)
            {
                j = J;
                return true;
            }
            else
            {
                j = J;
                return false;
            }
        }
        private double normaSqr(double[] v1, double[] v2) // počítá kvadrát normy mezi dvěma vektory
        {
            double norma = 0;
            int p = v1.Length;
            for (int i = 0; i < p; i++)
            {
                norma += (v1[i] - v2[i]) * (v1[i] - v2[i]);
            }
            return norma;
        }
    }
}
