// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

using NodeColor = System.Byte;

namespace System.Data.Entity.Core.Mapping.Update.Internal
{
    using System.Collections.Generic;
    using System.Data.Entity.Core.Common.Utils;
    using System.Data.Entity.Core.Objects;
    using System.Data.Entity.Resources;
    using System.Data.Entity.Utilities;
    using System.Diagnostics;
    using System.Linq;

    /// <summary>
    ///     Manages interactions between keys in the update pipeline (e.g. via referential constraints)
    /// </summary>
    internal class KeyManager
    {
        private readonly Dictionary<Tuple<EntityKey, string, bool>, int> _foreignKeyIdentifiers =
            new Dictionary<Tuple<EntityKey, string, bool>, int>();

        private readonly Dictionary<EntityKey, EntityKey> _valueKeyToTempKey = new Dictionary<EntityKey, EntityKey>();
        private readonly Dictionary<EntityKey, int> _keyIdentifiers = new Dictionary<EntityKey, int>();

        private readonly List<IdentifierInfo> _identifiers = new List<IdentifierInfo>
                                                                 {
                                                                     new IdentifierInfo()
                                                                 };

        private const NodeColor White = 0;
        private const NodeColor Black = 1;
        private const NodeColor Gray = 2;

        /// <summary>
        ///     Given an identifier, returns the canonical identifier for the clique including all identifiers
        ///     with the same value (via referential integrity constraints).
        /// </summary>
        internal int GetCliqueIdentifier(int identifier)
        {
            var partition = _identifiers[identifier].Partition;
            if (null != partition)
            {
                return partition.PartitionId;
            }
            // if there is no explicit (count > 1) partition, the node is its own
            // partition
            return identifier;
        }

        /// <summary>
        ///     Indicate that the principal identifier controls the value for the dependent identifier.
        /// </summary>
        internal void AddReferentialConstraint(IEntityStateEntry dependentStateEntry, int dependentIdentifier, int principalIdentifier)
        {
            var dependentInfo = _identifiers[dependentIdentifier];

            // A value is trivially constrained to be itself
            if (dependentIdentifier != principalIdentifier)
            {
                // track these as 'equivalent values'; used to determine canonical identifier for dependency
                // ordering and validation of constraints
                AssociateNodes(dependentIdentifier, principalIdentifier);

                // remember the constraint
                LinkedList<int>.Add(ref dependentInfo.References, principalIdentifier);
                var principalInfo = _identifiers[principalIdentifier];
                LinkedList<int>.Add(ref principalInfo.ReferencedBy, dependentIdentifier);
            }

            LinkedList<IEntityStateEntry>.Add(ref dependentInfo.DependentStateEntries, dependentStateEntry);
        }

        /// <summary>
        ///     Given an 'identifier' result, register it as the owner (for purposes of error reporting,
        ///     since foreign key results can sometimes get projected out after a join)
        /// </summary>
        internal void RegisterIdentifierOwner(PropagatorResult owner)
        {
            Debug.Assert(
                PropagatorResult.NullIdentifier != owner.Identifier, "invalid operation for a " +
                                                                     "result without an identifier");

            _identifiers[owner.Identifier].Owner = owner;
        }

        /// <summary>
        ///     Checks if the given identifier has a registered 'owner'
        /// </summary>
        internal bool TryGetIdentifierOwner(int identifier, out PropagatorResult owner)
        {
            owner = _identifiers[identifier].Owner;
            return null != owner;
        }

        /// <summary>
        ///     Gets identifier for an entity key member at the given offset (ordinal of the property
        ///     in the key properties for the relevant entity set)
        /// </summary>
        internal int GetKeyIdentifierForMemberOffset(EntityKey entityKey, int memberOffset, int keyMemberCount)
        {
            int result;

            // get offset for first element of key
            if (!_keyIdentifiers.TryGetValue(entityKey, out result))
            {
                result = _identifiers.Count;
                for (var i = 0; i < keyMemberCount; i++)
                {
                    _identifiers.Add(new IdentifierInfo());
                }
                _keyIdentifiers.Add(entityKey, result);
            }

            // add memberOffset relative to first element of key
            result += memberOffset;
            return result;
        }

