﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Silk.Mapping.Tests
{
	[TestClass]
	public class InflaterTests
	{
		private readonly IServiceCollection _serviceCollection = new ServiceCollection();
		private readonly IServiceProvider _serviceProvider;
		private readonly IObjectMapper _mapper;

		public InflaterTests()
		{
			_serviceCollection.AddSilkMappingServices()
				.Configure<MappingOptions>(options =>
				{
					options.AddInflater<int, InflateProperty, PropertyInflater>()
						.AddSuffix("Id");
					options.CreateMapping<InflateSource, InflateTarget>();
					options.CreateMapping<InflateSourceWithMultipleProperties, InflateTargetWithMultipleProperties>();
					options.CreateMapping<InflateSourceWithChildren, InflateTargetWithChildren>();
				});
			_serviceProvider = _serviceCollection.BuildServiceProvider();
			_mapper = _serviceProvider.GetRequiredService<IObjectMapper>();
		}

		[TestMethod]
		public void InflateWithSuffix()
		{
			var source = new InflateSource { PropertyId = 5 };
			var inflatedObjects = _mapper.Inflate<InflateTarget>(source);
			Assert.AreEqual(inflatedObjects.Count, 1);
			var inflatedObject = inflatedObjects.GetInflatedObject<InflateProperty, int>(source.PropertyId);
			Assert.IsNotNull(inflatedObject);
			Assert.AreEqual(inflatedObject.Id, source.PropertyId);
		}

		[TestMethod]
		public void InflateAndMapWithSuffix()
		{
			var source = new InflateSource { PropertyId = 5 };
			var mapped = _mapper.Map<InflateTarget>(source);
			Assert.IsNotNull(mapped);
			Assert.IsNotNull(mapped.Property);
			Assert.AreEqual(mapped.Property.Id, source.PropertyId);
		}

		[TestMethod]
		public void InflateOnlyCalledOnceWithEnumerableSource()
		{
			PropertyInflater.ResetCounters();
			var source = new[]
			{
				new InflateSource { PropertyId = 5 },
				new InflateSource { PropertyId = 6 },
				new InflateSource { PropertyId = 7 }
			};
			var mapped = _mapper.Map<InflateTarget>(source);
			Assert.AreEqual(mapped.Count(), 3);
			Assert.AreEqual(PropertyInflater.InvokeCount, 1);
			Assert.AreEqual(PropertyInflater.LastSourceCount, 3);
		}

		[TestMethod]
		public void InflateOnlyCalledOnceWithMulitpleProperties()
		{
			PropertyInflater.ResetCounters();
			var source = new[]
			{
				new InflateSourceWithMultipleProperties { PropertyOneId = 5, PropertyTwoId = 6 },
				new InflateSourceWithMultipleProperties { PropertyOneId = 7, PropertyTwoId = 8 },
				new InflateSourceWithMultipleProperties { PropertyOneId = 9, PropertyTwoId = 10 }
			};
			var mapped = _mapper.Map<InflateTargetWithMultipleProperties>(source);
			Assert.AreEqual(mapped.Count(), 3);
			Assert.AreEqual(PropertyInflater.InvokeCount, 1);
			Assert.AreEqual(PropertyInflater.LastSourceCount, 6);
		}

		[TestMethod]
		public void InflateChildrenAndPropertyInSingleCall()
		{
			PropertyInflater.ResetCounters();
			var source = new InflateSourceWithChildren
			{
				RootId = 4,
				PropertyOne = new InflateSource { PropertyId = 5 },
				PropertyTwo = new InflateSource { PropertyId = 6 }
			};
			var mapped = _mapper.Map<InflateTargetWithChildren>(source);
			Assert.IsNotNull(mapped);
			Assert.IsNotNull(mapped.Root);
			Assert.IsNotNull(mapped.PropertyOne);
			Assert.IsNotNull(mapped.PropertyOne.Property);
			Assert.IsNotNull(mapped.PropertyTwo);
			Assert.IsNotNull(mapped.PropertyTwo.Property);
			Assert.AreEqual(PropertyInflater.InvokeCount, 1);
			Assert.AreEqual(PropertyInflater.LastSourceCount, 3);
			Assert.AreEqual(source.RootId, mapped.Root.Id);
			Assert.AreEqual(source.PropertyOne.PropertyId, mapped.PropertyOne.Property.Id);
			Assert.AreEqual(source.PropertyTwo.PropertyId, mapped.PropertyTwo.Property.Id);
		}

		private class InflateSource
		{
			public int PropertyId { get; set; }
		}

		private class InflateSourceWithMultipleProperties
		{
			public int PropertyOneId { get; set; }
			public int PropertyTwoId { get; set; }
		}

		private class InflateSourceWithChildren
		{
			public int RootId { get; set; }
			public InflateSource PropertyOne { get; set; }
			public InflateSource PropertyTwo { get; set; }
		}

		private class InflateTarget
		{
			public InflateProperty Property { get; set; }
		}

		private class InflateTargetWithMultipleProperties
		{
			public InflateProperty PropertyOne { get; set; }
			public InflateProperty PropertyTwo { get; set; }
		}

		private class InflateTargetWithChildren
		{
			public InflateProperty Root { get; set; }
			public InflateTarget PropertyOne { get; set; }
			public InflateTarget PropertyTwo { get; set; }
		}

		private class InflateProperty
		{
			public int Id { get; }

			public InflateProperty(int id)
			{
				Id = id;
			}
		}

		private class PropertyInflater : IInflater<int, InflateProperty>
		{
			public static int LastSourceCount { get; private set; }
			public static int InvokeCount { get; private set; }

			public static void ResetCounters()
			{
				LastSourceCount = 0;
				InvokeCount = 0;
			}

			public IEnumerable<InflateProperty> Inflate(IEnumerable<int> source)
			{
				LastSourceCount = source.Count();
				InvokeCount++;
				var ret = new List<InflateProperty>();
				foreach(var id in source)
				{
					ret.Add(new InflateProperty(id));
				}
				return ret;
			}

			public Task<IEnumerable<InflateProperty>> InflateAsync(IEnumerable<int> source)
			{
				return Task.FromResult(Inflate(source));
			}
		}
	}
}
