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

namespace Intemi.InTrees.Implements
{
    public class Pruning2Config : IPruningConfiguration
    {
        private int nodeCount;
        public int NodeCount
        {
            get { return nodeCount; }
            set { nodeCount = value; }
        }
    }

    //public class Pruning2Result : IPruningResult
    //{
    //    private double accuracy;
    //    public double Accuracy
    //    {
    //        get { return accuracy; }
    //        set { accuracy = value; }
    //    }
    //}

    public class Pruning2Table : IPruningTable
    {
        public Pruning2Table(int maxNodeCount)
        {
            this.maxNodeCount = maxNodeCount;
            accuracy = new double[maxNodeCount+1];
        }

        //private IPruningConfiguration[] configuration;
        //public IPruningConfiguration[] Configuration
        //{
        //    get { return configuration; }
        //}

        private double[] accuracy;
        public double[] Accuracy
        {
            get { return accuracy; }
        }

        private int maxNodeCount;
        public int MaxNodeCount
        {
            get { return maxNodeCount; }
            //set { maxNodeCount = value; }
        }
    }

    public class Pruning2 : IPruning
    {
        /// <summary>
        /// Przycina drzewo stosując według kryteriów zawartych w konfiguracji przycinania
        /// </summary>
        /// <param name="tree">Drzewo do przycięcia</param>
        /// <param name="config">Konfiguracja przycinania</param>
        /// <param name="shouldTerminate">Polecenie przerwania procedury</param>
        /// <returns>Przycięte drzewo</returns>
        public DecisionTree Prune(DecisionTree tree, IPruningConfiguration config, IDataTable trainDT, ref bool shouldTerminate)
        {
            List<Node<DTNode>> candidates = new List<Node<DTNode>>();
            //IEnumerator<Node<DTNode>> treeEn = tree.GetBreadthFirstEnumerator();

            int nodeCount = tree.CountNodes(false);
            candidates = FindCandidates(tree.Root, ref shouldTerminate); 

            // W tym momencie mamy 
            // 1. Ilość węzłów w drzewie nie będących liśćmi
            // 2. Kolekcję węzłów kandydujących do przycięcia

            Node<DTNode> bestNode;
            double bestAcc;

            int maxCount = (config as Pruning2Config).NodeCount;
            while (nodeCount > maxCount)
            {
                // szukamy najlepszego węzła do przycięcia
                bestNode = null;
                bestAcc = 0;
                foreach (Node<DTNode> node in candidates)
                {
                    //node.Data.DataRecalculate(trainDT, ref shouldTerminate);
                    if (node.Data.Accuracy > bestAcc)
                    {
                        bestNode = node;
                        bestAcc = node.Data.Accuracy;
                    }
                }

                // przycinamy węzeł
                if (bestNode != null)
                {
                    bestNode.Data.IsLeaf = true;
                    bestNode.Children.Clear();
                    
                    // węzeł stał się liściem - usuwamy go z listy kandydatów
                    candidates.Remove(bestNode);

                    // ilość węzłów w drzewie zmiejsza się
                    nodeCount--;

                    // jeżeli w wyniku przycięcia węzła jego rodzic stał się węzłem posiadającym tylko dzieci-liście
                    // rodzic staje się kandydatem do przycięcia,
                    // ale jeżeli nodeCount <= maxCount to nie ma sensu sprawdzać, bo kończymy przycinanie
                    if ((nodeCount > maxCount) && IsCandidate(bestNode.Parent))
                    {
                        candidates.Add(bestNode.Parent);
                    }
                }
            }

            return (tree);
        }

        private List<Node<DTNode>> FindCandidates(Node<DTNode> node, ref bool shouldTerminate)
        {
            List<Node<DTNode>> candidates = new List<Node<DTNode>>();
            bool isCandidate = true;
            foreach (Node<DTNode> child in node.Children)
            {
                if (!child.Data.IsLeaf)
                {
                    isCandidate = false;
                    candidates.AddRange(FindCandidates(child, ref shouldTerminate));
                }
            }

            if (isCandidate)
                candidates.Add(node);

            return (candidates);
        }

