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

namespace Shlukova_Analyza_BP
{
    class SAHier
    {
        delegate double normaDelegat(int i, int j);
        delegate double[] koeficientyDelegat(int[] index);
        private double alphar, alphas, b, c;
        private int r, s, min, max;
        normaDelegat norma;
        koeficientyDelegat vypoctiKoeficienty;
        progressReporter progressor;
        public List<Siluety> listSiluet = new List<Siluety>();
        private List<List<double>> maticeVzdalenosti = new List<List<double>>(); // matice vzdalenosti
        private List<double[]> shlukovani = new List<double[]>();
        private int n, p; // počet scénářů, počet proměnných
        public double[][] datovaMatice, euklidovaMaticeVzdalenosti;
        public double[,] Shlukovani
        {
            get
            {
                int length = this.shlukovani.Count;
                double[,] shlukovani = new double[length, 3];
                for (int i = 0; i < length; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        shlukovani[i, j] = this.shlukovani[i][j];
                    }
                }
                return shlukovani;
            }
        }
        public SAHier(double[][] datovaMatice, int normaID, int metoda, progressReporter progressor, int min, int max) // konstruktor
        {
            this.min = min;
            this.max = max;
            n = datovaMatice.GetLength(0);
            p = datovaMatice[0].GetLength(0);
            this.datovaMatice = datovaMatice;
            priradDelegaty(normaID, metoda);
            this.progressor = progressor;
            //vytvorMaticiVzdalenosti(maticeVzdalenostiPrvky);
            vypoctiVzdalenosti();
            shlukuj();
            
        }
        public void shlukuj()
        {
            int spocitanychDvojic = 0;
            int celkemDvojic = (n - 1) * (n) / 2;
            int shluk = n; // přibývá v každém kroku, indentifikuje nově vzniklý shluk
            for (int k = 1; k < n; k++)
            {
                int[] index = slucDvaShluky(shluk);
                prepocitat(index, shluk);
                shluk++;
                spocitanychDvojic += n - k;
                progressor(100 * spocitanychDvojic / celkemDvojic);
            }
            Rozklady rozklady = new Rozklady(shlukovani, min, max);
            norma = normaEuklid;
            euklidovaMaticeVzdalenosti = vypocetEuklidovskeMaticeVzdalenosti();
            for (int i = 0; i <= max-min; i++)
            {
                listSiluet.Add(new Siluety(rozklady.vsechnyRozklady[i], euklidovaMaticeVzdalenosti));
            }
            //siluety = new Siluety(rozklady.pocatecniRozklad, euklidovaMaticeVzdalenosti);
        }
        private double[][] vypocetEuklidovskeMaticeVzdalenosti()
        {
            double[][] matice = new double[n][];
            matice[0] = new double[1] { 0 };
            for (int i = 1; i < n; i++)
            {
                matice[i] = new double[i];
                for (int j = 0; j < i; j++)
                {
                    matice[i][j] = norma(i ,j);
                }
            }
            return matice;
        }
        private void vypoctiVzdalenosti()
        {
            int spocitanychDvojic = 0;
            int celkemDvojic = n * (n + 1) / 2;
            for (int i = 0; i < n; i++)
            {
                maticeVzdalenosti.Add(new List<double>());
                for (int j = 0; j < i; j++)
                {
                    maticeVzdalenosti[i].Add(norma(i, j));
                }
                maticeVzdalenosti[i].Add(i); // číslo shluku na diagonále / 0 base
                maticeVzdalenosti[i].Add(1); // počet prvků ve shluku - na začátku vždy 1
                spocitanychDvojic += i + 1;
                progressor(100*spocitanychDvojic/celkemDvojic);
            }
        }
        private int[] slucDvaShluky(int shluk) //najde minimalni vzdalenost mezi dvema prvky, přidá záznam o sloučení
        {
            int[] index = new int[2];
            int radku = maticeVzdalenosti.Count;
            double minimum = maticeVzdalenosti[1][0];
            index[0] = 0;
            index[1] = 0;
            for (int i = 1; i < radku; i++)
            {
                for (int j = 0; j < i; j++)
                {
                    if (minimum >= maticeVzdalenosti[i][j])
                    {
                        minimum = maticeVzdalenosti[i][j];
                        index[0] = i;
                        index[1] = j;
                    }
                }
            }
            shlukovani.Add(new double[3]); // prida zaznam o shluknuti vcetne vzdalenosti
            shlukovani[shluk - n][0] = minimum;
            shlukovani[shluk - n][1] = maticeVzdalenosti[index[0]][index[0]];
            shlukovani[shluk - n][2] = maticeVzdalenosti[index[1]][index[1]];
            return index;
        }
        private void prepocitat(int[] index, int shluk) // přepočítá matici vzdáleností (přidá nový shluk, oddělá dva staré)
        {
            double[] koefs = vypoctiKoeficienty(index);
            // indentifikace pro prehlednost
            alphar = koefs[0];
            alphas = koefs[1];
            b = koefs[2];
            c = koefs[3];
            r = index[0];
            s = index[1];
            int radku = maticeVzdalenosti.Count;
            maticeVzdalenosti.Add(new List<double>()); // přidává řádek, kde budou vzdálenosti mezi novým a původním shluky

            for (int k = 0; k < s; k++) // komplikace, protože první index musí být ten větši..
            {
                maticeVzdalenosti[radku].Add(alphar * maticeVzdalenosti[r][k] + alphas * maticeVzdalenosti[s][k] + 
                    b * maticeVzdalenosti[r][s] + c * Math.Abs(maticeVzdalenosti[r][k] - maticeVzdalenosti[s][k]));
            }

            for (int k = s + 1; k < r; k++)
            {
                maticeVzdalenosti[radku].Add(alphar * maticeVzdalenosti[r][k] + alphas * maticeVzdalenosti[k][s] + 
                    b * maticeVzdalenosti[r][s] + c * Math.Abs(maticeVzdalenosti[r][k] - maticeVzdalenosti[k][s]));
            }

            for (int k = r + 1; k < radku; k++)
            {
                maticeVzdalenosti[radku].Add(alphar * maticeVzdalenosti[k][r] + alphas * maticeVzdalenosti[k][s] + 
                    b * maticeVzdalenosti[r][s] + c * Math.Abs(maticeVzdalenosti[k][r] - maticeVzdalenosti[k][s]));
            }
            maticeVzdalenosti[radku].Add(shluk); // číslo shluku
            maticeVzdalenosti[radku].Add(maticeVzdalenosti[r][r + 1] + maticeVzdalenosti[s][s + 1]); // počet prvků ve shluku
            vymaz(index[0], index[1]);
        }
        private void vymaz(int cislo1, int cislo2) //číslo 2 je menší // vymaže shluky, které se spojily
        {
            int delka = maticeVzdalenosti.Count - 1;
            for (int i = delka - 1; i > cislo1; i--)
            {
                maticeVzdalenosti[i].RemoveAt(cislo1);
                maticeVzdalenosti[i].RemoveAt(cislo2);
            }
            for (int i = cislo1 - 1; i > cislo2; i--)
            {
                maticeVzdalenosti[i].RemoveAt(cislo2);
            }
            maticeVzdalenosti.RemoveAt(cislo1);
            maticeVzdalenosti.RemoveAt(cislo2);
        }
        // Normy
        private double normaEuklidSqr(int i, int j)
        {
            double norma = 0;
            for (int k = 0; k < p; k++)
            {
                norma += (datovaMatice[i][k] - datovaMatice[j][k]) * (datovaMatice[i][k] - datovaMatice[j][k]);
            }
            return norma;
        }
        private double normaEuklid(int i, int j)
        {
            return Math.Sqrt(normaEuklidSqr(i, j));
        }
        private double normaManhattan(int i, int j)
        {
            double norma = 0;
            for (int k = 0; k < p; k++)
            {
                norma += Math.Abs(datovaMatice[i][k] - datovaMatice[j][k]);
            }
            return norma;
        }
        private double normaCebysev(int i, int j)
        {
            double norma = Math.Abs(datovaMatice[i][1] - datovaMatice[j][1]);
            for (int k = 1; k < p; k++)
            {
                if (Math.Abs(datovaMatice[i][k] - datovaMatice[j][k]) > norma)
                {
                    norma = Math.Abs(datovaMatice[i][k] - datovaMatice[j][k]);
                }
            }
            return norma;
        }
        // Koeficienty přepočtů
        private double[] koefNejblizsiSoused(int[] index)
        {
            double[] koefs = new double[4];
            koefs[0] = 0.5;
            koefs[1] = 0.5;
            koefs[2] = 0;
            koefs[3] = -0.5;
            return koefs;
        }
        private double[] koefNejvzdalenejsiSoused(int[] index)
        {
            double[] koefs = new double[4];
            koefs[0] = 0.5;
            koefs[1] = 0.5;
            koefs[2] = 0;
            koefs[3] = 0.5;
            return koefs;
        }
        private double[] koefCentroidni(int[] index)
        {
            double nr = maticeVzdalenosti[index[0]][index[0] + 1];
            double ns = maticeVzdalenosti[index[1]][index[1] + 1];
            double[] koefs = new double[4];
            koefs[0] = nr / (nr + ns);
            koefs[1] = ns / (nr + ns);
            koefs[2] = (-nr) * ns / ((ns + nr) * (ns + nr));
            koefs[3] = 0;
            return koefs;
        }
        private double[] koefMedianova(int[] index)
        {
            double[] koefs = new double[4];
            koefs[0] = 0.5;
            koefs[1] = 0.5;
            koefs[2] = -0.25;
            koefs[3] = 0;
            return koefs;
        }
        private double[] koefPrumernaVazbaNevazena(int[] index)
        {
            double nr = maticeVzdalenosti[index[0]][index[0] + 1];
            double ns = maticeVzdalenosti[index[1]][index[1] + 1];
            double[] koefs = new double[4];
            koefs[0] = nr / (nr + ns);
            koefs[1] = ns / (nr + ns);
            koefs[2] = 0;
            koefs[3] = 0;
            return koefs;
        }
        private double[] koefPrumernaVazbaVazena(int[] index)
        {
            double[] koefs = new double[4];
            koefs[0] = 0.5;
            koefs[1] = 0.5;
            koefs[2] = 0;
            koefs[3] = 0;
            return koefs;
        }
        // Přírazení delegátů
        private void priradDelegaty(int normaID, int metoda)
        {
            switch (normaID)
            {
                case 0:
                    norma = normaEuklid;
                    break;
                case 1:
                    norma = normaEuklidSqr;
                    break;
                case 2:
                    norma = normaManhattan;
                    break;
                case 3:
                    norma = normaCebysev;
                    break;
                default:
                    break;
            }
            switch (metoda)
            {
                case 0:
                    vypoctiKoeficienty = koefNejblizsiSoused;
                    break;
                case 1:
                    vypoctiKoeficienty = koefNejvzdalenejsiSoused;
                    break;
                case 2:
                    vypoctiKoeficienty = koefCentroidni;
                    break;
                case 3:
                    vypoctiKoeficienty = koefMedianova;
                    break;
                case 4:
                    vypoctiKoeficienty = koefPrumernaVazbaNevazena;
                    break;
                case 5:
                    vypoctiKoeficienty = koefPrumernaVazbaVazena;
                    break;
                default:
                    break;
            }
        }
    }
    class Rozklady
    {
        private List<double[]> shlukovani;
        private int n; // počet spojení, o jeden méně, než počet objektů
        public List<HashSet<int>> pocatecniRozklad;
        public List<List<HashSet<int>>> vsechnyRozklady = new List<List<HashSet<int>>>();
        public Rozklady(List<double[]> shlukovani, int min, int max)
        {
            this.shlukovani = shlukovani;
            n = shlukovani.Count;
            for (int i = min; i <= max; i++)
            {
                pocatecniRozklad = new List<HashSet<int>>();
                pocatecniDeleni(i);
                vsechnyRozklady.Add(pocatecniRozklad);
            }
        }
        private void pocatecniDeleni(int min)
        {
            HashSet<int> pocatek = new HashSet<int>();
            pocatek.Add(2*n);
            for (int i = n-1; i >= 0; i--) // i značí řádek
            {
                int shluk = i + n + 1;
                if (pocatek.Contains(shluk))
                {
                    pocatek.Remove(shluk);
                }
                pocatek.Add((int)shlukovani[i][1]);
                pocatek.Add((int)shlukovani[i][2]);
                if (pocatek.Count >= min)
                {
                    break;
                }
            }
            provedPocatecniRozklad(pocatek);
        }
        private void provedPocatecniRozklad(HashSet<int> pocatek)
        {
            int i = 0;
            foreach (int prvek in pocatek)
            {
                pocatecniRozklad.Add(new HashSet<int>());
                pridej(prvek, ref pocatecniRozklad, i);
                i++;
            }
        }
        private void pridej(int prvek, ref List<HashSet<int>> shluky, int cisloShluku)
        {
            if (prvek <= n)
            {
                shluky[cisloShluku].Add(prvek);
            }
            else
            {
                pridej((int)shlukovani[prvek - n - 1][1], ref shluky, cisloShluku);
                pridej((int)shlukovani[prvek - n - 1][2], ref shluky, cisloShluku);
            }
        }
    }
}
