namespace Kit.Helpers
{
    public interface ILockService<TKey>
    {
        TResult Lock<TResult>(TKey lockKey, Func<TResult> action);
        void Lock(TKey lockKey, Action action);
    }
    public class LockService<TKey> : ILockService<TKey>
    {
        private object _lockObjectLock = new object();
        private IDictionary<TKey, object> _lockObjects = new Dictionary<TKey, object>();

        /// <summary> Блокировка операции по ключу </summary>
        /// <param name="lockKey"> Ключ для блокировки </param>
        /// <param name="action"> Выполняемая операция </param>
        public TResult Lock<TResult>(TKey lockKey, Func<TResult> action)
        {
            try
            {
                lock (GetLockObject(lockKey))
                {
                    return action();
                }
            }
            finally
            {
                RemoveLockObject(lockKey);
            }
        }

        /// <summary> Блокировка операции по ключу </summary>
        /// <param name="lockKey"> Ключ для блокировки </param>
        /// <param name="action"> Выполняемая операция </param>
        public void Lock(TKey lockKey, Action action)
        {
            try
            {
                lock (GetLockObject(lockKey))
                {
                    action();
                }
            }
            finally
            {
                RemoveLockObject(lockKey);
            }
        }

        /// <summary> Получение объекта для блокировки по ключу </summary>
        /// <param name="lockKey"> Ключ для блокировки </param>
        /// <returns> Объект блокировки, соответствующий ключу </returns>
        private object GetLockObject(TKey lockKey)
        {
            lock (_lockObjectLock)
            {
                if (_lockObjects.ContainsKey(lockKey) == false)
                {
                    _lockObjects.Add(lockKey, new object());
                }

                return _lockObjects[lockKey];
            }
        }

        /// <summary> Удаление объекта блокировки, соответствующего ключу </summary>
        /// <param name="lockKey"> Ключ для блокировки </param>
        private void RemoveLockObject(TKey lockKey)
        {
            /// дожидаемся освобождения объекта
            lock (this.GetLockObject(lockKey)) { }

            /// удаляем объект блокировки из очереди
            lock (_lockObjectLock)
            {
                _lockObjects.Remove(lockKey);
            }
        }
    }
}
