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 InfoGainNTS : INodeTestSelector
    {
        private bool useRatio;
        public InfoGainNTS(bool useRatio)
        {
            this.useRatio = useRatio;
        }

        /// <summary>
        /// Zwraca najlepszy test z podanego zbioru testów. Kryterium: Information Gain.
        /// </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;
                }
            }

            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. Jeżeli test01 lepszy od test02 to RateTest(test01) > RateTest(test02).</returns>
        public double RateTest(INodeTest nodeTest, IDataTable dataTable, ref bool shouldTerminate)
        {
            IOneFeatureData tt0, tt;
            IDataTable[] newDataTables;
            List<int[]> countsList;
            double infoGain=double.NegativeInfinity;

            if (nodeTest != null)
            {

                tt0 = (dataTable as ITargets).Targets;

                //value = CalculateEntropy(nodeTest, dataTable);
                newDataTables = nodeTest.SplitData(dataTable, ref shouldTerminate);
                countsList = new List<int[]>();
                foreach (IDataTable dataTable2 in newDataTables)
                {
                    tt = (dataTable2 as ITargets).Targets;
                    countsList.Add(tt.FeatureStatistics.counts);
                }

                double infoBeforeSplit = Tools.Info(tt0.FeatureStatistics.counts, tt0.InstanceCount);
                double infoAfterSplit = Tools.Info(countsList);
                infoGain = infoBeforeSplit - infoAfterSplit;

                if ((this.useRatio) && (infoGain >0))
                    infoGain = infoGain / SplitInformation(dataTable, newDataTables);

                nodeTest.TestRate = infoGain;
            }

            return (infoGain);
        }

        public static double SplitInformation(IDataTable dataTable, IDataTable[] dataTables)
        {
            // liczba wektorów w węle przed podziałem
            int w = dataTable.InstanceCount;

            double SI=0;
            double p;
            for (int i = 0; i < dataTables.Length; i++)
            {
                p = (double)dataTables[i].InstanceCount / w;
                if (p == 0)
                    continue;
                SI += p * Math.Log(p, 2);
            }
            return (-SI);        
        }
    }
}
