using System;
using System.Collections.Generic;
using System.Text;

namespace Intemi.InTrees.Implements
{
    /// <summary>
    /// Wybór testu. 
    /// Wybór najlepszego testu ze względu na przyrost informacji (Information Gain).
    /// </summary>
    public class Gini : INodeTestSelector
    {
        //TODO - a może by zrobić z tego metodę statyczną, poco to za każdym razem implementować jak i tak wybór opiera się na test rate

        /// <summary>
        /// Zwraca najlepszy test z podanego zbioru testów. Kryterium: Gini Index.
        /// </summary>
        /// <param name="nodeTests">Zbiór dostępnych testów.</param>
        /// <param name="dataTable">Dane.</param>
        /// <param name="shouldTerminate">Polecenie przerwania procedury.</param>
        /// <returns>Najlepszy test.</returns>
        public INodeTest SelectTest(List<INodeTest> nodeTests, IDataTable dataTable, ref bool shouldTerminate)
        {
            INodeTest bestTest = null;
            double bestGain = double.NegativeInfinity;
            double infoGain;

            foreach (INodeTest nodeTest in nodeTests)
            {
                if (shouldTerminate)
                    break;

                infoGain = RateTest(nodeTest, dataTable, ref shouldTerminate);
                nodeTest.TestRate = infoGain;
                if (infoGain > bestGain)
                {
                    bestGain = infoGain;
                    bestTest = nodeTest;
                }
            }

            //if (bestGain == 0)
            //    bestTest = null;

            nodeTests.Sort(new NodeTestComparer());
            return (bestTest);
        }

     

        /// <summary>
        /// Ocenia test. Jeżeli test01 lepszy od test02 to RateTest(test01) > RateTest(test02).
        /// </summary>
        /// <param name="nodeTest">Test do oceny.</param>
        /// <param name="dataTable">Dane.</param>
        /// <param name="shouldTerminate">Polecenie przerwania procedury.</param>
        /// <returns>Ocena testu (Gini index). Jeżeli test01 lepszy od test02 to RateTest(test01) > RateTest(test02).</returns>
        public double RateTest(INodeTest nodeTest, IDataTable dataTable, ref bool shouldTerminate)
        {
            double giniDelta = double.NegativeInfinity;
            if (nodeTest != null)
            {

                // wartość gini dla węzła przed podziałem
                double giniBeforeSplit = CalculateGini(dataTable);

                // podział 
                IDataTable[] newDataTables = nodeTest.SplitData(dataTable, ref shouldTerminate);

                // suma gini dla wezłów po podziale
                double giniAfterSplit = 0;
                double p, tmpGini;
                foreach (IDataTable dataTable2 in newDataTables)
                {
                    // gini dla kazdego wezla po podziale 
                    p = ((double)dataTable2.InstanceCount / dataTable.InstanceCount);
                    tmpGini = CalculateGini(dataTable2);
                    giniAfterSplit += p * tmpGini;
                }

                giniDelta = giniBeforeSplit - giniAfterSplit; 
                nodeTest.TestRate = giniDelta;
            }

            return (giniDelta);
        }

        public static double CalculateGini(IDataTable dataTable)
        {
            double gini = 0;
            if (dataTable.InstanceCount > 0)
            {
                IOneFeatureData tt = (dataTable as ITargets).Targets;
                int[] counts = tt.FeatureStatistics.counts;  
                int total = tt.InstanceCount;

                gini = 0;
                foreach (int k in counts)
                {
                    gini += Math.Pow((k / (double)total), 2);
                }

                gini = 1 - gini;
            }
            return (gini);
        }
    }
}
