// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

namespace LightJson
{
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Diagnostics.CodeAnalysis;

	/// <summary>
	/// Represents a key-value pair collection of JsonValue objects.
	/// </summary>
	[DebuggerDisplay("Count = {Count}")]
	[DebuggerTypeProxy(typeof(JsonObjectDebugView))]
	internal sealed class JsonObject : IEnumerable<KeyValuePair<string, JsonValue>>, IEnumerable<JsonValue>
	{
		private IDictionary<string, JsonValue> properties;

		/// <summary>
		/// Initializes a new instance of the <see cref="JsonObject"/> class.
		/// </summary>
		public JsonObject()
		{
			this.properties = new Dictionary<string, JsonValue>();
		}

		/// <summary>
		/// Gets the number of properties in this JsonObject.
		/// </summary>
		/// <value>The number of properties in this JsonObject.</value>
		public int Count {
			get {
				return this.properties.Count;
			}
		}

		/// <summary>
		/// Gets or sets the property with the given key.
		/// </summary>
		/// <param name="key">The key of the property to get or set.</param>
		/// <remarks>
		/// The getter will return JsonValue.Null if the given key is not associated with any value.
		/// </remarks>
		public JsonValue this[string key] {
			get {
				JsonValue value;

				if (this.properties.TryGetValue(key, out value)) {
					return value;
				} else {
					return JsonValue.Null;
				}
			}

			set {
				this.properties[key] = value;
			}
		}

		/// <summary>
		/// Adds a key with a null value to this collection.
		/// </summary>
		/// <param name="key">The key of the property to be added.</param>
		/// <remarks>Returns this JsonObject.</remarks>
		/// <returns>The <see cref="JsonObject"/> that was added.</returns>
		public JsonObject Add(string key)
		{
			return this.Add(key, JsonValue.Null);
		}

		/// <summary>
		/// Adds a value associated with a key to this collection.
		/// </summary>
		/// <param name="key">The key of the property to be added.</param>
		/// <param name="value">The value of the property to be added.</param>
		/// <returns>Returns this JsonObject.</returns>
		public JsonObject Add(string key, JsonValue value)
		{
			this.properties.Add(key, value);
			return this;
		}

		/// <summary>
		/// Removes a property with the given key.
		/// </summary>
		/// <param name="key">The key of the property to be removed.</param>
		/// <returns>
		/// Returns true if the given key is found and removed; otherwise, false.
		/// </returns>
		public bool Remove(string key)
		{
			return this.properties.Remove(key);
		}

		/// <summary>
		/// Clears the contents of this collection.
		/// </summary>
		/// <returns>Returns this JsonObject.</returns>
		public JsonObject Clear()
		{
			this.properties.Clear();
			return this;
		}

		/// <summary>
		/// Changes the key of one of the items in the collection.
		/// </summary>
		/// <remarks>
		/// This method has no effects if the <i>oldKey</i> does not exists.
		/// If the <i>newKey</i> already exists, the value will be overwritten.
		/// </remarks>
		/// <param name="oldKey">The name of the key to be changed.</param>
		/// <param name="newKey">The new name of the key.</param>
		/// <returns>Returns this JsonObject.</returns>
		public JsonObject Rename(string oldKey, string newKey)
		{
			if (oldKey == newKey) {
				// Renaming to the same name just does nothing
				return this;
			}

			JsonValue value;

			if (this.properties.TryGetValue(oldKey, out value)) {
				this[newKey] = value;
				this.Remove(oldKey);
			}

			return this;
		}

		/// <summary>
		/// Determines whether this collection contains an item assosiated with the given key.
		/// </summary>
		/// <param name="key">The key to locate in this collection.</param>
		/// <returns>Returns true if the key is found; otherwise, false.</returns>
		public bool ContainsKey(string key)
		{
			return this.properties.ContainsKey(key);
		}

		/// <summary>
		/// Determines whether this collection contains the given JsonValue.
		/// </summary>
		/// <param name="value">The value to locate in this collection.</param>
		/// <returns>Returns true if the value is found; otherwise, false.</returns>
		public bool Contains(JsonValue value)
		{
			return this.properties.Values.Contains(value);
		}

		/// <summary>
		/// Returns an enumerator that iterates through this collection.
		/// </summary>
		/// <returns>The enumerator that iterates through this collection.</returns>
		public IEnumerator<KeyValuePair<string, JsonValue>> GetEnumerator()
		{
			return this.properties.GetEnumerator();
		}

		/// <summary>
		/// Returns an enumerator that iterates through this collection.
		/// </summary>
		/// <returns>The enumerator that iterates through this collection.</returns>
		IEnumerator<JsonValue> IEnumerable<JsonValue>.GetEnumerator()
		{
			return this.properties.Values.GetEnumerator();
		}

		/// <summary>
		/// Returns an enumerator that iterates through this collection.
		/// </summary>
		/// <returns>The enumerator that iterates through this collection.</returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		[ExcludeFromCodeCoverage]
		private class JsonObjectDebugView
		{
			private JsonObject jsonObject;

			public JsonObjectDebugView(JsonObject jsonObject)
			{
				this.jsonObject = jsonObject;
			}

			[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
			public KeyValuePair[] Keys {
				get {
					var keys = new KeyValuePair[this.jsonObject.Count];

					var i = 0;
					foreach (var property in this.jsonObject) {
						keys[i] = new KeyValuePair(property.Key, property.Value);
						i += 1;
					}

					return keys;
				}
			}

			[DebuggerDisplay("{value.ToString(),nq}", Name = "{key}", Type = "JsonValue({Type})")]
			public class KeyValuePair
			{
				[DebuggerBrowsable(DebuggerBrowsableState.Never)]
				private string key;

				[DebuggerBrowsable(DebuggerBrowsableState.Never)]
				private JsonValue value;

				public KeyValuePair(string key, JsonValue value)
				{
					this.key = key;
					this.value = value;
				}

				[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
				public object View {
					get {
						if (this.value.IsJsonObject) {
							return (JsonObject)this.value;
						} else if (this.value.IsJsonArray) {
							return (JsonArray)this.value;
						} else {
							return this.value;
						}
					}
				}

				[DebuggerBrowsable(DebuggerBrowsableState.Never)]
				private JsonValueType Type {
					get {
						return this.value.Type;
					}
				}
			}
		}
	}
}
