//---------------------------------------------------------------------
// <copyright file="ObjectItemLoadingSessionData.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;

namespace System.Data.Metadata.Edm
{
    internal sealed class ObjectItemLoadingSessionData
    {
        private Func<Assembly, ObjectItemLoadingSessionData, ObjectItemAssemblyLoader> _loaderFactory;

        // all the types that we encountered while loading - this may contain types from various assemblies
        private readonly Dictionary<string, EdmType> _typesInLoading;
        
        // 

        private bool _conventionBasedRelationshipsAreLoaded = false;

        private LoadMessageLogger _loadMessageLogger;

        // list of errors encountered during loading
        private readonly List<EdmItemError> _errors;

        // keep the list of new assemblies that got loaded in this load assembly call. The reason why we need to keep a seperate
        // list of assemblies is that we keep track of errors, and if there are no errors, only then do we add the list of assemblies
        // to the global cache. Hence global cache is never polluted with invalid assemblies
        private readonly Dictionary<Assembly, MutableAssemblyCacheEntry> _listOfAssembliesLoaded = new Dictionary<Assembly, MutableAssemblyCacheEntry>();

        // List of known assemblies - this list is initially passed by the caller and we keep adding to it, as and when we load
        // an assembly
        private readonly KnownAssembliesSet _knownAssemblies;
        private readonly LockedAssemblyCache _lockedAssemblyCache;
        private readonly HashSet<ObjectItemAssemblyLoader> _loadersThatNeedLevel1PostSessionProcessing;
        private readonly HashSet<ObjectItemAssemblyLoader> _loadersThatNeedLevel2PostSessionProcessing;

        private readonly EdmItemCollection _edmItemCollection;
        private Dictionary<string, KeyValuePair<EdmType, int>> _conventionCSpaceTypeNames;
        private Dictionary<EdmType, EdmType> _cspaceToOspace;
        private object _originalLoaderCookie;
        internal Dictionary<string, EdmType> TypesInLoading { get { return _typesInLoading; } }
        internal Dictionary<Assembly, MutableAssemblyCacheEntry> AssembliesLoaded { get { return _listOfAssembliesLoaded; } }
        internal List<EdmItemError> EdmItemErrors { get { return _errors; } }
        internal KnownAssembliesSet KnownAssemblies { get { return _knownAssemblies; } }
        internal LockedAssemblyCache LockedAssemblyCache { get { return _lockedAssemblyCache; } }
        internal EdmItemCollection EdmItemCollection { get { return _edmItemCollection; } }
        internal Dictionary<EdmType, EdmType> CspaceToOspace { get { return _cspaceToOspace; } }
        internal bool ConventionBasedRelationshipsAreLoaded 
        { 
            get { return _conventionBasedRelationshipsAreLoaded;  }
            set { _conventionBasedRelationshipsAreLoaded = value; }
        }

        internal LoadMessageLogger LoadMessageLogger
        { 
            get 
            { 
                return this._loadMessageLogger; 
            }
        }

        // dictionary of types by name (not including namespace), we also track duplicate names
        // so if one of those types is used we can log an error
        internal Dictionary<string, KeyValuePair<EdmType, int>> ConventionCSpaceTypeNames 
        { 
            get 
            {
                if (_edmItemCollection != null && _conventionCSpaceTypeNames == null)
                {
                    _conventionCSpaceTypeNames = new Dictionary<string, KeyValuePair<EdmType, int>>();

                    // create the map and cache it
                    foreach (var edmType in _edmItemCollection.GetItems<EdmType>())
                    {
                        if ((edmType is StructuralType && edmType.BuiltInTypeKind != BuiltInTypeKind.AssociationType) || Helper.IsEnumType(edmType))
                        {

                            KeyValuePair<EdmType, int> pair;
                            if (_conventionCSpaceTypeNames.TryGetValue(edmType.Name, out pair))
                            {
                                _conventionCSpaceTypeNames[edmType.Name] = new KeyValuePair<EdmType, int>(pair.Key, pair.Value + 1);
                            }
                            else
                            {
                                pair = new KeyValuePair<EdmType, int>(edmType, 1);
                                _conventionCSpaceTypeNames.Add(edmType.Name, pair);
                            }
                        }
                    }
                }
                return _conventionCSpaceTypeNames;
            } 
        }

