using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
#if !NETCORE
using System.Runtime.Remoting.Messaging;
#endif
using System.Threading;

namespace ServiceStack.OrmLite
{
    public class OrmLiteContext
    {
        public static readonly OrmLiteContext Instance = new OrmLiteContext();

        /// <summary>
        /// Tell ServiceStack to use ThreadStatic Items Collection for Context Scoped items.
        /// Warning: ThreadStatic Items aren't pinned to the same request in async services which callback on different threads.
        /// </summary>
        public static bool UseThreadStatic = false;

        [ThreadStatic]
        public static IDictionary ContextItems;

#if NETCORE
        AsyncLocal<IDictionary> localContextItems = new();
#endif

        /// <summary>
        /// Gets a list of items for this context. 
        /// </summary>
        public virtual IDictionary Items
        {
            get => GetItems() ?? (CreateItems());
            set => CreateItems(value);
        }

        private const string _key = "__OrmLite.Items";

        private IDictionary GetItems()
        {
#if NETCORE
            if (UseThreadStatic)
                return ContextItems;

            return localContextItems.Value;
#else
            try
            {
                if (UseThreadStatic)
                    return ContextItems;

                return CallContext.LogicalGetData(_key) as IDictionary;
            }
            catch (NotImplementedException)
            {
                //Fixed in Mono master: https://github.com/mono/mono/pull/817
                return CallContext.GetData(_key) as IDictionary;
            }
#endif
        }

        private IDictionary CreateItems(IDictionary items = null)
        {
#if NETCORE
                if (UseThreadStatic)
                {
                    ContextItems = items ??= new Dictionary<object, object>();
                }
                else
                {
                    localContextItems.Value = items ??= new ConcurrentDictionary<object, object>();
                }
#else                
            try
            {
                if (UseThreadStatic)
                {
                    ContextItems = items ??= new Dictionary<object, object>();
                }
                else
                {
                    CallContext.LogicalSetData(_key, items ??= new ConcurrentDictionary<object, object>());
                }
            }
            catch (NotImplementedException)
            {
                //Fixed in Mono master: https://github.com/mono/mono/pull/817
                CallContext.SetData(_key, items ??= new ConcurrentDictionary<object, object>());
            }
#endif
            return items;
        }

        public void ClearItems()
        {
            if (UseThreadStatic)
            {
                ContextItems = new Dictionary<object, object>();
            }
            else
            {
#if NETCORE
                localContextItems.Value = new ConcurrentDictionary<object, object>();                
#else                
                CallContext.FreeNamedDataSlot(_key);
#endif
            }
        }

        public T GetOrCreate<T>(Func<T> createFn)
        {
            if (Items.Contains(typeof(T).Name))
                return (T)Items[typeof(T).Name];

            return (T)(Items[typeof(T).Name] = createFn());
        }

        internal static void SetItem<T>(string key, T value)
        {
            if (Equals(value, default(T)))
            {
                Instance.Items.Remove(key);
            }
            else
            {
                Instance.Items[key] = value;
            }
        }

        public static OrmLiteState CreateNewState()
        {
            var state = new OrmLiteState();
            OrmLiteState = state;
            return state;
        }

        public static OrmLiteState GetOrCreateState()
        {
            return OrmLiteState
                ?? CreateNewState();
        }

        public static OrmLiteState OrmLiteState
        {
            get
            {
                if (Instance.Items.Contains("OrmLiteState"))
                    return Instance.Items["OrmLiteState"] as OrmLiteState;

                return null;
            }
            set => SetItem("OrmLiteState", value);
        }

        //Only used when using OrmLite API's against a native IDbConnection (i.e. not from DbFactory) 
        internal static IDbTransaction TSTransaction
        {
            get
            {
                var state = OrmLiteState;
                return state?.TSTransaction;
            }
            set => GetOrCreateState().TSTransaction = value;
        }
    }

    public class OrmLiteState
    {
        private static long Counter;
        public long Id;

        public OrmLiteState()
        {
            Id = Interlocked.Increment(ref Counter);
        }

        public IDbTransaction TSTransaction;
        public IOrmLiteResultsFilter ResultsFilter;

        public override string ToString()
        {
            return $"State Id: {Id}";
        }
    }
}
