using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ServiceStack.Html
{
	public class ModelStateDictionary : IDictionary<string, ModelState>
	{
		private readonly Dictionary<string, ModelState> innerDictionary = new Dictionary<string, ModelState>(StringComparer.OrdinalIgnoreCase);

		public ModelStateDictionary() {}

		public ModelStateDictionary(ModelStateDictionary dictionary)
		{
			if (dictionary == null)
			{
				throw new ArgumentNullException("dictionary");
			}

			foreach (var entry in dictionary)
			{
				innerDictionary.Add(entry.Key, entry.Value);
			}
		}

		public int Count
		{
			get
			{
				return innerDictionary.Count;
			}
		}

		public bool IsReadOnly
		{
			get
			{
				return ((IDictionary<string, ModelState>)innerDictionary).IsReadOnly;
			}
		}

		public bool IsValid
		{
			get
			{
				return Values.All(modelState => modelState.Errors.Count == 0);
			}
		}

		public ICollection<string> Keys
		{
			get
			{
				return innerDictionary.Keys;
			}
		}

		public ModelState this[string key]
		{
			get
			{
				ModelState value;
				innerDictionary.TryGetValue(key, out value);
				return value;
			}
			set
			{
				innerDictionary[key] = value;
			}
		}

		public ICollection<ModelState> Values
		{
			get
			{
				return innerDictionary.Values;
			}
		}

		public void Add(KeyValuePair<string, ModelState> item)
		{
			((IDictionary<string, ModelState>)innerDictionary).Add(item);
		}

		public void Add(string key, ModelState value)
		{
			innerDictionary.Add(key, value);
		}

		public void AddModelError(string key, Exception exception)
		{
			GetModelStateForKey(key).Errors.Add(exception);
		}

		public void AddModelError(string key, string errorMessage)
		{
			GetModelStateForKey(key).Errors.Add(errorMessage);
		}

		public void Clear()
		{
			innerDictionary.Clear();
		}

		public bool Contains(KeyValuePair<string, ModelState> item)
		{
			return ((IDictionary<string, ModelState>)innerDictionary).Contains(item);
		}

		public bool ContainsKey(string key)
		{
			return innerDictionary.ContainsKey(key);
		}

		public void CopyTo(KeyValuePair<string, ModelState>[] array, int arrayIndex)
		{
			((IDictionary<string, ModelState>)innerDictionary).CopyTo(array, arrayIndex);
		}

		public IEnumerator<KeyValuePair<string, ModelState>> GetEnumerator()
		{
			return innerDictionary.GetEnumerator();
		}

		private ModelState GetModelStateForKey(string key)
		{
			if (key == null)
			{
				throw new ArgumentNullException("key");
			}

			ModelState modelState;
			if (!TryGetValue(key, out modelState))
			{
				modelState = new ModelState();
				this[key] = modelState;
			}

			return modelState;
		}

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

			// if the key is not found in the dictionary, we just say that it's valid (since there are no errors)
			return DictionaryHelpers.FindKeysWithPrefix(this, key).All(entry => entry.Value.Errors.Count == 0);
		}

		public void Merge(ModelStateDictionary dictionary)
		{
			if (dictionary == null)
			{
				return;
			}

			foreach (var entry in dictionary)
			{
				this[entry.Key] = entry.Value;
			}
		}

		public bool Remove(KeyValuePair<string, ModelState> item)
		{
			return ((IDictionary<string, ModelState>)innerDictionary).Remove(item);
		}

		public bool Remove(string key)
		{
			return innerDictionary.Remove(key);
		}

		public void SetModelValue(string key, ValueProviderResult value)
		{
			GetModelStateForKey(key).Value = value;
		}

		public bool TryGetValue(string key, out ModelState value)
		{
			return innerDictionary.TryGetValue(key, out value);
		}

		#region IEnumerable Members
		IEnumerator IEnumerable.GetEnumerator()
		{
			return ((IEnumerable)innerDictionary).GetEnumerator();
		}
		#endregion
	}

	internal static class DictionaryHelpers
	{
		public static IEnumerable<KeyValuePair<string, TValue>> FindKeysWithPrefix<TValue>(IDictionary<string, TValue> dictionary, string prefix)
		{
			TValue exactMatchValue;
			if (dictionary.TryGetValue(prefix, out exactMatchValue))
			{
				yield return new KeyValuePair<string, TValue>(prefix, exactMatchValue);
			}

			foreach (var entry in dictionary)
			{
				string key = entry.Key;

				if (key.Length <= prefix.Length)
				{
					continue;
				}

				if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}

				char charAfterPrefix = key[prefix.Length];
				switch (charAfterPrefix)
				{
					case '[':
					case '.':
						yield return entry;
						break;
				}
			}
		}

		public static bool DoesAnyKeyHavePrefix<TValue>(IDictionary<string, TValue> dictionary, string prefix)
		{
			return FindKeysWithPrefix(dictionary, prefix).Any();
		}

        public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue @default)
        {
            TValue value;
            if (dict.TryGetValue(key, out value)) {
                return value;
            }
            return @default;
        }
	}
}
