using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace BS.Shared.Core
{
	public static class DisplayEnum
	{
		public static readonly DisplayEnum<Auszahlungsintervall> Auszahlungsintervall = new DisplayEnum<Auszahlungsintervall>();
		public static readonly DisplayEnum<Zahlungstyp>          Zahlungstyp          = new DisplayEnum<Zahlungstyp>();

		public static IDisplayEnum GetDisplayEnum(Type type)
		{
			var fields = typeof(DisplayEnum).GetFields(BindingFlags.Public | BindingFlags.Static);

			if (type.IsGenericType && !type.IsGenericTypeDefinition)
			{
				// handle Nullable<>
				var genericTypeDef = type.GetGenericTypeDefinition();
				if (genericTypeDef == typeof(Nullable<>))
				{
					type = type.GetGenericArguments()[0];
				}
			}

			foreach (var field in fields)
			{
				if (field.FieldType == type)
				{
					return (IDisplayEnum)field.GetValue(null);
				}

				IList genericArguments = field.FieldType.GetGenericArguments();
				if (genericArguments.Contains(type))
				{
					return (IDisplayEnum)field.GetValue(null);
				}
			}
			return null;
		}
	}

	public interface IDisplayEnum
	{
		IList<IEnumValue> Values { get; }
		IList<IEnumValue> SortedValues { get; }

		IEnumValue this[Object enumObj] { get; }
	}

	public class DisplayEnum<T> : IDisplayEnum
		where T : struct, IComparable, IFormattable, IConvertible
	{
		private readonly Object lockObj = new Object();

		private readonly ISet<T> excludedValues;
		private IList<EnumValue<T>> enumValues;

		public DisplayEnum(params T[] excludedValues)
		{
			this.excludedValues = new HashSet<T>(excludedValues);
		}

		public IEnumerable<EnumValue<T>> Values
		{
			get
			{
				if (enumValues == null)
				{
					Initialize();
				}
				return enumValues;
			}
		}

		public IEnumerable<EnumValue<T>> SortedValues
		{
			get
			{
				if (enumValues == null)
				{
					Initialize();
				}
				var sortedValues = new List<EnumValue<T>>(enumValues);
				sortedValues.Sort();

				return sortedValues;
			}
		}

		public IList<EnumValue<T>> ExcludedValues { get; private set; }

		private void Initialize()
		{
			lock (lockObj)
			{
				if (enumValues == null)
				{
					enumValues = new List<EnumValue<T>>();
					ExcludedValues = new List<EnumValue<T>>();

					foreach (T enumValue in Enum.GetValues(typeof(T)))
					{
						var value = CreateEnumValue(enumValue);
						if (!excludedValues.Contains(enumValue))
						{
							enumValues.Add(value);
						}
						else
						{
							ExcludedValues.Add(value);
						}
					}
				}
			}
		}

		protected virtual EnumValue<T> CreateEnumValue(T enumValue)
		{
			var value = new EnumValue<T>(enumValue, EnumTranslations.AllTranslations[(Enum)(Object)enumValue]);
			return value;
		}

		public EnumValue<T> this[T enumObj]
		{
			get
			{
				var result = Values.FirstOrDefault(value => value.Enum.Equals(enumObj));

				return result ?? ExcludedValues.FirstOrDefault(value => value.Enum.Equals(enumObj));
			}
		}

		public EnumValue<T> this[T? enumObj]
		{
			get
			{
				return enumObj != null ? this[enumObj.Value] : null;
			}
		}

		#region IDisplayEnum Implementation

		IList<IEnumValue> IDisplayEnum.Values
		{
			get { return this.Values.Cast<IEnumValue>().ToList(); }
		}

		IList<IEnumValue> IDisplayEnum.SortedValues
		{
			get { return SortedValues.Cast<IEnumValue>().ToList(); }
		}

		IEnumValue IDisplayEnum.this[Object enumObj]
		{
			get
			{
				if (enumObj is T)
				{
					return this[(T)enumObj];
				}
				return null;
			}
		}

		#endregion
	}
}