        /// <summary>
        ///     Creates identifier for a (non-key) entity member (or return existing identifier).
        /// </summary>
        internal int GetKeyIdentifierForMember(EntityKey entityKey, string member, bool currentValues)
        {
            int result;
            var position = Tuple.Create(entityKey, member, currentValues);

            if (!_foreignKeyIdentifiers.TryGetValue(position, out result))
            {
                result = _identifiers.Count;
                _identifiers.Add(new IdentifierInfo());
                _foreignKeyIdentifiers.Add(position, result);
            }

            return result;
        }

        /// <summary>
        ///     Gets all relationship entries constrained by the given identifier. If there is a referential constraint
        ///     where the identifier is the principal, returns results corresponding to the constrained
        ///     dependent relationships.
        /// </summary>
        internal IEnumerable<IEntityStateEntry> GetDependentStateEntries(int identifier)
        {
            return LinkedList<IEntityStateEntry>.Enumerate(_identifiers[identifier].DependentStateEntries);
        }

        /// <summary>
        ///     Given a value, returns the value for its principal owner.
        /// </summary>
        internal object GetPrincipalValue(PropagatorResult result)
        {
            var currentIdentifier = result.Identifier;

            if (PropagatorResult.NullIdentifier == currentIdentifier)
            {
                // for non-identifiers, there is nothing to resolve
                return result.GetSimpleValue();
            }

            // find principals for this value
            var first = true;
            object value = null;
            foreach (var principal in GetPrincipals(currentIdentifier))
            {
                var ownerResult = _identifiers[principal].Owner;
                if (null != ownerResult)
                {
                    if (first)
                    {
                        // result is taken from the first principal
                        value = ownerResult.GetSimpleValue();
                        first = false;
                    }
                    else
                    {
                        // subsequent results are validated for consistency with the first
                        if (!ByValueEqualityComparer.Default.Equals(value, ownerResult.GetSimpleValue()))
                        {
                            throw new ConstraintException(Strings.Update_ReferentialConstraintIntegrityViolation);
                        }
                    }
                }
            }

            if (first)
            {
                // if there are no principals, return the current value directly
                value = result.GetSimpleValue();
            }
            return value;
        }

        /// <summary>
        ///     Gives all principals affecting the given identifier.
        /// </summary>
        internal IEnumerable<int> GetPrincipals(int identifier)
        {
            return WalkGraph(identifier, (info) => info.References, true);
        }

        /// <summary>
        ///     Gives all direct references of the given identifier
        /// </summary>
        internal IEnumerable<int> GetDirectReferences(int identifier)
        {
            var references = _identifiers[identifier].References;
            foreach (var i in LinkedList<int>.Enumerate(references))
            {
                yield return i;
            }
        }

        /// <summary>
        ///     Gets all dependents affected by the given identifier.
        /// </summary>
        internal IEnumerable<int> GetDependents(int identifier)
        {
            return WalkGraph(identifier, (info) => info.ReferencedBy, false);
        }

        private IEnumerable<int> WalkGraph(int identifier, Func<IdentifierInfo, LinkedList<int>> successorFunction, bool leavesOnly)
        {
            var stack = new Stack<int>();
            stack.Push(identifier);

            // using a non-recursive implementation to avoid overhead of recursive yields
            while (stack.Count > 0)
            {
                var currentIdentifier = stack.Pop();
                var successors = successorFunction(_identifiers[currentIdentifier]);
                if (null != successors)
                {
                    foreach (var successor in LinkedList<int>.Enumerate(successors))
                    {
                        stack.Push(successor);
                    }
                    if (!leavesOnly)
                    {
                        yield return currentIdentifier;
                    }
                }
                else
                {
                    yield return currentIdentifier;
                }
            }
        }

