using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;

namespace InABox.Core
{
    public class CoreTreeNode : INotifyPropertyChanged
    {

        private CoreTreeNodes _owner;
        
        public ObservableCollection<CoreTreeNode> Children => new ObservableCollection<CoreTreeNode>(_owner.GetChildrenOfParent(_id));
        
        private Guid _id;
        public Guid ID 
        {
            get { return _id; }
            set
            {
                _id = value;
                RaisedOnPropertyChanged("ID");
            }
        }
        
        private Guid _parent;
        public Guid Parent 
        {
            get { return _parent; }
            set
            {
                _parent = value;
                RaisedOnPropertyChanged("Parent");
            }
        }
   
        private object _image;
        public object Image
        {
            get => _image;
            set
            {
                _image = value;
                RaisedOnPropertyChanged("Image");
            }
        }

        private CoreRow _row;
        public CoreRow Row
        {
            get => _row;
            set
            {
                _row = value;
                RaisedOnPropertyChanged("Row");
                RaisedOnPropertyChanged("Item[]");
            }
        }

        public object? this[string column]
        {
            get => Row[column];
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        public void RaisedOnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public CoreTreeNode(CoreTreeNodes owner, CoreRow row)
        {
            _owner = owner;
            _row = row;
        }

        public CoreTreeNode(CoreTreeNodes owner, Guid id, Guid parent, CoreRow row) : this(owner, row)
        {
            _id = id;
            _parent = parent;
        }

        public CoreTreeNode? GetParent() => _owner[_parent];

        public int Index()
        {
            var parent = GetParent();
            return parent != null
                ? parent.Children.IndexOf(this) + 1
                : _owner.Nodes.IndexOf(this) + 1;
        }

        public void InvalidateData()
        {
            RaisedOnPropertyChanged("Row");
            RaisedOnPropertyChanged("Item[]");
        }

        public String Number
        {
            get
            {
                String result = Index().ToString();
                var parent = GetParent();
                while (parent != null)
                {
                    int index = parent.Index();
                    result = $"{index}.{result}";
                    parent = parent.GetParent();
                }
                return result;
            }
        }
        
    }

    public class CoreTreeNodes 
    {

        private List<CoreTreeNode> _nodes;
        private Dictionary<Guid, CoreTreeNode> _nodeMap;

        public ObservableCollection<CoreTreeNode> Nodes => new ObservableCollection<CoreTreeNode>(_nodes.Where(x => x.Parent == Guid.Empty));
        
        public CoreTreeNode? this[Guid id] => _nodes.FirstOrDefault(x => x.ID == id);
        
        public CoreTreeNodes()
        {
            _nodes = new List<CoreTreeNode>();
            _nodeMap = new Dictionary<Guid, CoreTreeNode>();
        }

        public CoreTreeNode Add(Guid id, Guid parent, CoreRow row)
        {
            var node = new CoreTreeNode(this, id, parent, row);
            _nodes.Add(node);
            _nodeMap[id] = node;
            return node;
        }

        public CoreTreeNode Find(Guid id) => _nodeMap.GetValueOrDefault(id);

        public CoreTreeNode? Find(CoreRow row) => _nodes.FirstOrDefault(x => x.Row == row);

        /// <summary>
        /// Gets a given node and recursively all its children given by <paramref name="id"/>.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public IEnumerable<CoreTreeNode> GetChildren(Guid id)
        {
            if(!_nodeMap.TryGetValue(id, out var node))
            {
                yield break;
            }
            yield return node;
            var children = GetChildrenOfParent(id);
            foreach (var child in children)
                foreach (var item in GetChildren(child.ID))
                    yield return item;
        }
        
        /// <summary>
        /// Get all the direct children of the parent given by <paramref name="id"/>.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public IEnumerable<CoreTreeNode> GetChildrenOfParent(Guid id)
        {
            return _nodes.Where(x => x.Parent.Equals(id) && (x.ID != id));
        }

        public void Load<T>(CoreTable table, Expression<Func<T, Guid>> id, Expression<Func<T, Guid>> parentid)
        {
            _nodes.Clear();
            foreach (var row in table.Rows)
            {
                var _id = row.Get(id);
                var _parent = row.Get(parentid);
                Add(_id, _parent, row);
            }
        }
        
    }
}