//---------------------------------------------------------------------
// <copyright file="EntityStoreSchemaGenerator.LoadMethodSessionState.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Entity.Design.Common;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Data.Entity.Design.SsdlGenerator;
using System.Diagnostics;

namespace System.Data.Entity.Design
{
    public sealed partial class EntityStoreSchemaGenerator
    {
        // responsible for holding all the 
        // state for a single execution of the Load
        // method
        private class LoadMethodSessionState
        {
            public List<AssociationType> AssociationTypes = new List<AssociationType>();
            public UniqueIdentifierService UsedTypeNames = new UniqueIdentifierService(false,s=>s.Replace(".", "_"));
            public IEnumerable<EntityStoreSchemaFilterEntry> Filters;
            public Dictionary<EntityType, EntitySet> EntityTypeToSet = new Dictionary<EntityType, EntitySet>();
            public Dictionary<RelationshipEndMember, EntityType> RelationshipEndTypeLookup = new Dictionary<RelationshipEndMember, EntityType>();
            public Dictionary<string, EntityContainer> EntityContainerLookup = new Dictionary<string, EntityContainer>();
            public HashSet<EntityType> ReadOnlyEntities = new HashSet<EntityType>();
            public HashSet<EdmType> InvalidTypes = new HashSet<EdmType>();
            public MetadataItemSerializer.ErrorsLookup ItemToErrorsMap = new MetadataItemSerializer.ErrorsLookup();
            public StoreItemCollection ItemCollection;
            public List<EdmFunction> Functions = new List<EdmFunction>();

            private Dictionary<DbObjectKey, EntityType> _entityLookup = new Dictionary<DbObjectKey, EntityType>();
            private Dictionary<EntityType, DbObjectKey> _reverseEntityLookup = new Dictionary<EntityType, DbObjectKey>();
            private HashSet<DbObjectKey> _missingEntities = new HashSet<DbObjectKey>();
            private HashSet<DbObjectKey> _tablesWithoutKeys = new HashSet<DbObjectKey>();
            private Dictionary<DbObjectKey, RowType> _tvfReturnTypeLookup = new Dictionary<DbObjectKey, RowType>();
            private string _storeNamespace;

            private readonly Version _targetEntityFrameworkVersion;

            public LoadMethodSessionState(Version targetEntityFrameworkVersion)
            {
                _targetEntityFrameworkVersion = targetEntityFrameworkVersion;
            }

            public IEnumerable<EdmSchemaError> Errors
            {
                get
                {
                    foreach (List<EdmSchemaError> errors in ItemToErrorsMap.Values)
                    {
                        foreach (EdmSchemaError error in errors)
                        {
                            yield return error;
                        }
                    }
                }
            }

            public void AddTableWithoutKey(DbObjectKey tableKey)
            {
                _tablesWithoutKeys.Add(tableKey);
            }

            public bool ContainsTableWithoutKey(DbObjectKey tableKey)
            {
                return _tablesWithoutKeys.Contains(tableKey);
            }

            public EntitySet GetEntitySet(RelationshipEndMember end)
            {
                EntityType type = RelationshipEndTypeLookup[end];
                return EntityTypeToSet[type];
            }

            public void AddEntity(DbObjectKey key, EntityType type)
            {
                _entityLookup.Add(key, type);
                _reverseEntityLookup.Add(type, key);
            }

            public IEnumerable<EntityType> GetAllEntities()
            {
                return _entityLookup.Values;
            }

            public bool TryGetEntity(DbObjectKey key, out EntityType type)
            {
                if (_entityLookup.TryGetValue(key, out type))
                {
                    return true;
                }

                //Ignore relationships which refer to table without keys
                if (ContainsTableWithoutKey(key))
                {
                    return false;
                }

                if (!_missingEntities.Contains(key))
                {
                    AddErrorsForType(null,
                        new EdmSchemaError(
                        Strings.TableReferencedByAssociationWasNotFound(key),
                        (int)ModelBuilderErrorCode.MissingEntity,
                        EdmSchemaErrorSeverity.Error));
                    _missingEntities.Add(key);
                }
                return false;
            }

            public DbObjectKey GetKey(EntityType type)
            {
                DbObjectKey key;
                bool value = _reverseEntityLookup.TryGetValue(type, out key);
                Debug.Assert(value, "How did we possibly get an EntityType without a TableKey");
                return key;
            }

            public void AddTvfReturnType(DbObjectKey key, RowType type)
            {
                _tvfReturnTypeLookup.Add(key, type);
            }

            public bool TryGetTvfReturnType(DbObjectKey key, out RowType type)
            {
                return _tvfReturnTypeLookup.TryGetValue(key, out type);
            }

            /// <summary>
            /// Attempts to get the primitive store type for the given type name. This method takes the target .NET Framework
            /// into account and only returns primitive types that are supported by the Framework that is being targeted.
            /// If a type is recognized but excluded because it is not supported by the target Framework then the excludedForTarget
            /// flag is set to true.
            /// </summary>
            internal bool TryGetStorePrimitiveType(string typeName, out PrimitiveType primitiveType, out bool excludedForTarget)
            {
                excludedForTarget = false;
                var success = ItemCollection.TryGetItem<PrimitiveType>(StoreNamespace + "." + typeName, false, out primitiveType);

                // If targetting 4.0 using 4.5 then we need to ignore geometry and geography types just like we would have done when
                // generating with 4.0. We can only get the base spatial types since we won't reverse engineer to derived spatial
                // types. We don't need to do anything for enums because we will never reverse engineer to an enum.
                if (success &&
                    _targetEntityFrameworkVersion.CompareTo(EntityFrameworkVersions.Version3) < 0 &&
                    (primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Geography || primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Geometry))
                {
                    excludedForTarget = true;
                    primitiveType = null;
                    return false;
                }
                return success;
            }

            internal string StoreNamespace
            {
                get
                {
                    if (_storeNamespace == null)
                    {
                        foreach (PrimitiveType type in ItemCollection.GetItems<PrimitiveType>())
                        {
                            if (MetadataUtil.IsStoreType(type))
                            {
                                _storeNamespace = type.NamespaceName;
                                break;
                            }
                        }
                    }
                    return _storeNamespace;
                }
            }

            internal void AddErrorsForType(EdmType type, params EdmSchemaError [] errors)
            {
                AddErrorsForType(type, (ICollection<EdmSchemaError>)errors);
            }

            internal void AddErrorsForType(EdmType type, ICollection<EdmSchemaError> errors)
            {
                if (errors.Count != 0)
                {
                    if (type == null)
                    {
                        type = MetadataItemSerializer.NoSpecificTypeSentinal;
                    }
                    
                    if (ItemToErrorsMap.ContainsKey(type))
                    {
                        ItemToErrorsMap[type].AddRange(errors);
                    }
                    else
                    {
                        List<EdmSchemaError> list = errors as List<EdmSchemaError>;
                        if (list == null)
                        {
                            list = new List<EdmSchemaError>(errors);
                        }

                        ItemToErrorsMap.Add(type, list);
                    }
                }
            }
        }
    }
}
