﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace REKT
{
    public interface IResourceDictionary<T> where T : class, IDisposable
    {
        T this[string key] { get; }
        int Count { get; }
        bool ContainsKey(string key);
        T GetValue(string key);
    }

    public sealed class ResourceDictionary<T> : IStatefulDisposable, IResourceDictionary<T> where T : class, IDisposable
    {
        /////////////////////////////////////////////////////////////////////
        // PROPERTIES/VARIABLES
        /////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Has this been disposed?
        /// </summary>
        public bool IsDisposed
        {
            get { return disposed; }
        }

        public int Count => values.Count;

        public T this[string key]
        {
            get => GetValue(key);
        }

        private volatile bool disposed = false;
        private readonly Disposables disposables = new Disposables(); //just for convenience
        private readonly Dictionary<string, T> values = new Dictionary<string, T>();
        private Func<string, T> factoryFunc;

        /////////////////////////////////////////////////////////////////////
        // LIFE CYCLE
        /////////////////////////////////////////////////////////////////////

        public ResourceDictionary(Func<string, T> factoryFunc)
        {
            this.factoryFunc = factoryFunc ?? throw new ArgumentNullException("factoryFunc");
        }

        public void Dispose()
        {
            if (!disposed)
            {
                lock (values)
                {
                    if (disposed)
                        return;
                    disposed = true;
                    factoryFunc = null;
                    values.Clear();
                    disposables.Dispose();
                }
            }
        }

        /////////////////////////////////////////////////////////////////////
        // DICTIONARY
        /////////////////////////////////////////////////////////////////////

        public bool ContainsKey(string key)
        {
            lock (values)
            {
                this.DisposeCheck();
                return values.ContainsKey(key);
            }
        }

        public T GetValue(string key)
        {
            T value = null;
            lock (values)
            {
                if (!values.TryGetValue(key, out value))
                {
                    values.Add(key, value = factoryFunc(key));
                    if (value != null)
                        disposables.Add(value);
                }
            }
            return value;
        }
    }
}
