using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;

namespace LabelerCore.Model.Classifiers
{
    [Serializable]
    public class Node<T>
    {
        public List<Node<T>> children = new List<Node<T>>();
        [XmlAttribute] 
        public string ID;
        public T data;
    }

    public class Tree<T>
    {
        public Node<T> Root { get;  protected set; }
        public Node<T> Current { get; protected set; }

        private Node<T> _getNode(Node<T> node, string nodeID)
        {
            if (node == null)
                return null;
            else if(node.ID == nodeID)
                return node;
            else
            {
                foreach(Node<T> child in node.children)
                {
                    Node<T> x = _getNode(child, nodeID);
                    if (x != null)
                        return x;
                }
                return null;
            }
        }

        public Tree(Node<T> root)
        {
            Root = root;
            Current = Root;
        }

        public void Insert(Node<T> node)
        {
            Current.children.Add(node);
            Current = Current.children[Current.children.Count - 1];
        }

        public void Add(Node<T> node)
        {
            Current.children.Add(node);
        }

        public Node<T> GetNode(string nodeID)
        {
            return _getNode(Root, nodeID);
        }

        public bool MoveTo(string nodeID)
        {
            Node<T> node = GetNode(nodeID);
            if (node == null)
                return false;
            else
            {
                Current = node;
                return true;
            }
        }
    }

    public interface IClfNodeData
    {
        IClassifier Clf {get ; set;}
        HashSet<int> Classes {get; set;}
    }

    public class ClfTree<T> : IClassifier
        where T : IClfNodeData
    {
        private Tree<T> _tree;
        private Transformers.MinMaxScaler _minMax = new Transformers.MinMaxScaler();

        private double[][] _XFull;
        private int[] _yFull;

        public ClfTree(Tree<T> tree)
        {
            _tree = tree;
        }

        public void Fit(double[][] X, int[] y)
        {
            CheckInputs.CheckNumberOfSamples(X, y);
            _XFull = _minMax.FitTransform(X);
            _yFull = y;
            _fitTree(_tree.Root);
        }
        
        private void _fitTree(Node<T> root)
        {
            if(root != null)
            {
                //get train data for this node (X[y==node_classes], y[y in node_classes])
                int[] ind = _yFull.Select((v, i) => new KeyValuePair<int, int>(v, i))
                                   .Where(x => root.data.Classes.Contains(x.Key))
                                   .Select(x => x.Value).ToArray();
                root.data.Clf.Fit(Model.Math.Utils.Select(_XFull, ind), 
                                  Model.Math.Utils.Select(_yFull, ind));
                foreach(Node<T> child in root.children)
                    _fitTree(child);
            }
        }

        public int[] Predict(double[][] X)
        {
            CheckInputs.CheckNumberOfFeatures(_XFull, X);
            double[][] XTest = _minMax.Transform(X);
            int[] yTest = new int[X.Length];
            //assume that root node have all classes
            int[] ind = Enumerable.Range(0, X.Length).ToArray();
            _predict(_tree.Root, ind, XTest, ref yTest);
            return yTest;
        }

        private void _predict(Node<T> node, int[] ind, double[][] XTest, ref int[] yTest)
        {
            if (node == null)
                return;
            else
            {
                if(ind.Length > 0)
                {
                    int[] pred = node.data.Clf.Predict(Math.Utils.Select(XTest, ind));
                    for (int i = 0; i < ind.Length; i++)
                        yTest[ind[i]] = pred[i];
                }
                //if node is null, ind will be ignored
                List<int[]> indLs = new List<int[]>();
                foreach(Node<T> child in node.children)
                {
                    if(child != null)
                    {
                        //yTest in node classes
                        int[] ind_ = yTest.Select((v, i) => new KeyValuePair<int, int>(v, i))
                                          .Where(x => child.data.Classes.Contains(x.Key))
                                          .Select(x => x.Value).ToArray();
                        indLs.Add(ind_);
                    }
                }
                for(int i = 0; i < node.children.Count; i++)
                    _predict(node.children[i], indLs[i], XTest, ref yTest);
            }
        }

        public int Predict(double[] x)
        {
            return Predict(new double[][] { x })[0];
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }

    }


}
