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

namespace InABox.Core
{
    public interface ICoreTreeNode
    {
        object? ID { get; set; }
        object? Parent { get; set; }
        object? Image { get; set; }
        CoreRow? Row { get; set; }
        object? this[string column] { get; set; }
        string Number { get; }
    }

    public class CoreTreeNode<TKey> : INotifyPropertyChanged, ICoreTreeNode
    {

        private CoreTreeNodes<TKey> _owner;
        
        public ObservableCollection<CoreTreeNode<TKey>> Children => new ObservableCollection<CoreTreeNode<TKey>>(_owner.GetChildrenOfParent(_id));

        object? ICoreTreeNode.ID
        {
            get => ID;
            set => ID = (TKey)value!;
        }
        
        private TKey _id;
        public TKey ID 
        {
            get { return _id; }
            set
            {
                _id = value;
                RaisedOnPropertyChanged("ID");
            }
        }
        
        object? ICoreTreeNode.Parent
        {
            get => Parent;
            set => Parent = (TKey)value!;
        }

        
        private TKey _parent;
        public TKey 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];
            set
            {
                Row[column] = value;
                _owner.DoColumnChanged(this, column);
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

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

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

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

        public CoreTreeNode<TKey>? 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 int Depth()
        {
            var result = 0;
            foreach (var child in Children)
                result = Math.Max(result, child.Depth());
            return 1 + result;
        }
        
    }

    public class CoreTreeNodes<TKey>
    {
        private List<CoreTreeNode<TKey>> _nodes;
        private Dictionary<TKey, CoreTreeNode<TKey>> _nodeMap;
        
        public TKey DefaultKey { get; set; }

        public ObservableCollection<CoreTreeNode<TKey>> Nodes { get; } = new ObservableCollection<CoreTreeNode<TKey>>();
        public CoreTreeNode<TKey>? this[TKey id] => _nodeMap.GetValueOrDefault(id);
        
        public CoreTreeNodes(TKey defaultKey)
        {
            _nodes = new List<CoreTreeNode<TKey>>();
            _nodeMap = new Dictionary<TKey, CoreTreeNode<TKey>>();
            DefaultKey = defaultKey;
        }

        public CoreTreeNode<TKey> Add(TKey id, TKey parent, CoreRow row)
        {
            var node = new CoreTreeNode<TKey>(this, id, parent, row);
            _nodes.Add(node);
            _nodeMap[id] = node;
            if(Equals(node.Parent, DefaultKey))
            {
                Nodes.Add(node);
            }
            return node;
        }

        public void Clear()
        {
            _nodes.Clear();
            _nodeMap.Clear();
            Nodes.Clear();
        }

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

        public CoreTreeNode<TKey>? 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<TKey>> GetChildren(TKey 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<TKey>> GetChildrenOfParent(TKey id)
        {
            return _nodes.Where(x => object.Equals(x.Parent, id) && !object.Equals(x.ID, id));
        }

        public void Load<T>(CoreTable table, Expression<Func<T, TKey>> id, Expression<Func<T, TKey>> parentid)
        {
            _nodes.Clear();
            foreach (var row in table.Rows)
            {
                var _id = row.Get(id);
                var _parent = row.Get(parentid);
                Add(_id, _parent, row);
            }
        }
        
        public delegate void ColumnChangedEventHandler(CoreTreeNode<TKey> node, string column);
        public event ColumnChangedEventHandler? ColumnChanged;

        internal void DoColumnChanged(CoreTreeNode<TKey> node, string column)
        {
            ColumnChanged?.Invoke(node, column);
        }

        public int Depth()
        {
            var result = 0;
            var roots = Nodes.Where(x => Equals(DefaultKey, x.Parent));
            foreach (var root in roots)
                result = Math.Max(result, root.Depth());
            return result;
        }
        
    }

}