        internal Func<Assembly, ObjectItemLoadingSessionData, ObjectItemAssemblyLoader> ObjectItemAssemblyLoaderFactory
        {
            get { return _loaderFactory; }
            set
            {
                if (_loaderFactory != value)
                {
                    Debug.Assert(_loaderFactory == null || _typesInLoading.Count == 0, "Only reset the factory after types have not been loaded or load from the cache");
                    _loaderFactory = value;
                }
            }
        }

        internal object LoaderCookie
        {
            get
            {
                // be sure we get the same factory/cookie as we had before... if we had one
                if (_originalLoaderCookie != null)
                {
                    Debug.Assert(_loaderFactory == null ||
                                 (object)_loaderFactory == _originalLoaderCookie, "The loader factory should determine the next loader, so we should always have the same loader factory");
                    return _originalLoaderCookie;
                }

                return _loaderFactory;
            }
        }
        internal ObjectItemLoadingSessionData(KnownAssembliesSet knownAssemblies, LockedAssemblyCache lockedAssemblyCache, EdmItemCollection edmItemCollection, Action<String> logLoadMessage, object loaderCookie)
        {
            Debug.Assert(loaderCookie == null || loaderCookie is Func<Assembly, ObjectItemLoadingSessionData, ObjectItemAssemblyLoader>, "This is a bad loader cookie");
            
            _typesInLoading = new Dictionary<string, EdmType>(StringComparer.Ordinal);
            _errors = new List<EdmItemError>();
            _knownAssemblies = knownAssemblies;
            _lockedAssemblyCache = lockedAssemblyCache;
            _loadersThatNeedLevel1PostSessionProcessing = new HashSet<ObjectItemAssemblyLoader>();
            _loadersThatNeedLevel2PostSessionProcessing = new HashSet<ObjectItemAssemblyLoader>();
            _edmItemCollection = edmItemCollection;
            _loadMessageLogger = new LoadMessageLogger(logLoadMessage);
            _cspaceToOspace = new Dictionary<EdmType, EdmType>();
            _loaderFactory = (Func<Assembly, ObjectItemLoadingSessionData, ObjectItemAssemblyLoader>)loaderCookie;
            _originalLoaderCookie = loaderCookie;
            if (_loaderFactory == ObjectItemConventionAssemblyLoader.Create && _edmItemCollection != null)
            {
                foreach (KnownAssemblyEntry entry in _knownAssemblies.GetEntries(_loaderFactory, edmItemCollection))
                {
                    foreach (EdmType type in entry.CacheEntry.TypesInAssembly.OfType<EdmType>())
                    {
                        if (Helper.IsEntityType(type))
                        {
                            ClrEntityType entityType = (ClrEntityType)type;
                            _cspaceToOspace.Add(_edmItemCollection.GetItem<StructuralType>(entityType.CSpaceTypeName), entityType);
                        }
                        else if (Helper.IsComplexType(type))
                        {
                            ClrComplexType complexType = (ClrComplexType)type;
                            _cspaceToOspace.Add(_edmItemCollection.GetItem<StructuralType>(complexType.CSpaceTypeName), complexType);
                        }
                        else if(Helper.IsEnumType(type))
                        {
                            ClrEnumType enumType = (ClrEnumType)type;
                            _cspaceToOspace.Add(_edmItemCollection.GetItem<EnumType>(enumType.CSpaceTypeName), enumType);
                        }
                        else
                        {
                            Debug.Assert(Helper.IsAssociationType(type));
                            _cspaceToOspace.Add(_edmItemCollection.GetItem<StructuralType>(type.FullName), type);
                        }
                    }
                }
            }

        }

        internal void RegisterForLevel1PostSessionProcessing(ObjectItemAssemblyLoader loader)
        {
            _loadersThatNeedLevel1PostSessionProcessing.Add(loader);
        }

        internal void RegisterForLevel2PostSessionProcessing(ObjectItemAssemblyLoader loader)
        {
            _loadersThatNeedLevel2PostSessionProcessing.Add(loader);
        }
        
        internal void CompleteSession()
        {
            foreach (ObjectItemAssemblyLoader loader in _loadersThatNeedLevel1PostSessionProcessing)
            {
                loader.OnLevel1SessionProcessing();
            }

            foreach (ObjectItemAssemblyLoader loader in _loadersThatNeedLevel2PostSessionProcessing)
            {
                loader.OnLevel2SessionProcessing();
            }
        }
    }
}
