using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using LeanCloud.Storage.Internal;

namespace LeanCloud.Storage.Internal
{
    internal class ObjectSubclassingController : IObjectSubclassingController
    {
        // Class names starting with _ are documented to be reserved. Use this one
        // here to allow us to 'inherit' certain properties.
        private static readonly string avObjectClassName = "_AVObject";

        private readonly ReaderWriterLockSlim mutex;
        private readonly IDictionary<String, ObjectSubclassInfo> registeredSubclasses;
        private Dictionary<String, Action> registerActions;

        public ObjectSubclassingController()
        {
            mutex = new ReaderWriterLockSlim();
            registeredSubclasses = new Dictionary<String, ObjectSubclassInfo>();
            registerActions = new Dictionary<string, Action>();

            // Register the AVObject subclass, so we get access to the ACL,
            // objectId, and other AVFieldName properties.
            RegisterSubclass(typeof(AVObject));
        }

        public String GetClassName(Type type)
        {
            return type == typeof(AVObject)
              ? avObjectClassName
              : ObjectSubclassInfo.GetClassName(type.GetTypeInfo());
        }

        public Type GetType(String className)
        {
            ObjectSubclassInfo info = null;
            mutex.EnterReadLock();
            registeredSubclasses.TryGetValue(className, out info);
            mutex.ExitReadLock();

            return info != null
              ? info.TypeInfo.AsType()
              : null;
        }

        public bool IsTypeValid(String className, Type type)
        {
            ObjectSubclassInfo subclassInfo = null;

            mutex.EnterReadLock();
            registeredSubclasses.TryGetValue(className, out subclassInfo);
            mutex.ExitReadLock();

            return subclassInfo == null
              ? type == typeof(AVObject)
              : subclassInfo.TypeInfo == type.GetTypeInfo();
        }

        public void RegisterSubclass(Type type)
        {
            TypeInfo typeInfo = type.GetTypeInfo();
            if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(typeInfo))
            {
                throw new ArgumentException("Cannot register a type that is not a subclass of AVObject");
            }

            String className = GetClassName(type);

            try
            {
                // Perform this as a single independent transaction, so we can never get into an
                // intermediate state where we *theoretically* register the wrong class due to a
                // TOCTTOU bug.
                mutex.EnterWriteLock();

                ObjectSubclassInfo previousInfo = null;
                if (registeredSubclasses.TryGetValue(className, out previousInfo))
                {
                    if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo))
                    {
                        // Previous subclass is more specific or equal to the current type, do nothing.
                        return;
                    }
                    else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo))
                    {
                        // Previous subclass is parent of new child, fallthrough and actually register
                        // this class.
                        /* Do nothing */
                    }
                    else
                    {
                        throw new ArgumentException(
                          "Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName +
                          " as the AVObject subclass of " + className + ". Cannot determine the right class " +
                          "to use because neither inherits from the other."
                        );
                    }
                }

                ConstructorInfo constructor = type.FindConstructor();
                if (constructor == null)
                {
                    throw new ArgumentException("Cannot register a type that does not implement the default constructor!");
                }

                registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor);
            }
            finally
            {
                mutex.ExitWriteLock();
            }

            Action toPerform;

            mutex.EnterReadLock();
            registerActions.TryGetValue(className, out toPerform);
            mutex.ExitReadLock();

            if (toPerform != null)
            {
                toPerform();
            }
        }

        public void UnregisterSubclass(Type type)
        {
            mutex.EnterWriteLock();
            registeredSubclasses.Remove(GetClassName(type));
            mutex.ExitWriteLock();
        }

        public void AddRegisterHook(Type t, Action action)
        {
            mutex.EnterWriteLock();
            registerActions.Add(GetClassName(t), action);
            mutex.ExitWriteLock();
        }

        public AVObject Instantiate(String className)
        {
            ObjectSubclassInfo info = null;

            mutex.EnterReadLock();
            registeredSubclasses.TryGetValue(className, out info);
            mutex.ExitReadLock();

            return info != null
              ? info.Instantiate()
              : new AVObject(className);
        }

        public IDictionary<String, String> GetPropertyMappings(String className)
        {
            ObjectSubclassInfo info = null;
            mutex.EnterReadLock();
            registeredSubclasses.TryGetValue(className, out info);
            if (info == null)
            {
                registeredSubclasses.TryGetValue(avObjectClassName, out info);
            }
            mutex.ExitReadLock();

            return info.PropertyMappings;
        }

    }
}
