using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace ObjectExtensions
{
	public static class ExtensionProperties
	{
		#region Static Fields
		
		//вспомогательный словарь свойств-расширений
		private static readonly Dictionary<WeakReference, Dictionary<string, object>> _extensionMembers;
		//таймер, убивающий словари-расширения мертвых объектов
		private static readonly Timer _cleaner;

		#endregion

		#region Constructors

		static ExtensionProperties()
		{
			_extensionMembers = new Dictionary<WeakReference, Dictionary<string, object>>();
			
			_cleaner = new Timer((o) =>
			{
				//будем убирать мусор каждые 10 секунд
				foreach (var key in _extensionMembers.Keys.Where(k => !k.IsAlive).ToArray())
				{
					_extensionMembers.Remove(key);
				}
			}, null, 0, 10000);
		}

		#endregion

		#region Static Methods

		/// <summary>
		/// метод создания/получения существующего словаря свойств-расширений объекта
		/// </summary>
		private static Dictionary<string, object> Extensions(this object targetObject, bool createIfNotExist)
		{
			//нет объекта - расширять нечего
			if (targetObject == null)
				return null;
		
			lock (targetObject)
			{
				//получаем ключ из вспомогательного словаря
				var weakKey = _extensionMembers.Keys.FirstOrDefault(w => object.ReferenceEquals(w.Target, targetObject));

				if (weakKey != null)
				{
					//ключ найден, возвращаем словарь
					return _extensionMembers[weakKey];
				}
				else if (createIfNotExist)
				{
					//создаем новый ключ и словарь для объекта
					weakKey = new WeakReference(targetObject, false);					
					var members = new Dictionary<string, object>(StringComparer.Ordinal);
					_extensionMembers[weakKey] = members;
					return members;
				}
			}
			return null;
		}

		#endregion

		#region Extension Methods

		/// <summary>
		/// метод удаления словаря свойств-расширений объекта
		/// </summary>
		public static void ClearExtensions(this object targetObject)
		{
			//нет объекта - удалять нечего
			if (targetObject == null)
				return;

			lock (targetObject)
			{
				//получаем ключ из вспомогательного словаря
				var weakKey = _extensionMembers.Keys.FirstOrDefault(w => object.ReferenceEquals(w.Target, targetObject));

				if (weakKey != null)
				{
					//ключ найден, удаляем словарь
					_extensionMembers.Remove(weakKey);
				}
			}
		}

		/// <summary>
		/// Установить значение свойства-расширения
		/// </summary>
		public static void Extension(this object targetObject, string key, object value)
		{
			if (targetObject == null || String.IsNullOrEmpty(key))
				return;

			//получаем словарь свойств-расширений, либо создаем новый
			var extensions = targetObject.Extensions(true);

			//устанавливаем значение, либо удаляем его, если value == null	
			if (value != null)
			{
				extensions[key] = value;
			}
			else
			{
				lock(targetObject)
				{
					extensions.Remove(key);
				}
			}
		}

		/// <summary>
		/// Получить значение свойства-расширения
		/// </summary>
		public static T Extension<T>(this object targetObject, string key)
		{
			if (targetObject != null && !String.IsNullOrEmpty(key))
			{
				//получаем словарь свойств-расширений, новый не создаем
				var extensions = targetObject.Extensions(false);

				//если есть словарь
				if (extensions != null)
				{
					//если есть свойство нужное в словаре
					if (extensions.ContainsKey(key))
					{
						lock(targetObject)
						{
							if (extensions.ContainsKey(key))
							{
								//и это свойство того типа, который мы ожидаем
								var value = extensions[key];
								if (value is T)
									//возвращаем значение
									return (T)extensions[key];
							}
						}
					}
				}
			}

			return default(T);
		}

		public static object Extension(this object targetObject, string key)
		{
			return targetObject.Extension<object>(key);
		}

		#endregion
	}
}