#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Newtonsoft.Json.Linq
{
    internal class JPropertyKeyedCollection : Collection<JToken>
    {
        private static readonly IEqualityComparer<string> Comparer = StringComparer.Ordinal;

        private Dictionary<string, JToken> _dictionary;

        private void AddKey(string key, JToken item)
        {
            EnsureDictionary();
            _dictionary[key] = item;
        }

        protected void ChangeItemKey(JToken item, string newKey)
        {
            if (!ContainsItem(item))
                throw new ArgumentException("The specified item does not exist in this KeyedCollection.");

            string keyForItem = GetKeyForItem(item);
            if (!Comparer.Equals(keyForItem, newKey))
            {
                if (newKey != null)
                    AddKey(newKey, item);

                if (keyForItem != null)
                    RemoveKey(keyForItem);
            }
        }

        protected override void ClearItems()
        {
            base.ClearItems();

            if (_dictionary != null)
                _dictionary.Clear();
        }

        public bool Contains(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            if (_dictionary != null)
                return _dictionary.ContainsKey(key);

            return false;
        }

        private bool ContainsItem(JToken item)
        {
            if (_dictionary == null)
                return false;

            string key = GetKeyForItem(item);
            JToken value;
            return _dictionary.TryGetValue(key, out value);
        }

        private void EnsureDictionary()
        {
            if (_dictionary == null)
                _dictionary = new Dictionary<string, JToken>(Comparer);
        }

        private string GetKeyForItem(JToken item)
        {
            return ((JProperty)item).Name;
        }

        protected override void InsertItem(int index, JToken item)
        {
            AddKey(GetKeyForItem(item), item);
            base.InsertItem(index, item);
        }

        public bool Remove(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            if (_dictionary != null)
                return _dictionary.ContainsKey(key) && Remove(_dictionary[key]);

            return false;
        }

        protected override void RemoveItem(int index)
        {
            string keyForItem = GetKeyForItem(Items[index]);
            RemoveKey(keyForItem);
            base.RemoveItem(index);
        }

        private void RemoveKey(string key)
        {
            if (_dictionary != null)
                _dictionary.Remove(key);
        }

        protected override void SetItem(int index, JToken item)
        {
            string keyForItem = GetKeyForItem(item);
            string keyAtIndex = GetKeyForItem(Items[index]);

            if (Comparer.Equals(keyAtIndex, keyForItem))
            {
                if (_dictionary != null)
                    _dictionary[keyForItem] = item;
            }
            else
            {
                AddKey(keyForItem, item);

                if (keyAtIndex != null)
                    RemoveKey(keyAtIndex);
            }
            base.SetItem(index, item);
        }

        public JToken this[string key]
        {
            get
            {
                if (key == null)
                    throw new ArgumentNullException("key");

                if (_dictionary != null)
                    return _dictionary[key];

                throw new KeyNotFoundException();
            }
        }

        public bool TryGetValue(string key, out JToken value)
        {
            if (_dictionary == null)
            {
                value = null;
                return false;
            }

            return _dictionary.TryGetValue(key, out value);
        }

        public ICollection<string> Keys
        {
            get
            {
                EnsureDictionary();
                return _dictionary.Keys;
            }
        }

        public ICollection<JToken> Values
        {
            get
            {
                EnsureDictionary();
                return _dictionary.Values;
            }
        }

        public bool Compare(JPropertyKeyedCollection other)
        {
            if (this == other)
                return true;

            // dictionaries in JavaScript aren't ordered
            // ignore order when comparing properties
            Dictionary<string, JToken> d1 = _dictionary;
            Dictionary<string, JToken> d2 = other._dictionary;

            if (d1 == null && d2 == null)
                return true;

            if (d1 == null)
                return (d2.Count == 0);

            if (d2 == null)
                return (d1.Count == 0);

            if (d1.Count != d2.Count)
                return false;

            foreach (KeyValuePair<string, JToken> keyAndProperty in d1)
            {
                JToken secondValue;
                if (!d2.TryGetValue(keyAndProperty.Key, out secondValue))
                    return false;

                JProperty p1 = (JProperty)keyAndProperty.Value;
                JProperty p2 = (JProperty)secondValue;

                if (p1.Value == null)
                    return (p2.Value == null);

                if (!p1.Value.DeepEquals(p2.Value))
                    return false;
            }

            return true;
        }
    }
}