using System;
using System.Collections.Generic;
using System.Text;
using Intemi.InTrees.Implements;

namespace Intemi.InTrees
{
    public class InTreeConfig : IConfiguration
    {
        public static byte PRUNING_NONE = 0;
        public static byte PRUNING_MANUAL = 1;
        public static byte PRUNING_AUTO = 2;

        private INodeTestGenerator nodeTestGenerator;
        public INodeTestGenerator NodeTestGenerator
        {
            get { return nodeTestGenerator; }
            set { nodeTestGenerator = value; }
        }

        private INodeTestSelector nodeTestSelector;
        public INodeTestSelector NodeTestSelector
        {
            get { return nodeTestSelector; }
            set { nodeTestSelector = value; }
        }

        private IStoppingCriterion stoppingCriterium;
        public IStoppingCriterion StoppingCriterium
        {
            get { return stoppingCriterium; }
            set { stoppingCriterium = value; }
        }

        private ITargetSelector targetSelector;
        public ITargetSelector TargetSelector
        {
            get { return targetSelector; }
            set { targetSelector = value; }
        }

        private IPruning pruning = null;
        public IPruning Pruning
        {
            get { return pruning; }
            set { pruning = value; }
        }

        private IPruningConfiguration pruningConfig = null;
        public IPruningConfiguration PruningConfig
        {
            get { return pruningConfig; }
            set { pruningConfig = value; }
        }

        //private bool usePruning = false;
        //public bool UsePruning
        //{
        //    get { return usePruning; }
        //    set { usePruning = value; }
        //}

        private byte pruningMode = InTreeConfig.PRUNING_AUTO;
        public byte PruningMode
        {
            get { return pruningMode; }
            set { pruningMode = value; }
        }

        public void Configure(ConfigBuilder confBuilder)
        {
            confBuilder.DeclareInput("Dataset",
                new Type[] { typeof(IDataTable), typeof(ITargets) }, false);
            confBuilder.DeclareOutput("Classifier",null,
                new Type[] { typeof(IClassifier) });
        }
        public object Clone() 
        {
            InTreeConfig conf = new InTreeConfig();
            conf.Pruning = Pruning;
            conf.NodeTestGenerator = NodeTestGenerator;
            conf.NodeTestSelector = NodeTestSelector;
            conf.PruningConfig = PruningConfig;
            conf.StoppingCriterium = StoppingCriterium;
            conf.TargetSelector = TargetSelector;
            //conf.UsePruning = UsePruning;
            conf.PruningMode = PruningMode;
            return conf; 
        }
    }

    [Machine("INTREE", typeof(InTreeConfig))]
    public class InTree : IMachine, IClassifier
    {
        IMachineBase machineBase;
        public void SetMachineBase(IMachineBase mb) 
        { 
            machineBase = mb; 
        }

        IFeature targetsInfo;
        public IFeature TargetsInfo
        {
            get
            {
                return targetsInfo;
            }
        }

        private DecisionTree decisionTree;

