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

namespace Shlukova_Analyza_BP
{
    class SAKMeans 
    {
        progressReporter progressor;
        private double[][] maticeVzdalenostiPrvky;
        public Siluety siluety;
        public int n, p, k, iteraci; // počet scénářů, počet proměnných
        public double J;
        public double[][] a,c,d;
        public List<HashSet<int>> u0, u1;
        //private List<List<double>> d = new List<List<double>>(); // matice vzdalenosti
        public SAKMeans(double[][] datovaMatice, double[][] maticeVzdalenostiPrvky, int k, progressReporter progressor)
        {
            this.maticeVzdalenostiPrvky = maticeVzdalenostiPrvky;
            a = datovaMatice;
            this.k = k;
            this.progressor = progressor;
            n = datovaMatice.Length;
            p = datovaMatice[0].Length;
            vygenerujCentroidy();
            u0 = new List<HashSet<int>>(k);
            for (int i = 0; i < k; i++)
            {
                u0.Add(new HashSet<int>()); // kvůli porovnání v první iteraci
            }
        }
        public SAKMeans(double[][] datovaMatice, int k, progressReporter progressor)
        {
            a = datovaMatice;
            this.k = k;
            this.progressor = progressor;
            n = datovaMatice.Length;
            p = datovaMatice[0].Length;
            vygenerujCentroidy();
            u0 = new List<HashSet<int>>(k);
            for (int i = 0; i < k; i++)
            {
                u0.Add(new HashSet<int>()); // kvůli porovnání v první iteraci
            }
        } // půjde pryč
        public void shlukuj()
        {
            for (int i = 0; i < 100; i++)
            {
                vzdalenost();
                clenstvi();
                prepocetCentroidu();
                J = ucelFce();
                if (nepokracovat())
                {
                    progressor(Convert.ToInt32(J));
                    iteraci = i;
                    break;
                }
                u0 = u1;
                //progressor(Convert.ToInt32(J));
            }
        }
        protected virtual void vzdalenost() // počítá matici vzdálenosti (Euklidovská norma kvadrát)
        {
            d = new double[n][];
            for (int i = 0; i < n; i++)
            {
                d[i] = new double[k];
                for (int q = 0; q < k; q++)
                {
                    double norma = 0;
                    for (int j = 0; j < p; j++)
                    {
                        norma += (a[i][j] - c[q][j]) * (a[i][j] - c[q][j]);
                    }
                    d[i][q] = norma;
                }
            }
        }
        protected virtual void clenstvi()
        {
            u1 = new List<HashSet<int>>(k);
            for (int i = 0; i < k; i++)
            {
                u1.Add(new HashSet<int>());
            }
            double min;
            int minIndex;
            for (int i = 0; i < n; i++) // probíhá prvky
            {
                min = d[i][0];
                minIndex = 0;
                for (int j = 0; j < k; j++) // probíhá shluky
                {
                    if (min > d[i][j])
                    {
                        min = d[i][j];
                        minIndex = j;
                    }
                }
                u1[minIndex].Add(i);
            }
        }
        protected virtual void prepocetCentroidu()
        {
            vynulujCentroidy();
            for (int i = 0; i < k; i++) // probíhá shluky
            {
                foreach (int item in u1[i]) // probíhá prvky patřící do shluku i
                {
                    for (int j = 0; j < p; j++) // probíhá proměnné
                    {
                        c[i][j] += a[item][j]; // v každé proměnné sčítá hodnoty 
                    }
                }
                int delitel = u1[i].Count;
                for (int j = 0; j < p; j++)
                {
                    c[i][j] /= delitel;
                }
            }
        }
        protected virtual void vynulujCentroidy()
        {
            for (int i = 0; i < k; i++)
            {
                for (int j = 0; j < p; j++)
                {
                    c[i][j] = 0;
                }
            }
        }
        protected virtual bool nepokracovat()
        {
            for (int i = 0; i < k; i++) // probíhá shluky a porovnává jejich prvky
            {
                if (!u1[i].SetEquals(u0[i]))
                {
                    return false;
                }
            }
            return true;
        }
        public double ucelFce()
        {
            double J = 0;
            for (int i = 0; i < k; i++) // probíhá shluky
            {
                foreach (int item in u1[i]) // probíhá prvky shluku
                {
                    double norma = 0;
                    for (int j = 0; j < p; j++) // probíhá proměnné
                    {
                        norma += (a[item][j] - c[i][j]) * (a[item][j] - c[i][j]);
                    }
                    J += norma;
                }
            }
            return J;
        }
        protected virtual void vygenerujCentroidy()
        {
            int cislo = 0;
            c = new double[k][];
            HashSet<int> mnozina = new HashSet<int>();
            Random r = new Random();
            bool stejny;
            while (mnozina.Count < k)
            {
                stejny = false;
                cislo = r.Next(n);
                foreach (int item in mnozina)
                {
                    if (cislo < item)
                    {
                        if (maticeVzdalenostiPrvky[item][cislo] == 0)
                        {
                            stejny = true;
                            break;
                        }
                    }
                    if (cislo > item)
                    {
                        if (maticeVzdalenostiPrvky[cislo][item] == 0)
                        {
                            stejny = true;
                            break;
                        }
                    }
                }
                if (!stejny)
                {
                    mnozina.Add(cislo); // náhodně se vybere k prvků z množiny dat
                } 
            }
            cislo = 0;
            foreach (int item in mnozina)
            {
                c[cislo] = new double[p];
                for (int j = 0; j < p; j++)
                {
                    c[cislo][j] = a[item][j];
                }
                cislo++; 
            }

            // Náhodné generování
            /*c = new double[k][];
            Random rnd = new Random();
            for (int i = 0; i < k; i++)
            {
                c[i] = new double[p];
                for (int j = 0; j < p; j++)
                {
                    c[i][j] = 3 * rnd.NextDouble() - 1.5;
                }
            }*/
        }
        public void postprocess()
        {
            siluety = new Siluety(u1, maticeVzdalenostiPrvky);
        }
        private double norma(double[] v1, double[] v2)
        {
            double norma = 0;
            for (int i = 0; i < p; i++) // prochází sloupce
            {
                norma += (v1[i] - v2[i]) * (v1[i] - v2[i]);
            }
            return Math.Sqrt(norma);
        }
    }
}