        private bool IsCandidate(Node<DTNode> node)
        {
            bool isCandidate = true;
            foreach (Node<DTNode> node0 in node.Children)
            {
                if (!node0.Data.IsLeaf)
                {
                    isCandidate = false;
                    break;
                }
            }
            return (isCandidate);
        }

        /// <summary>
        /// Wskazuje optymalną konfigurację przycinania
        /// </summary>
        /// <param name="tables">Tablice zawierające konfigurację przycinania i osiągnięte przez nie wyniki klasyfikacji</param>
        /// <param name="shouldTerminate">Polecenie przerwania procedury</param>
        /// <returns>Optymalna konfiguracja</returns>
        public IPruningConfiguration SelectOptimalConfiguration(List<IPruningTable> tables, ref bool shouldTerminate)
        {
            // ustalamy największą liczbę wezłów w drzewie
            int globalMaxNC = 0;
            int nodeCount;
            foreach (IPruningTable table in tables)
            {
                nodeCount = (table as Pruning2Table).MaxNodeCount;
                if (nodeCount > globalMaxNC)
                    globalMaxNC = nodeCount;
            }

            double[,] acc = new double[tables.Count,globalMaxNC+1];

            //double accur;

            // tworzymy dla każdej PruningTable tworzymy tabelkę zawierającą osiągniętą Acc dla danej ilości węzłów
            Pruning2Table pTable;
            int i = 0;
            foreach (IPruningTable table in tables)
            {
                pTable = table as Pruning2Table; 
                for (int a = 0; a <= pTable.MaxNodeCount; a++)
                {
                    acc[i, a] = pTable.Accuracy[a];
                }
                i++;
            }

            // dla każdej ilości węzłów obliczamy średnią accuracy i szukamy najwyższej
            double[] avAcc = new double[globalMaxNC+1];
            //double maxAcc = 0;
            nodeCount=0;
            for (int k = 0; k <= globalMaxNC; k++)
            {
                for(int l=0;l<tables.Count;l++)
                {
                    if (acc[l,k]>0)
                        avAcc[k]+=acc[l,k];
                    else
                    {
                        // oznacza to że taka ilość węzłów nie wystąpiła w którymś zestawie, więc nie może być ona optymalną
                        avAcc[k]=0;
                        break;
                    }
                }
                avAcc[k] = avAcc[k]/tables.Count;
                if (avAcc[k] > avAcc[nodeCount])
                {
                    nodeCount = k;
                }
            }

            Pruning2Config conf = new Pruning2Config();
            conf.NodeCount = nodeCount;

            return (conf);
        }

        /// <summary>
        /// Przycina drzewo w różnych konfiguracjach i zwraca tablicę zawierającą tego efekty
        /// </summary>
        /// <param name="tree">Drzewo</param>
        /// <param name="shouldTerminate">Polecenie przerwania procedury</param>
        /// <returns>Tablica zawierająca kongigurację przycinania i osiągnięte przez nie wyniki klasyfikacji</returns>
        public IPruningTable BuildPruningTable(DecisionTree tree, IDataTable trainDT, IDataTable testDT, ref bool shouldTerminate)
        {
            int nodeCount = tree.CountNodes(false);
            Pruning2Table pTable = new Pruning2Table(nodeCount);
            Pruning2Config config = new Pruning2Config();

            pTable.Accuracy[nodeCount]=tree.Accuracy(testDT,ref shouldTerminate);
            while (nodeCount > 0)
            {
                config.NodeCount = nodeCount - 1;
                tree = Prune(tree, config, trainDT, ref shouldTerminate);
                nodeCount--;
                pTable.Accuracy[nodeCount] = tree.Accuracy(testDT, ref shouldTerminate);
            }

            return (pTable);
        }
    }
}
