﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;

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

		public ObjectConstructionTests()
		{
			_serviceCollection.AddSilkMappingServices()
				.Configure<MappingOptions>(options =>
			{
				options.CreateMapping<PocoA, PocoB>();
				options.CreateMapping<PocoB, PocoA>().UseCtor((services,from) => new PocoA("Test"));
				options.CreateMapping<PocoA, PocoC>().ApplyOverride(new AToCOverride());
				options.CreateMapping<PocoA, PocoWithPrivateCtor>();
			});
			_serviceProvider = _serviceCollection.BuildServiceProvider();
			_mapper = _serviceProvider.GetRequiredService<IObjectMapper>();
		}

		[TestMethod]
		public void ConstructPocoWithPrivateCtor()
		{
			var pocoInstance = _mapper.CreateInstance<PocoA, PocoWithPrivateCtor>(null);
			Assert.IsNotNull(pocoInstance);
		}

		[TestMethod]
		public void ConstructPoco()
		{
			var pocoInstance = _mapper.CreateInstance<PocoA, PocoB>(null);
			Assert.IsNotNull(pocoInstance);
		}

		[TestMethod]
		public void ConstructPocoWithCustomCtor()
		{
			var pocoInstance = _mapper.CreateInstance<PocoB, PocoA>(null);
			Assert.IsNotNull(pocoInstance);
			Assert.AreEqual(pocoInstance.Property, "Test");
		}

		[TestMethod]
		public void ConstructPocoWithOverriddenCtor()
		{
			var source = new PocoA("Testing");
			var mapped = _mapper.CreateInstance<PocoA, PocoC>(source);
			Assert.IsNotNull(mapped);
			Assert.AreEqual(mapped.Property, "Testing");
		}

		[TestMethod]
		public void ConstructPocoEnumeration()
		{
			var pocoInstances = _mapper
				.CreateInstances<PocoA, PocoB>(new PocoA[] { null, null, null })
				.ToArray();
			Assert.IsNotNull(pocoInstances);
			Assert.AreEqual(pocoInstances.Length, 3);
			foreach (var pocoInstance in pocoInstances)
				Assert.IsNotNull(pocoInstance);
		}

		[TestMethod]
		public void ConstructPocoEnumerationWithCustomCtor()
		{
			var pocoInstances = _mapper
				.CreateInstances<PocoB, PocoA>(new PocoB[] { null, null, null })
				.ToArray();
			Assert.IsNotNull(pocoInstances);
			Assert.AreEqual(pocoInstances.Length, 3);
			foreach (var pocoInstance in pocoInstances)
			{
				Assert.IsNotNull(pocoInstance);
				Assert.AreEqual(pocoInstance.Property, "Test");
			}
		}

		[TestMethod]
		public void ConstructPocoEnumerationWithOverriddenCtor()
		{
			var source = new[] {
				new PocoA("Testing 1"),
				new PocoA("Testing 2"),
				new PocoA("Testing 3")
				};
			var mapped = _mapper.CreateInstances<PocoA, PocoC>(source).ToArray();
			Assert.IsNotNull(mapped);
			Assert.AreEqual(mapped.Length, 3);
			for (var i = 0; i < 3; i++)
			{
				Assert.IsNotNull(mapped[i]);
				Assert.AreEqual(mapped[i].Property, $"Testing {i+1}");
			}
		}

		[TestMethod]
		public void NoFromTypeParameterConstructPocoWithPrivateCtor()
		{
			var pocoInstance = _mapper.CreateInstance<PocoWithPrivateCtor>(new PocoA(""));
			Assert.IsNotNull(pocoInstance);
		}

		[TestMethod]
		public void NoFromTypeParameterConstructPoco()
		{
			var pocoInstance = _mapper.CreateInstance<PocoB>(new PocoA(""));
			Assert.IsNotNull(pocoInstance);
		}

		[TestMethod]
		public void NoFromTypeParameterConstructPocoWithCustomCtor()
		{
			var pocoInstance = _mapper.CreateInstance<PocoA>(new PocoB());
			Assert.IsNotNull(pocoInstance);
			Assert.AreEqual(pocoInstance.Property, "Test");
		}

		[TestMethod]
		public void NoFromTypeParameterConstructPocoWithOverriddenCtor()
		{
			var source = new PocoA("Testing");
			var mapped = _mapper.CreateInstance<PocoC>(source);
			Assert.IsNotNull(mapped);
			Assert.AreEqual(mapped.Property, "Testing");
		}

		[TestMethod]
		public void NoFromTypeParameterConstructPocoEnumeration()
		{
			var pocoInstances = _mapper
				.CreateInstances<PocoB>(new PocoA[] { null, null, null })
				.ToArray();
			Assert.IsNotNull(pocoInstances);
			Assert.AreEqual(pocoInstances.Length, 3);
			foreach (var pocoInstance in pocoInstances)
				Assert.IsNotNull(pocoInstance);
		}

		[TestMethod]
		public void NoFromTypeParameterConstructPocoEnumerationWithCustomCtor()
		{
			var pocoInstances = _mapper
				.CreateInstances<PocoA>(new PocoB[] { null, null, null })
				.ToArray();
			Assert.IsNotNull(pocoInstances);
			Assert.AreEqual(pocoInstances.Length, 3);
			foreach (var pocoInstance in pocoInstances)
			{
				Assert.IsNotNull(pocoInstance);
				Assert.AreEqual(pocoInstance.Property, "Test");
			}
		}

		[TestMethod]
		public void NoFromTypeParameterConstructPocoEnumerationWithOverriddenCtor()
		{
			var source = new[] {
				new PocoA("Testing 1"),
				new PocoA("Testing 2"),
				new PocoA("Testing 3")
				};
			var mapped = _mapper.CreateInstances<PocoC>(source).ToArray();
			Assert.IsNotNull(mapped);
			Assert.AreEqual(mapped.Length, 3);
			for (var i = 0; i < 3; i++)
			{
				Assert.IsNotNull(mapped[i]);
				Assert.AreEqual(mapped[i].Property, $"Testing {i + 1}");
			}
		}

		private class PocoA
		{
			public string Property { get; }

			public PocoA(string parameter)
			{
				Property = parameter;
			}
		}

		private class PocoB
		{
		}

		private class PocoC
		{
			public string Property { get; }

			public PocoC(string parameter)
			{
				Property = parameter;
			}
		}

		public class PocoWithPrivateCtor
		{
			private PocoWithPrivateCtor()
			{
			}
		}

		private class AToCOverride : IMappingOverride<PocoA, PocoC>
		{
			public void OverrideMapping(MapSettings<PocoA, PocoC> mapping)
			{
				mapping.UseCtor((serviceProvider, pocoA) => new PocoC(pocoA.Property));
			}
		}
	}
}
