using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Tests.OrmBattle.Helper
{
	public class GenericEqualityComparer<T> : IComparable<T>, IEqualityComparer<T>
	{
		private readonly List<PropertyInfo> _propertyInfos = new ();

		public GenericEqualityComparer(params Expression<Func<T, object>>[] property)
		{
			var props = property.Select(pe => ExpressionUtils.ExtractMember(pe).Member).OfType<PropertyInfo>();

			_propertyInfos.AddRange(props);
		}

		#region IEqualityComparer Members

		public bool Equals(T? x, T? y)
		{
			foreach (var propertyInfo in _propertyInfos)
			{
				//get the current value of the comparison property of x and of y
				var xValue = propertyInfo.GetValue(x, null);
				var yValue = propertyInfo.GetValue(y, null);

				//if the xValue is null then we consider them equal if and only if yValue is null
				if (xValue == null)
				{
					return yValue == null;
				}

				if (!xValue.Equals(yValue))
					return false;
			}
			return true;
		}

		public int GetHashCode(T obj)
		{
			var values = new List<object?>();
			foreach (var propertyInfo in _propertyInfos)
			{
				//get the current value of the comparison property of x and of y
				var xValue = propertyInfo.GetValue(obj, null);
				values.Add(xValue);
			}
			return HashCodeBuilder.Hash(values.ToArray());
		}

		#endregion

		public int CompareTo(T? other)
		{
			var x = this;
			var y = other;
			foreach (var propertyInfo in _propertyInfos)
			{
				//get the current value of the comparison property of x and of y
				var xValue = propertyInfo.GetValue(x, null);
				var yValue = propertyInfo.GetValue(y, null);

				//if the xValue is null then we consider them equal if and only if yValue is null
				if (xValue == null)
				{
					if (yValue != null)
						return -1;
					return 0;
				}

				if (!xValue.Equals(yValue))
					return 1;
			}

			return 0;
		}
	}

	public static class HashCodeBuilder
	{
		public static int Hash(params object?[]? args)
		{
			if (args == null)
			{
				return 0;
			}

			var num = 42;

			unchecked
			{
				foreach (var item in args)
				{
					if (ReferenceEquals(item, null))
					{
					}
					else if (item.GetType().IsArray)
					{
						foreach (var subItem in (IEnumerable) item)
						{
							num = num * 37 + Hash(subItem);
						}
					}
					else
					{
						num = num * 37 + item.GetHashCode();
					}
				}
			}

			return num;
		}
	}
}