        public void Run(ref bool shouldTerminate)
        {
            IPruningConfiguration pruningConf = null;

            IDataTable dataTable = machineBase.GetInput("Dataset") as IDataTable;
            targetsInfo = (dataTable as ITargets).Targets.FeatureInfo;

            InTreeConfig config = (InTreeConfig)machineBase.ConfBase.Configuration;

            if(config.PruningMode==InTreeConfig.PRUNING_AUTO)
            {
                if (config.Pruning == null)
                {
                    throw new ExceptionIncorectPruningParameters();
                }
                else
                {

                    #region przycinanie - konfiguracja podmodeli

                    //int threadCount = 10;
                    int repeatCount = 1;
                    int cvFolds = 10;

                    ConfigBase repeater = machineBase.Project.GetConfiguration("Repeater");

                    ConfigBase trainTestDistrBoard =
                        machineBase.Project.GetConfiguration("TrainTest Distribution Board");
                    TTDistributionBoardConfig ttConfig =
                        ((TTDistributionBoardConfig)trainTestDistrBoard.Configuration);
                    ttConfig.Count = cvFolds;

                    trainTestDistrBoard.Inputs.BindParentInput("Dataset", 2,"Dataset");

                    repeater.Subconfigurations[0].Outputs.Bind(
                        "Distribution Board", 0, "Distribution Board");
                    repeater.Subconfigurations[0].Subconfigurations.Add(0,
                        trainTestDistrBoard);

                    ((RepeaterConfig)repeater.Configuration).SynchronizeIO(repeater);
                    ((RepeaterConfig)repeater.Configuration).CoexistingMachines = repeatCount * cvFolds;
                    ((RepeaterConfig)repeater.Configuration).Count = repeatCount;

                    ConfigBase dt = machineBase.Project.GetConfiguration("DT");
                    DTConfig dtConfig = ((DTConfig)dt.Configuration);
                    dtConfig.StoppingCriterium = config.StoppingCriterium;
                    dtConfig.TargetSelector = config.TargetSelector;
                    dtConfig.NodeTestGenerator = config.NodeTestGenerator;
                    dtConfig.NodeTestSelector = config.NodeTestSelector;
                    // Na tym etapie drzewo nie jest przycinane
                    dtConfig.UsePruning = false;
                    dtConfig.Pruning = null;
                    dtConfig.PruningConfig = null;
                    dt.Inputs.BindParentInput("Train dataset", "Training dataset");
                    repeater.Subconfigurations[1].Subconfigurations.Add(0, dt);

                    ConfigBase dtValidator = machineBase.Project.GetConfiguration("DTValidator");
                    dtValidator.Inputs.BindParentInput("Test dataset", "Test dataset");
                    dtValidator.Inputs.BindSiblingConfig("Decision tree", 0, "Decision tree");
                    dtValidator.Inputs.BindSiblingConfig("Train dataset",0, "Output train dataset");
                    repeater.Subconfigurations[1].Subconfigurations.Add(1, dtValidator);
                        
                    ((DTVConfig)dtValidator.Configuration).Pruning = config.Pruning;

                    machineBase.Submachines.Count = 1;
                    machineBase.Submachines.Create(0, repeater); 

                    machineBase.Submachines.WaitAll();

                    List<IPruningTable> tables = new List<IPruningTable>();

                    int index;
                    DTValidator dtv;
                    for (int repeat = 0; repeat < repeatCount; repeat++)
                    {
                        for (int fold = 0; fold < cvFolds; fold++)
                        {
                            index = (2 * cvFolds + 1) * repeat + 2 + 2*fold;
                            dtv = machineBase.Submachines[0].Submachines[index].Submachines[1].Machine as DTValidator;
                            tables.Add(dtv.PruningTable);
                        }
                    }
                    // tu zbieramy PruningTable 
                    pruningConf = config.Pruning.SelectOptimalConfiguration(tables, ref shouldTerminate);
                    //Console.WriteLine((pruningConf as Pruning2Config).NodeCount);
                    
                    #endregion

                }
            }
            else if (config.PruningMode == InTreeConfig.PRUNING_MANUAL)
            {
                pruningConf = config.PruningConfig;
            }

            #region Drzewo z ostateczną konfiguracją

            ConfigBase dt2 = machineBase.Project.GetConfiguration("DT");
            DTConfig dtConfig2 = ((DTConfig)dt2.Configuration);

            //Podstawowe parametry
            dtConfig2.NodeTestGenerator = config.NodeTestGenerator;
            dtConfig2.NodeTestSelector = config.NodeTestSelector;
            dtConfig2.StoppingCriterium = config.StoppingCriterium;
            dtConfig2.TargetSelector = config.TargetSelector;

            dtConfig2.Pruning = config.Pruning;
            dtConfig2.PruningConfig = pruningConf;
            dtConfig2.UsePruning = (config.PruningMode != InTreeConfig.PRUNING_NONE);

            dt2.Inputs.BindParentInput("Train dataset", "Dataset");

            machineBase.Submachines.RemoveAll();
            machineBase.Submachines.Count = 1;
            machineBase.Submachines.Create(0, dt2);

            machineBase.Submachines.WaitAll();

            // oto ostateczne drzewo
            this.decisionTree = ((DecisionTree)machineBase.Submachines[0].GetOutput("Decision tree"));
            Console.WriteLine(this.decisionTree.CountNodes(true));

            //Form1 form1 = new Form1(this.decisionTree.Root);
            //form1.ShowDialog();

            //Console.WriteLine(this.decisionTree.Accuracy(dataTable, ref shouldTerminate));

            Console.Write("*");

            #endregion

        }

        #region IClassifier implements
        public IOneFeatureData Classify(IDataSet dataSetToClassify)
        {
            IDataTable dataTable = machineBase.GetInput("Dataset") as IDataTable;
            IOneFeatureData tt = (dataTable as ITargets).Targets;
            IOneFeatureDataBuilder t = tt.CopyStructure(dataSetToClassify.InstanceCount);

            IVectorEnumerator t2 = tt.FeatureEnumerator();
            t2.GoToInstance(0);

            IVectorEnumerator ve = (IVectorEnumerator)dataSetToClassify.InstanceEnumerator();
            IVectorEnumerator ve2 = (IVectorEnumerator)dataTable.InstanceEnumerator();

            float[] a1 = ve.Vector, a2 = ve2.Vector, labels = t2.Vector;

            for (int i = 0; i < dataSetToClassify.InstanceCount; i++)
            {
                ve.GoToInstance(i);
                DTNode dtNode = this.decisionTree.TreeWalking(ve.Vector, ve.Missing);

                if (dtNode.IsLeaf)
                    t.AddValue(dtNode.Target);
                else
                    // nadajemy etykietę klasy większościowej
                    t.AddValue(decisionTree.Root.Data.Target);
                    //throw new ExceptionClassifyError();
            }
            return (IOneFeatureData)t.Build();
        }

        public bool VerifyTestData(IDataSet d)
        {
            IDataTable inputDT = machineBase.GetInput("Dataset") as IDataTable;
            int featureCount = inputDT != null ? inputDT.FeaturesInfo.Count : 0;
            IDataTable dt = d as IDataTable;
            return dt != null && dt.FeaturesInfo.Count == featureCount;
        }
        #endregion

    }
}