        /// <summary>
        ///     Checks whether the given identifier has any contributing principals.
        /// </summary>
        internal bool HasPrincipals(int identifier)
        {
            return null != _identifiers[identifier].References;
        }

        /// <summary>
        ///     Checks whether there is a cycle in the identifier graph.
        /// </summary>
        internal void ValidateReferentialIntegrityGraphAcyclic()
        {
            // _identifierRefConstraints describes the referential integrity
            // 'identifier' graph. How is a conflict
            // even possible? The state manager does not enforce integrity
            // constraints but rather forces them to be satisfied. In other words,
            // the dependent entity takes the value of its parent. If a parent
            // is also a child however, there is no way of determining which one
            // controls the value.

            // Standard DFS search

            // Color nodes as we traverse the graph: White means we have not
            // explored a node yet, Gray means we are currently visiting a node, and Black means
            // we have finished visiting a node.
            var color = new NodeColor[_identifiers.Count];

            for (int i = 0, n = _identifiers.Count; i < n; i++)
            {
                if (color[i] == White)
                {
                    ValidateReferentialIntegrityGraphAcyclic(i, color, null);
                }
            }
        }

        /// <summary>
        ///     Registers an added entity so that it can be matched by a foreign key lookup.
        /// </summary>
        internal void RegisterKeyValueForAddedEntity(IEntityStateEntry addedEntry)
        {
            DebugCheck.NotNull(addedEntry);
            Debug.Assert(!addedEntry.IsRelationship);
            Debug.Assert(!addedEntry.IsKeyEntry);
            Debug.Assert(addedEntry.EntityKey.IsTemporary);

            // map temp key to 'value' key (if all values of the key are non null)
            var tempKey = addedEntry.EntityKey;
            EntityKey valueKey;
            var keyMembers = addedEntry.EntitySet.ElementType.KeyMembers;
            var currentValues = addedEntry.CurrentValues;

            var keyValues = new object[keyMembers.Count];
            var hasNullValue = false;

            for (int i = 0, n = keyMembers.Count; i < n; i++)
            {
                var ordinal = currentValues.GetOrdinal(keyMembers[i].Name);
                if (currentValues.IsDBNull(ordinal))
                {
                    hasNullValue = true;
                    break;
                }
                else
                {
                    keyValues[i] = currentValues.GetValue(ordinal);
                }
            }

            if (hasNullValue)
            {
                return;
            }
            else
            {
                valueKey = keyValues.Length == 1
                               ? new EntityKey(addedEntry.EntitySet, keyValues[0])
                               : new EntityKey(addedEntry.EntitySet, keyValues);
            }

            if (_valueKeyToTempKey.ContainsKey(valueKey))
            {
                // null indicates that there are collisions on key values
                _valueKeyToTempKey[valueKey] = null;
            }
            else
            {
                _valueKeyToTempKey.Add(valueKey, tempKey);
            }
        }

        /// <summary>
        ///     There are three states:
        ///     - No temp keys with the given value exists (return false, out null)
        ///     - A single temp key exists with the given value (return true, out non null)
        ///     - Multiple temp keys exist with the given value (return true, out null)
        /// </summary>
        internal bool TryGetTempKey(EntityKey valueKey, out EntityKey tempKey)
        {
            return _valueKeyToTempKey.TryGetValue(valueKey, out tempKey);
        }

