﻿using MJennings.Extensions;
using MJennings.Logging;
using System;
using System.Globalization;
using System.Runtime.Caching;

namespace MJennings.Caching
{
    public class MemoryCache : BaseCache, IDisposable
    {
        #region Private Fields

        private const string _cacheName = "MJennings.Caching.MemoryCache";
        private System.Runtime.Caching.MemoryCache _cache;

        #endregion

        #region Constructors

        public MemoryCache()
        {
            Init();
        }

        #endregion

        #region Public Methods

        public override bool ContainsItem(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(key);
            }

            return CaseSensitive ? _cache.Contains(key) : _cache.Contains(key.ToUpperInvariant());
        }

        public override object GetItem(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(key);
            }

            var value = CaseSensitive ? _cache.Get(key) : _cache.Get(key.ToUpperInvariant());

            if (Logger != null)
            {
                Logger.Log(new LogEntry
                {
                    Severity = LogLevel.Trace,
                    Source = "MJennings.Caching.MemoryCache.GetItem",
                    Message = string.Format(CultureInfo.InvariantCulture, "Cache item {0}: {1}", value == null ? "miss" : "hit", key)
                });
            }

            return value;
        }

        public override T GetItem<T>(string key)
        {
            return GetItem(key).ConvertTo<T>();
        }

        public override void RemoveAllItems()
        {
            Init();
        }

        public override void RemoveItem(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(key);
            }

            if (CaseSensitive)
            {
                _cache.Remove(key);
            }
            else
            {
                _cache.Remove(key.ToUpperInvariant());
            }
        }

        public override void SetItem(string key, object value)
        {
            SetItem(key, value, DefaultExpiration);
        }

        public override void SetItem(string key, object value, TimeSpan expiration)
        {
            SetItem(key, value, expiration, null);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "MJennings.Logging.LogEntry.set_Message(System.String)")]
        public override void SetItem(string key, object value, TimeSpan expiration, EventHandler<CacheItemExpiredEventArgs> expirationCallback)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(key);
            }

            var policy = new CacheItemPolicy
            {
                AbsoluteExpiration = DateTimeOffset.UtcNow.Add(expiration),
                RemovedCallback = CacheEntryRemovedCallback
            };

            if (expirationCallback != null)
            {
                policy.RemovedCallback +=
                    delegate (CacheEntryRemovedArguments arguments)
                    {
                        var e = new CacheItemExpiredEventArgs { ItemKey = arguments.CacheItem.Key };
                        expirationCallback(this, e);
                    };
            }

            if (CaseSensitive)
            {
                _cache.Set(key, value, policy);
            }
            else
            {
                _cache.Set(key.ToUpperInvariant(), value, policy);
            }

            if (Logger != null)
            {
                Logger.Log(new LogEntry
                {
                    Severity = LogLevel.Debug,
                    Source = "MJennings.Caching.MemoryCache.SetItem",
                    Message = string.Concat("Cache item set: ", key)
                });
            }
        }

        #endregion

        #region Private Methods

        private void Init()
        {
            if (_cache != null)
            {
                _cache.Dispose();
            }

            _cache = new System.Runtime.Caching.MemoryCache(_cacheName);
        }

        private void CacheEntryRemovedCallback(CacheEntryRemovedArguments arguments)
        {
            if (arguments.RemovedReason != CacheEntryRemovedReason.Removed)
            {
                OnItemExpired(new CacheItemExpiredEventArgs
                {
                    ItemKey = arguments.CacheItem.Key
                });
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_cache != null)
                {
                    _cache.Dispose();
                }
            }
        }

        #endregion
    }
}
