using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MediaBrowser.Controller.Providers
{
    public class ItemIdentifier<TLookupInfo, TIdentity>
        where TLookupInfo : ItemLookupInfo
        where TIdentity : IItemIdentity
    {
        public async Task<IEnumerable<TIdentity>> FindIdentities(TLookupInfo item, IProviderManager providerManager, CancellationToken cancellationToken)
        {
            var providers = providerManager.GetItemIdentityProviders<TLookupInfo, TIdentity>();
            var converters = providerManager.GetItemIdentityConverters<TIdentity>();

            var identities = new List<IdentityPair>();

            foreach (var provider in providers)
            {
                var result = new IdentityPair
                {
                    Identity = await provider.FindIdentity(item),
                    Order = provider.Order
                };

                if (!Equals(result.Identity, default(TIdentity)))
                {
                    identities.Add(result);
                }
            }

            var convertersAvailable = new List<IItemIdentityConverter<TIdentity>>(converters);
            bool changesMade;

            do
            {
                changesMade = false;

                for (int i = convertersAvailable.Count - 1; i >= 0; i--)
                {
                    var converter = convertersAvailable[i];
                    var input = identities.FirstOrDefault(id => id.Identity.Type == converter.SourceType);
                    var existing = identities.Where(id => id.Identity.Type == converter.ResultType);

                    if (input != null && !existing.Any(id => id.Order <= converter.Order))
                    {
                        var result = new IdentityPair
                        {
                            Identity = await converter.Convert(input.Identity).ConfigureAwait(false),
                            Order = converter.Order
                        };

                        if (!Equals(result.Identity, default(TIdentity)))
                        {
                            identities.Add(result);
                            convertersAvailable.RemoveAt(i);
                            changesMade = true;
                        }
                    }
                }
            } while (changesMade);

            return identities.OrderBy(id => id.Order).Select(id => id.Identity);
        }

        private class IdentityPair
        {
            public TIdentity Identity;
            public int Order;
        }
    }
}