        private void ValidateReferentialIntegrityGraphAcyclic(int node, NodeColor[] color, LinkedList<int> parent)
        {
            color[node] = Gray; // color the node to indicate we're visiting it
            LinkedList<int>.Add(ref parent, node);
            foreach (var successor in LinkedList<int>.Enumerate(_identifiers[node].References))
            {
                switch (color[successor])
                {
                    case White:
                        // haven't seen this node yet; visit it
                        ValidateReferentialIntegrityGraphAcyclic(successor, color, parent);
                        break;
                    case Gray:
                        {
                            // recover all affected entities from the path (keep on walking
                            // until we hit the 'successor' again which bounds the cycle)
                            var stateEntriesInCycle = new List<IEntityStateEntry>();
                            foreach (var identifierInCycle in LinkedList<int>.Enumerate(parent))
                            {
                                var owner = _identifiers[identifierInCycle].Owner;
                                if (null != owner)
                                {
                                    stateEntriesInCycle.Add(owner.StateEntry);
                                }

                                if (identifierInCycle == successor)
                                {
                                    // cycle complete
                                    break;
                                }
                            }

                            throw new UpdateException(
                                Strings.Update_CircularRelationships, null, stateEntriesInCycle.Cast<ObjectStateEntry>().Distinct());
                        }
                    default:
                        // done
                        break;
                }
            }
            color[node] = Black; // color the node to indicate we're done visiting it
        }

        /// <summary>
        ///     Ensures firstId and secondId belong to the same partition
        /// </summary>
        internal void AssociateNodes(int firstId, int secondId)
        {
            if (firstId == secondId)
            {
                // A node is (trivially) associated with itself
                return;
            }
            var firstPartition = _identifiers[firstId].Partition;
            if (null != firstPartition)
            {
                var secondPartition = _identifiers[secondId].Partition;
                if (null != secondPartition)
                {
                    // merge partitions
                    firstPartition.Merge(this, secondPartition);
                }
                else
                {
                    // add y to existing x partition
                    firstPartition.AddNode(this, secondId);
                }
            }
            else
            {
                var secondPartition = _identifiers[secondId].Partition;
                if (null != secondPartition)
                {
                    // add x to existing y partition
                    secondPartition.AddNode(this, firstId);
                }
                else
                {
                    // Neither node is known
                    Partition.CreatePartition(this, firstId, secondId);
                }
            }
        }

        private sealed class Partition
        {
            internal readonly int PartitionId;
            private readonly List<int> _nodeIds;

            private Partition(int partitionId)
            {
                _nodeIds = new List<int>(2);
                PartitionId = partitionId;
            }

            internal static void CreatePartition(KeyManager manager, int firstId, int secondId)
            {
                var partition = new Partition(firstId);
                partition.AddNode(manager, firstId);
                partition.AddNode(manager, secondId);
            }

            internal void AddNode(KeyManager manager, int nodeId)
            {
                Debug.Assert(!_nodeIds.Contains(nodeId), "don't add existing node to partition");
                _nodeIds.Add(nodeId);
                manager._identifiers[nodeId].Partition = this;
            }

            internal void Merge(KeyManager manager, Partition other)
            {
                if (other.PartitionId == PartitionId)
                {
                    return;
                }
                foreach (var element in other._nodeIds)
                {
                    // reparent the node
                    AddNode(manager, element);
                }
            }
        }

        /// <summary>
        ///     Simple linked list class.
        /// </summary>
        private sealed class LinkedList<T>
        {
            private readonly T _value;
            private readonly LinkedList<T> _previous;

            private LinkedList(T value, LinkedList<T> previous)
            {
                _value = value;
                _previous = previous;
            }

            internal static IEnumerable<T> Enumerate(LinkedList<T> current)
            {
                while (null != current)
                {
                    yield return current._value;
                    current = current._previous;
                }
            }

            internal static void Add(ref LinkedList<T> list, T value)
            {
                list = new LinkedList<T>(value, list);
            }
        }

        /// <summary>
        ///     Collects information relevant to a particular identifier.
        /// </summary>
        private sealed class IdentifierInfo
        {
            internal Partition Partition;
            internal PropagatorResult Owner;
            internal LinkedList<IEntityStateEntry> DependentStateEntries;
            internal LinkedList<int> References;
            internal LinkedList<int> ReferencedBy;
        }
    }
}
