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

namespace Shlukova_Analyza_BP
{
    public class Promenne
    {
        private HashSet<int> mnozinaUzlu = new HashSet<int>();
        private HashSet<int> mnozinaPlatnychUzlu = new HashSet<int>();
        private double[][] datovaMatice, maticeVzdalenostiPrvky;
        private bool[] typ;
        private List<double[]> promenne = new List<double[]>(); // seznam proměnných, které se budou standardizovat, mazat, etc
        private List<List<int>> platneRadky = new List<List<int>>(); // list platnych radku pro evo, mbu,...
        public Promenne(List<Scenar> scenare, bool[] typ) // konstruktor pro vsechny uzly
        {
            vytvorMnozinuUzlu(scenare[0], typ);
            int[] listOblasti = vytvorListOblasti();
            spolecnyKonstruktor(scenare, typ, listOblasti);
        }
        public Promenne(List<Scenar> scenare, bool[] typ, int[] listOblasti, bool oddelatUzly) // konstruktor pro vybrane uzly
        {
            vytvorMnozinuUzlu(scenare[0], typ);
            if (oddelatUzly)
            {
                listOblasti = oddelatOblasti(listOblasti);
            }
            spolecnyKonstruktor(scenare, typ, listOblasti);
        }
        public Promenne(double[][] datovaMatice) // konstruktor pro načtená data
        {
            this.datovaMatice = datovaMatice;
            for (int i = 0; i < datovaMatice[0].GetLength(0); i++)
            {
                promenne.Add(new double[datovaMatice.GetLength(0)]);
                for (int j = 0; j < datovaMatice.GetLength(0); j++)
                {
                    promenne[i][j] = datovaMatice[j][i];
                }
            }
        }
        private void spolecnyKonstruktor(List<Scenar> scenare, bool[] typ, int[] listOblasti)
        {
            this.typ = typ;
            vyrobPlatneRadky(scenare[0], listOblasti, typ);
            zavedeniPromennych(scenare, typ);
            naplneniPromennych(scenare, typ);
            upravaPromennych();
            datovaMatice = datovaMaticeKMeans();
            maticeVzdalenostiPrvky = vypocetMaticeVzdalenostiPrvky();
        }
        public double[][] MaticeVzdalenostiPrvky
        {
            get
            {
                if (maticeVzdalenostiPrvky == null)
                {
                    maticeVzdalenostiPrvky = vypocetMaticeVzdalenostiPrvky();
                }
                return maticeVzdalenostiPrvky;
            }
        }
        public double[][] DatovaMatice
        {
            get
            {
                return datovaMatice;
            }
        }
        private void vyrobPlatneRadky(Scenar scenar, int[] listOblasti, bool[] typ)
        {
            if (typ[0])
            {
                najdiPlatneRadky(scenar.evo, listOblasti);
            }
            if (typ[1])
            {
                najdiPlatneRadky(scenar.mbu, listOblasti);
            }
            if (typ[2])
            {
                najdiPlatneRadky(scenar.skl, listOblasti);
            }
            if (typ[3])
            {
                najdiPlatneRadky(scenar.zlf, listOblasti);
            }
            if (typ[4])
            {
                najdiPlatneRadky(scenar.prekl, listOblasti);
            }
        }
        private void najdiPlatneRadky(double[,] scenare, int[] listOblasti) // najde radky, na kterych se nechazi dane oblasti
        {
            platneRadky.Add(new List<int>());
            int misto = platneRadky.Count - 1;
            int length = scenare.GetLength(0);
            foreach (int item in listOblasti)
            {
                for (int i = 0; i < length; i++)
                {
                    if (item == scenare[i, 0])
                    {
                        platneRadky[misto].Add(i);
                        mnozinaPlatnychUzlu.Add(item);
                    }
                }
            }
        }
        private void zavedeniPromennych(List<Scenar> scenare, bool[] typ) // vytvoří seznam proměnných, které se budou standardizovat, mazat, etc.
        {
            int pocet = scenare.Count;
            int poradi = 0;
            //evo
            int length = scenare[0].evo.GetLength(1) - 1;
            if (typ[0])
            {
                foreach (int item in platneRadky[poradi])
                {
                    for (int i = 0; i < length; i++)
                    {
                        promenne.Add(new double[pocet]);
                    }
                }
                poradi++;
            }

            //mbu
            length = scenare[0].mbu.GetLength(1) - 1;
            if (typ[1])
            {
                foreach (int item in platneRadky[poradi])
                {
                    for (int i = 0; i < length; i++)
                    {
                        promenne.Add(new double[pocet]);
                    }
                }
                poradi++;
            }
            //skl
            length = scenare[0].skl.GetLength(1) - 1;
            if (typ[2])
            {
                foreach (int item in platneRadky[poradi])
                {
                    for (int i = 0; i < length; i++)
                    {
                        promenne.Add(new double[pocet]);
                    }
                }
                poradi++;
            }
            //zlf
            length = scenare[0].zlf.GetLength(1) - 1;
            if (typ[3])
            {
                foreach (int item in platneRadky[poradi])
                {
                    for (int i = 0; i < length; i++)
                    {
                        promenne.Add(new double[pocet]);
                    }
                }
                poradi++;
            }
            //prekl
            length = scenare[0].prekl.GetLength(1) - 1;
            if (typ[4])
            {
                foreach (int item in platneRadky[poradi])
                {
                    for (int i = 0; i < length; i++)
                    {
                        promenne.Add(new double[pocet]);
                    }
                }
                poradi++;
            }
        }
        private void naplneniPromennych(List<Scenar> scenare, bool[] typ) // nacpe čísla do seznamu proměnných
        {
            int lengthEvo = scenare[0].evo.GetLength(1);
            int lengthMbu = scenare[0].mbu.GetLength(1);
            int lengthSkl = scenare[0].skl.GetLength(1);
            int lengthZlf = scenare[0].zlf.GetLength(1);
            int lengthPrekl = scenare[0].prekl.GetLength(1);
            int cisloScenare = 0;
            foreach (Scenar scenar in scenare) // prochází scénáře
            {
                int radek = 0; // číslo proměnné
                int poradi = 0; // k identifikaci spravneho radku v platneRadky
                // evo
                if (typ[0])
                {
                    foreach (int item in platneRadky[poradi]) // prochází platné řádky
                    {
                        for (int i = 1; i < lengthEvo; i++) // prochází sloupce v datech evo, mbu,... začíná od 1, protože na prvním místě je číslo oblasti
                        {
                            promenne[radek][cisloScenare] = scenar.evo[item, i];
                            radek++;
                        }
                    }
                    poradi++;
                }
                // mbu
                if (typ[1])
                {
                    foreach (int item in platneRadky[poradi]) // prochází platné řádky
                    {
                        for (int i = 1; i < lengthMbu; i++) // prochází sloupce v datech evo, mbu,... začíná od 1, protože na prvním místě je číslo oblasti
                        {
                            promenne[radek][cisloScenare] = scenar.mbu[item, i];
                            radek++;
                        }
                    }
                    poradi++;
                }
                // skl
                if (typ[2])
                {
                    foreach (int item in platneRadky[poradi]) // prochází platné řádky
                    {
                        for (int i = 1; i < lengthSkl; i++) // prochází sloupce v datech evo, mbu,... začíná od 1, protože na prvním místě je číslo oblasti
                        {
                            promenne[radek][cisloScenare] = scenar.skl[item, i];
                            radek++;
                        }
                    }
                    poradi++;
                }
                // zlf
                if (typ[3])
                {
                    foreach (int item in platneRadky[poradi]) // prochází platné řádky
                    {
                        for (int i = 1; i < lengthZlf; i++) // prochází sloupce v datech evo, mbu,... začíná od 1, protože na prvním místě je číslo oblasti
                        {
                            promenne[radek][cisloScenare] = scenar.zlf[item, i];
                            radek++;
                        }
                    }
                    poradi++;
                }
                // prekl
                if (typ[4])
                {
                    foreach (int item in platneRadky[poradi]) // prochází platné řádky
                    {
                        for (int i = 1; i < lengthPrekl; i++) // prochází sloupce v datech evo, mbu,... začíná od 1, protože na prvním místě je číslo oblasti
                        {
                            promenne[radek][cisloScenare] = scenar.prekl[item, i];
                            radek++;
                        }
                    }
                    poradi++;
                }
                cisloScenare++;
            }
        }
        private void vymazZbytecnePromenne() // vymaže proměnné, které mají pro všechny scénáře stejnou hodnotu - tedy nulový rozptyl
        {
            List<int> smazat = new List<int>();
            int cislo = 0;
            int length = promenne[0].Length;
            foreach (double[] promenna in promenne)
            {
                bool stejna = true;
                double prvni = promenna[0];
                for (int i = 1; i < length; i++)
                {
                    if (prvni != promenna[i])
                    {
                        stejna = false;
                        break;
                    }
                }
                if (stejna)
                {
                    smazat.Add(cislo);
                }
                cislo++;
            }
            int pocet = smazat.Count - 1;
            for (int i = pocet; i >= 0; i--)
            {
                promenne.RemoveAt(smazat[i]);
            }
        }
        private void standardizujPromenne() // standardizuje proměnné
        {
            int pocet = promenne[0].Length;
            int cislo = 0;
            foreach (double[] promenna in promenne)
            {
                double suma = 0;
                for (int i = 0; i < pocet; i++) // výpočet průměru
                {
                    suma += promenna[i];
                }
                double prumer = suma / pocet;
                suma = 0;
                for (int i = 0; i < pocet; i++) // výpočet směrodatné odchylky
                {
                    suma += ((promenna[i] - prumer) * (promenna[i] - prumer));
                }
                double odchylka = Math.Sqrt(suma / pocet);
                for (int i = 0; i < pocet; i++) // transformace
                {
                    promenne[cislo][i] = (promenne[cislo][i] - prumer) / odchylka;
                }
                cislo++;
            }
        }
        private int[] oddelatOblasti(int[] listOblasti)
        {
            foreach (int i in listOblasti)
            {
                if (mnozinaUzlu.Contains(i))
                {
                    mnozinaUzlu.Remove(i);
                }
            }
            return vytvorListOblasti();
        } // oddělá oblasti, které uživatel nechce
        private void upravaPromennych() // standardizace atp.
        {
            vymazZbytecnePromenne();
            vymazKorelovanePromenne();
            standardizujPromenne();
        }
        private void vytvorMnozinuUzlu(Scenar scenar, bool[] typ)
        {
            int length = scenar.evo.GetLength(0);
            if (typ[0])
            {
                for (int i = 0; i < length; i++)
                {
                    mnozinaUzlu.Add(Convert.ToInt32(scenar.evo[i, 0]));
                }
            }
            length = scenar.mbu.GetLength(0);
            if (typ[1])
            {
                for (int i = 0; i < length; i++)
                {
                    mnozinaUzlu.Add(Convert.ToInt32(scenar.mbu[i, 0]));
                }
            }
            length = scenar.skl.GetLength(0);
            if (typ[2])
            {
                for (int i = 0; i < length; i++)
                {
                    mnozinaUzlu.Add(Convert.ToInt32(scenar.skl[i, 0]));
                }
            }
            length = scenar.zlf.GetLength(0);
            if (typ[3])
            {
                for (int i = 0; i < length; i++)
                {
                    mnozinaUzlu.Add(Convert.ToInt32(scenar.zlf[i, 0]));
                }
            }
            length = scenar.prekl.GetLength(0);
            if (typ[4])
            {
                for (int i = 0; i < length; i++)
                {
                    mnozinaUzlu.Add(Convert.ToInt32(scenar.prekl[i, 0]));
                }
            }
        } // vytvoří číselný seznam uzlů
        private int[] vytvorListOblasti()
        {
            int[] predelaneOblasti = new int[mnozinaUzlu.Count];
            int k = 0;
            foreach (int i in mnozinaUzlu)
            {
                predelaneOblasti[k] = i;
                k++;
            }
            return predelaneOblasti;
        }
        private double[][] vypocetMaticeVzdalenostiPrvky()
        {
            int n = promenne[0].GetLength(0);
            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(datovaMatice[i], datovaMatice[j]);
                }
            }
            return matice;
        }
        private double norma(double[] v1, double[] v2)
        {
            int p = v1.GetLength(0);
            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);
        } // euklidovská
        public List<double[]> datovaMaticeFuzzy()
        {
            List<double[]> a = new List<double[]>();
            int p = promenne.Count;
            int n = promenne[0].Length;
            for (int i = 0; i < n; i++)
            {
                a.Add(new double[p]);
                for (int j = 0; j < p; j++)
                {
                    a[i][j] = promenne[j][i];
                }
            }
            return a;
        } // vytvoří datovou matici pro Fuzzy shlukování
        public double[,] datovaMaticeHier()
        {
            int p = promenne.Count;
            int n = promenne[0].Length;
            double[,] a = new double[n, p];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < p; j++)
                {
                    a[i,j] = promenne[j][i];
                }
            }
            return a;
        } // vytvoří datovou matici pro hierarchické shlukování
        public double[][] datovaMaticeKMeans()
        {
            int p = promenne.Count;
            int n = promenne[0].Length;
            double[][] a = new double[n][];
            for (int i = 0; i < n; i++)
            {
                a[i] = new double[p];
                for (int j = 0; j < p; j++)
                {
                    a[i][j] = promenne[j][i];
                }
            }
            return a;
        }
        public string N
        {
            get { return promenne[0].GetLength(0).ToString(); }
        }
        public string Oblasti
        {
            get
            {
                string str = "";
                foreach (int i in mnozinaPlatnychUzlu)
                {
                    str += i.ToString() + " ";
                }
                str.Remove(str.Length - 1);
                return str;
            }
        }
        public string Pro
        {
            get
            {
                string str = "";
                if (typ[0])
                {
                    str += "EVO ";
                }
                if (typ[1])
                {
                    str += "MBU ";
                }
                if (typ[2])
                {
                    str += "SKL ";
                }
                if (typ[3])
                {
                    str += "ZLF ";
                }
                if (typ[4])
                {
                    str += "PREKL";
                }
                return str;
            }
        }
        public void vymazKorelovanePromenne()
        {
            bool isEqual;
            int p = promenne[0].GetLength(0);
            for (int i = 0; i < promenne.Count; i++)
            {
                for (int j = i+1; j < promenne.Count; j++)
                {
                    isEqual = Enumerable.SequenceEqual(promenne[i], promenne[j]);
                    if (isEqual)
                    {
                        promenne.RemoveAt(j);
                        j--;
                    }
                }
            }
        }
    }
}
