using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using Quadarax.Foundation.Common.Object;
using StructureMap;

namespace Quadarax.Foundation.Infrastructure.Layers
{
    public abstract class Layer : DisposableObject
    {
        private IContainer _container;
        private IList<Layer> _childLayers = new List<Layer>();
        private Layer _parentLayer;

        protected ILog Log { get; }

        public string Name { get; }

        public int ChildLayersCount => _childLayers.Count;

        public LayerClassificationEnum LayerType { get; protected set; }

        protected Layer(string name, Layer parentLayer)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException(nameof(name), "Infrastucture layer must have a name.");
            _parentLayer = parentLayer;

            Log = LogManager.GetLogger(GetType());
            Name = name;

            InitializeInternal();

            Log.Info($"Layer '{Name}' fully created.");
        }


        private void InitializeInternal()
        {
            // _container = _parentLayer == null ? new Container() : _parentLayer._container.CreateChildContainer();
            _container = _parentLayer == null ? new Container() : _parentLayer._container;
            OnModuleRegistry(_container);
            OnLayerRegistry();
            Log.Info($"Layer '{Name}' initialization done");
        }

        protected void AddChildLayer(Layer layer)
        {
            if (layer==null)
                throw new ArgumentNullException(nameof(layer));
            _childLayers.Add(layer);
            Log.Info($"Layer 'Name' append child layer '{layer.Name}'. Current child count on layer '{Name}' is {ChildLayersCount} childs.");
        }

        protected abstract void OnLayerRegistry();
        
        protected abstract void OnModuleRegistry(IContainer container);

        protected override void OnDisposing()
        {
            _parentLayer = null;

            if (_childLayers != null)
            {
                foreach (var layer in _childLayers)
                {
                    layer.Dispose();
                }
            }

            Log.Info($"Layer '{Name}' disposed.");
        }

        public TModule Get<TModule>() where TModule : IModule
        {
            Log.Debug($"Try to find module '{typeof(TModule)}' in layer '{Name}'...");
            var result = _container.TryGetInstance<TModule>();
            if (result != null)
                return result;

            Log.Debug($"Cannot find module '{typeof(TModule)}' in layer '{Name}'. Trying to find in child layers.");

            foreach (var child in _childLayers)
            {
                result = child.Get<TModule>();
                if (result != null)
                    return result;
            }

            return default(TModule);
        }

        public IEnumerable<TLayer> FindChildLayer<TLayer>(bool recursive = true) where TLayer : Layer
        {
            var result = new List<TLayer>();
            if (recursive)
            {
                foreach (var child in _childLayers)
                {
                    result.AddRange(child.FindChildLayer<TLayer>(recursive));
                }
            }
            result.AddRange(_childLayers.Where(x => x.GetType() == typeof(TLayer)).Select(x=> (TLayer)x));
            return result.ToArray();
        }

        public IEnumerable<Layer> FindChildLayer(string layerName, bool recursive = true)
        {
            var result = new List<Layer>();
            if (recursive)
            {
                foreach (var child in _childLayers)
                {
                    result.AddRange(child.FindChildLayer(layerName, recursive));
                }
            }

            result.AddRange(_childLayers.Where(x => x.Name == layerName));
            return result.ToArray();
        }

        public IEnumerable<string> GetChildLayerNames(bool recursive = true)
        {
            var result = new List<string>();
            if (recursive)
            {
                foreach (var child in _childLayers)
                {
                    result.AddRange(child.GetChildLayerNames(recursive));
                }
            }
            result.AddRange(_childLayers.Select(x => x.Name));
            return result.ToArray();
        }

        public string Dump()
        {
            return DumpInternal(0);
        }

        private string DumpInternal(int level)
        {
            var sb = new StringBuilder();
            sb.Append(new string(' ', level * 3)).Append("*** Layer [").Append(level).Append("]: ").Append(Name).Append(" [").Append(LayerType).Append("] * BEGIN ***").AppendLine();
            sb.Append(new string(' ', level * 3)).Append("Parent:").AppendLine();
            sb.Append(_parentLayer == null ? "none" : _parentLayer.Name);
            sb.AppendLine();
            sb.Append(new string(' ', level * 3)).Append("Childs:").AppendLine();
            if (_childLayers.Count > 0)
                foreach (var child in _childLayers)
                    sb.Append(child.DumpInternal(level + 1)).AppendLine();
            else
                sb.Append(new string(' ', level * 3)).Append("no childs").AppendLine();

            sb.Append(new string(' ', level * 3)).Append("*** Layer [").Append(level).Append("]: ").Append(LayerType).Append("] * END ***").AppendLine();
            return sb.ToString();
        }

        public string DumpContainer(bool recursive = false)
        {
            var sb = new StringBuilder();
            sb.Append("LAYER: ").Append(Name).AppendLine();
            sb.AppendLine(_container.WhatDoIHave());
            if (recursive)
            {
                sb.Append("LAYER: ").Append(Name).Append(" childs").AppendLine();
                foreach (var layer in _childLayers)
                {
                    sb.AppendLine(layer.DumpContainer(recursive));
                }
            }
            return sb.ToString();
        }

        public override string ToString()
        {
            return $"Layer={Name};Type={LayerType};ChildCount={ChildLayersCount}";
        }
    }
}
