using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;

namespace NzbDrone.Common.Cache
{
    public class CachedDictionary<TValue> : ICachedDictionary<TValue>
    {
        private readonly Func<IDictionary<string, TValue>> _fetchFunc;
        private readonly TimeSpan? _ttl;

        private DateTime _lastRefreshed = DateTime.MinValue;
        private ConcurrentDictionary<string, TValue> _items = new ConcurrentDictionary<string, TValue>();

        public CachedDictionary(Func<IDictionary<string, TValue>> fetchFunc = null, TimeSpan? ttl = null)
        {
            _fetchFunc = fetchFunc;
            _ttl = ttl;
        }

        public bool IsExpired(TimeSpan ttl)
        {
            return _lastRefreshed.Add(ttl) < DateTime.UtcNow;
        }

        public void RefreshIfExpired()
        {
            if (_ttl.HasValue && _fetchFunc != null)
            {
                RefreshIfExpired(_ttl.Value);
            }
        }

        public void RefreshIfExpired(TimeSpan ttl)
        {
            if (IsExpired(ttl))
            {
                Refresh();
            }
        }

        public void Refresh()
        {
            if (_fetchFunc == null)
            {
                throw new InvalidOperationException("Cannot update cache without data source.");
            }

            Update(_fetchFunc());
            ExtendTTL();
        }

        public void Update(IDictionary<string, TValue> items)
        {
            _items = new ConcurrentDictionary<string, TValue>(items);
            ExtendTTL();
        }

        public void ExtendTTL()
        {
            _lastRefreshed = DateTime.UtcNow;
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public ICollection<TValue> Values
        {
            get
            {
                RefreshIfExpired();
                return _items.Values;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int Count
        {
            get
            {
                RefreshIfExpired();
                return _items.Count;
            }
        }

        public TValue Get(string key)
        {
            RefreshIfExpired();

            if (!_items.TryGetValue(key, out var result))
            {
                throw new KeyNotFoundException(string.Format("Item {0} not found in cache.", key));
            }

            return result;
        }

        public TValue Find(string key)
        {
            RefreshIfExpired();

            _items.TryGetValue(key, out var result);

            return result;
        }

        public void Clear()
        {
            _items.Clear();
            _lastRefreshed = DateTime.MinValue;
        }

        public void ClearExpired()
        {
            if (!_ttl.HasValue)
            {
                throw new InvalidOperationException("Checking expiry without ttl not possible.");
            }

            if (IsExpired(_ttl.Value))
            {
                Clear();
            }
        }

        public void Remove(string key)
        {
            _items.TryRemove(key, out _);
        }
    }
}
