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

namespace Intemi.InTrees
{
    /// <summary>
    /// Machine węzła drzewa.
    /// </summary>
    public class DTNode
    {
        /// <summary>
        /// Określa czy dany test jest liściem.
        /// </summary>
        private bool isLeaf;
        public bool IsLeaf
        {
            get { return isLeaf; }
            set { isLeaf = value; }
        }

        /// <summary>
        /// Domyślna etykieta kategori przykładów wpadających do tego węzła.
        /// </summary>
        private int target;
        public int Target
        {
            get { return target; }
            set { target = value; }
        }
       
        /// <summary>
        /// Test przypisany do tego węzła.
        /// </summary>
        private INodeTest nodeTest;
        public INodeTest NodeTest
        {
            get { return nodeTest; }
            set { nodeTest = value; }
        }

        /// <summary>
        ///  Określa dokładny warunek, pochodzący z węzła nadrzędnego 
        ///  znajdujący się na krawędzi prowadzącej do tego węzła.
        /// </summary>
        private IClassificationRule rule;
        public IClassificationRule Rule
        {
            get { return rule; }
            set { rule = value; }
        }

        private IFeaturesInfo featureInfo;
        public IFeaturesInfo FeatureInfo
        {
            get { return featureInfo; }
        }

        private double accuracy;
        public double Accuracy
        {
            get { return accuracy; }
            set { accuracy = value; }
        }

        private int positive;
        public int Positive
        {
            get { return positive; }
            set { positive = value; }
        }

        private int vectorsCount;
        public int InstanceCount
        {
            get { return vectorsCount; }
            set { vectorsCount = value; }
        }

        private IDataTable dataTable;
        public IDataTable DataTable
        {
            get { return dataTable; }
            set { dataTable = value; }
        }

        public DTNode(IFeaturesInfo featureInfo, IClassificationRule rule, INodeTest nodeTest, int target, bool isLeaf)
        {
            this.featureInfo = featureInfo;
            this.rule = rule;
            this.nodeTest = nodeTest;
            this.target = target;
            this.IsLeaf = isLeaf;           
            DataReset();
        }


        //public string TargetString()
        //{
        //    return(this.featureInfo[0].Value(this.Target));
        //}

        public void DataRecalculate(IDataTable dataTable, ref bool shouldTerminate)
        {
            IOneFeatureData tt = (dataTable as ITargets).Targets;
            IVectorEnumerator ve = (IVectorEnumerator)tt.InstanceEnumerator();
            int posit = 0;
            
            float[] vector;
            for (int i = 0; !shouldTerminate && i < tt.InstanceCount; i++)
            {
                ve.GoToInstance(i);
                vector = ve.Vector;
;
                if ((int)vector[0] == this.Target)
                    posit++;
            }

            this.DataTable = dataTable;
            this.Positive = posit;
            this.InstanceCount = dataTable.InstanceCount;
            this.Accuracy = (float)this.Positive / (float)this.InstanceCount;
        }

        public void DataReset()
        {
            this.Positive = -1;
            this.Accuracy = double.NaN;
            this.InstanceCount = -1;
            this.DataTable = null;
        }


        public override string ToString()
        {
            //string s = "---" + Rule + "--->" + NodeTest+" etykieta: "+Target;
            string nodeLabel="";
            if (isLeaf)
            {
                //nodeLabel = "Liść (etykieta: " + target + ")";
                nodeLabel = string.Format("{0} Liść (etykieta: {1}) __vc: {2} ps: {3} acc: {4}__ ",NodeTest, target,this.InstanceCount, this.Positive, this.Accuracy);
            }
            else
            {
                //nodeLabel = NodeTest + " (etykieta: " + target + "test rate: " + NodeTest.TestRate+" )";
                nodeLabel = string.Format("{0} (etykieta: {1}; test rate: {2}) __vc: {3} ps: {4} acc: {5}__ ", NodeTest, target, NodeTest.TestRate,this.InstanceCount, this.Positive, this.Accuracy);
            }
            return (nodeLabel);
        }

        public string ToHtml()
        {
            return (ToString());
        }
    }

    /// <summary>
    /// Drzewo decyzyjne
    /// </summary>
    public class DecisionTree : IOutput, IIntemi
    {
        private Node<DTNode> root;
        public Node<DTNode> Root
        {
            get { return root; }
            set { root = value; }
        }

        public DecisionTree(Node<DTNode> root)
        {
            this.root = root;
        }

        /// <summary>
        /// Zwraca węzał drzewa z najniższego możliwie poziomu, którego reguła podziału jest spełniona przez wektor
        /// </summary>
        /// <param name="data">Wektor danych</param>
        /// <param name="miss">Informacja o brakujących wartościach</param>
        /// <returns>Węzał drzewa z najniższego możliwie poziomu, którego reguła podziału jest spełniona przez wektor</returns>
        public DTNode TreeWalking(float[] data, bool[] miss)
        {
            return (TreeWalking(this.root,data,miss).Data);
        }
        
        // Metoda pomocniczna dla publicznego TreeWalking
        private Node<DTNode> TreeWalking(Node<DTNode> node0, float[] data, bool[] miss)
        {
            Node<DTNode> finalNode = node0;
            foreach (Node<DTNode> node in node0.Children)
            {
                if (node.Data.Rule.Satisfied(data, miss, node.Data.FeatureInfo))
                {
                    finalNode = TreeWalking(node, data, miss);
                }
            }
            return (finalNode);
        }

        /// <summary>
        /// Zlicza błędy drzewa dla konretnego zbioru danych
        /// </summary>
        /// <param name="dataTable">Zbiór danych</param>
        /// <param name="shouldTerminate">Polecenie przerwania procedury</param>
        /// <returns>Ilość błędnie sklasyfikowanych przykładów</returns>
        public int CountErrors(IDataTable dataTable, ref bool shouldTerminate)
        {
            IOneFeatureData tt = (dataTable as ITargets).Targets;
            IVectorEnumerator vt = (IVectorEnumerator)tt.InstanceEnumerator(); //etykiety
            IVectorEnumerator vd = (IVectorEnumerator)dataTable.InstanceEnumerator(); //dane

            int label, trueLabel;
            int errors = 0;
            for (int i = 0; i < dataTable.InstanceCount; i++)
            {
                vt.GoToInstance(i);
                vd.GoToInstance(i);

                // Jeżeli to co wskaże klasyfikator nie zgadza się z rzeczywistą etykietą klasy... 
                trueLabel = (int)vt.Vector[0];
                label = TreeWalking(vd.Vector, vd.Missing).Target;

                if (trueLabel != label)
                {
                    // ...zwiększamy ilość błędów
                    errors++;
                }
            }
            return (errors);
        }

        /// <summary>
        /// Oblicza wrtość Accuracy drzewa dla konretnego zbioru danych
        /// </summary>
        /// <param name="dataTable">Zbiór danych</param>
        /// <param name="shouldTerminate">Polecenie przerwania procedury</param>
        /// <returns>Accuracy</returns>
        public double Accuracy(IDataTable dataTable, ref bool shouldTerminate)
        {
            int errors = this.CountErrors(dataTable, ref shouldTerminate);
            int vec = dataTable.InstanceCount;
            return ((double)(vec - errors) / (double)vec);
        }

        /// <summary>
        /// Zwraca ilość węzłów w drzewie
        /// </summary>
        /// <param name="withLeaves">Określa czy zliczać liście</param>
        /// <returns>Ilość węzłów w drzewie</returns>
        public int CountNodes(bool withLeaves)
        {
            return (root.CountNodes(withLeaves));
        }
    }
}
