using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace InABox.Core
{
    public interface IPostResult<TPostable>
        where TPostable : IPostable
    {
        /// <summary>
        /// All successful or failed <typeparamref name="TPostable"/>s.
        /// </summary>
        public IEnumerable<TPostable> PostedEntities { get; }
        public IEnumerable<TPostable> SuccessfulEntities => PostedEntities.Where(x => x.PostedStatus == PostedStatus.Posted);
        public IEnumerable<TPostable> FailedEntities => PostedEntities.Where(x => x.PostedStatus == PostedStatus.PostFailed);

        public IEnumerable<KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>> Fragments { get; }
    }

    public class PostResult<TPostable> : IPostResult<TPostable>
        where TPostable : IPostable
    {
        private List<TPostable> posts = new List<TPostable>();

        private Dictionary<Type, List<IPostableFragment<TPostable>>> fragments = new Dictionary<Type, List<IPostableFragment<TPostable>>>();

        /// <summary>
        /// All successful or failed <typeparamref name="TPostable"/>s.
        /// </summary>
        public IEnumerable<TPostable> PostedEntities => posts;

        public IEnumerable<KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>> Fragments => 
            fragments.Select(x => new KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>(x.Key, x.Value));

        public void AddSuccess(TPostable post)
        {
            post.Post();
            posts.Add(post);
        }

        public void AddFailed(TPostable post, string note)
        {
            post.FailPost(note);
            posts.Add(post);
        }

        public void AddFragment<TPostableFragment>(TPostableFragment fragment, PostedStatus status)
            where TPostableFragment : IPostableFragment<TPostable>, IPostable
        {
            var type = fragment.GetType();
            if (!fragments.TryGetValue(type, out var fragmentsList))
            {
                fragmentsList = new List<IPostableFragment<TPostable>>();
                fragments[type] = fragmentsList;
            }
            fragment.Post();
            fragment.PostedStatus = status;
            fragmentsList.Add(fragment);
        }

        public void AddFragment(IPostableFragment<TPostable> fragment)
        {
            var type = fragment.GetType();
            if (!fragments.TryGetValue(type, out var fragmentsList))
            {
                fragmentsList = new List<IPostableFragment<TPostable>>();
                fragments[type] = fragmentsList;
            }
            if(fragment is IPostable postableFragment)
            {
                postableFragment.Post();
            }
            fragmentsList.Add(fragment);
        }
    }

    public enum PullResultType
    {
        New,
        Linked,
        Updated
    }

    public class PullResultItem<TPostable>
        where TPostable : IPostable
    {
        public PullResultType Type { get; set; }

        public TPostable Item { get; set; }

        public PullResultItem(PullResultType type, TPostable item)
        {
            Type = type;
            Item = item;
        }
    }

    public interface IPullResult<TPostable>
        where TPostable : IPostable
    {
        public IEnumerable<PullResultItem<TPostable>> PulledEntities { get; }
    }

    public class PullResult<TPostable> : IPullResult<TPostable>
        where TPostable : IPostable
    {
        private List<PullResultItem<TPostable>> posts = new List<PullResultItem<TPostable>>();

        /// <summary>
        /// All successful or failed <typeparamref name="TPostable"/>s.
        /// </summary>
        public IEnumerable<PullResultItem<TPostable>> PulledEntities => posts;

        public void AddEntity(PullResultType type, TPostable post)
        {
            post.Post();
            posts.Add(new PullResultItem<TPostable>(type, post));
        }
    }
}
