using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Demos
{
	class Program
	{
		static void Main(string[] args)
		{
			//ValueTuples();
			//Delegates();
		}

		static void ValueTuples()
		{
			var t = (1, "test", "foo", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
			Console.WriteLine(t.GetType().GetGenericArguments().Length);
			Console.WriteLine(((ITuple)t).Length);
		}

		static void Delegates()
		{
			var mi = typeof(Program).GetMethod(nameof(DelegatesHelper));
			var reflection = MeasureHelper(() => mi.Invoke(null, new[] { "NET DD" }));

			var func = DelegateBuilder.BuildMethodDelegate(mi);
			var @delegate = MeasureHelper(() => func(new[] { "NET DD" }));

			Console.WriteLine($"Reflection: {reflection}");
			Console.WriteLine($"Delegate: {@delegate}");
			Console.WriteLine(reflection.TotalMilliseconds / @delegate.TotalMilliseconds);
		}
		public static void DelegatesHelper(string s)
		{
			Console.WriteLine($"Hello {s}!");
		}
		static TimeSpan MeasureHelper(Action action)
		{
			var sw = Stopwatch.StartNew();
			for (var i = 0; i < 10000; i++)
			{
				action();
			}
			return sw.Elapsed;
		}
	}

	static class TupleHelper
	{
		const int SingleTupleMaxSize = 7;

		public static Type[] GetTupleTypes(Type tuple)
		{
			return GetTupleTypesImpl(tuple).ToArray();
		}

		static IEnumerable<Type> GetTupleTypesImpl(Type tuple)
		{
			var genericArguments = tuple.GetGenericArguments();
			if (genericArguments.Length > SingleTupleMaxSize)
			{
				return genericArguments.Take(SingleTupleMaxSize).Concat(GetTupleTypes(genericArguments[SingleTupleMaxSize]));
			}
			else
			{
				return genericArguments;
			}
		}

		public static (Type, object) GetItemValueReflection(int index, object tuple)
		{
			var type = tuple.GetType();
			if (index >= 7)
			{
				return GetItemValueReflection(index - 7, type.GetField("Rest").GetValue(tuple));
			}
			else
			{
				var genericArguments = type.GetGenericArguments();
				return (genericArguments[index], type.GetField($"Item{index + 1}").GetValue(tuple));
			}
		}
	}

	class DelegateBuilder
	{
		public static Func<object[], IEnumerator> BuildMethodDelegate(MethodInfo method)
		{
			var methodParameters = method.GetParameters();
			var lambdaParameter = Expression.Parameter(typeof(object[]));
			var arguments = methodParameters.Select((e, i) => Expression.Convert(Expression.ArrayIndex(lambdaParameter, Expression.Constant(i)), e.ParameterType)).ToArray();
			var call = Expression.Call(null, method, arguments);
			if (method.ReturnType == typeof(void))
			{
				var lambda = Expression.Lambda<Action<object[]>>(call, lambdaParameter);
				var compiled = lambda.Compile();
				return ps => { compiled(ps); return null; };
			}
			else
			{
				var lambda = Expression.Lambda<Func<object[], IEnumerator>>(call, lambdaParameter);
				return lambda.Compile();
			}
		}
	}
}
