using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;

namespace InABox.Core
{
    public interface ISubObject
    {
        BaseObject GetLinkedParent();

        void SetLinkedParent(BaseObject obj);

        string GetLinkedPath();

        void SetLinkedPath(string path);
    }

    public static class LinkedProperties
    {

        private static readonly Dictionary<Type, List<ILinkedProperty>> _LinkedProperties = new Dictionary<Type, List<ILinkedProperty>>();
        
        public static void Register<TLinkedEntity, TEntityLink, TType>(Expression<Func<TLinkedEntity,TEntityLink>> path, Expression<Func<TEntityLink, TType>> source,
            Expression<Func<TLinkedEntity, TType>> target)
        {
            var map = new LinkedProperty<TLinkedEntity, TEntityLink, TType>(path, source, target);
            if(!_LinkedProperties.TryGetValue(map.Type, out var props))
            {
                props = new List<ILinkedProperty>();
                _LinkedProperties[map.Type] = props;
            }
            if(!props.Any(x => x.Equals(map)))
            {
                props.Add(map);
            }
        }

        /*public static IEnumerable<ILinkedProperty> Find(object parent, object path)
        {
            var all =  _LinkedProperties.Where(x => x.Type == parent.GetType());
            var filtered =  all.Where(x => CoreUtils.GetPropertyValue(parent,x.Path)?.GetType() == path?.GetType());
            return filtered;
        }*/

        private static BaseObject GetParent(ISubObject link)
        {
            while (true)
            {
                var parent = link.GetLinkedParent();
                if(parent is IEntityLink parentLink)
                {
                    link = parentLink;
                }
                else
                {
                    return parent;
                }
            }
        }

        private static string GetPath(ISubObject link)
        {
            var path = link.GetLinkedPath();
            while (true)
            {
                var parent = link.GetLinkedParent();
                if (parent is IEntityLink parentLink)
                {
                    link = parentLink;
                    path = $"{link.GetLinkedPath()}.{path}";
                }
                else
                {
                    return path;
                }
            }
        }

        public static IEnumerable<ILinkedProperty> Find(ISubObject subObject, out BaseObject parent)
        {
            parent = GetParent(subObject);
            if (!_LinkedProperties.TryGetValue(parent.GetType(), out var props))
            {
                return Enumerable.Empty<ILinkedProperty>();
            }

            var path = GetPath(subObject);
            return  props.Where(x => x.Path == path);
        }

        public static bool Find(ISubObject subObject, string name, [NotNullWhen(true)] out ILinkedProperty? property, out BaseObject parent)
        {
            property = null;
            parent = GetParent(subObject);
            if (parent == null)
                return false;
            if(!_LinkedProperties.TryGetValue(parent.GetType(), out var props))
                return false;
            var path = GetPath(subObject);
            property = props.FirstOrDefault(x => String.Equals($"{x.Path}{(String.IsNullOrWhiteSpace(x.Path) ? "" : ".")}{x.Source}", $"{path}{(String.IsNullOrWhiteSpace(path) ? "" : ".")}{name}"));
            return property != null;
        }
        
    }
}