// ----------------------------------------------------------------------------
// <copyright file="Extensions.cs" company="Exit Games GmbH">
//   Photon Extensions - Copyright (C) 2018 Exit Games GmbH
// </copyright>
// <summary>
//   Provides some helpful methods and extensions for Hashtables, etc.
// </summary>
// <author>developer@photonengine.com</author>
// ----------------------------------------------------------------------------

#if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
#define SUPPORTED_UNITY
#endif


namespace Photon.Realtime
{
    using System.Collections;
	using System.Collections.Generic;
    using ExitGames.Client.Photon;

    #if SUPPORTED_UNITY
    using UnityEngine;
    using Debug = UnityEngine.Debug;
    #endif
    #if SUPPORTED_UNITY || NETFX_CORE
    using Hashtable = ExitGames.Client.Photon.Hashtable;
    using SupportClass = ExitGames.Client.Photon.SupportClass;
    #endif


    /// <summary>
    /// This static class defines some useful extension methods for several existing classes (e.g. Vector3, float and others).
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Merges all keys from addHash into the target. Adds new keys and updates the values of existing keys in target.
        /// </summary>
        /// <param name="target">The IDictionary to update.</param>
        /// <param name="addHash">The IDictionary containing data to merge into target.</param>
        public static void Merge(this IDictionary target, IDictionary addHash)
        {
            if (addHash == null || target.Equals(addHash))
            {
                return;
            }

            foreach (object key in addHash.Keys)
            {
                target[key] = addHash[key];
            }
        }

        /// <summary>
        /// Merges keys of type string to target Hashtable.
        /// </summary>
        /// <remarks>
        /// Does not remove keys from target (so non-string keys CAN be in target if they were before).
        /// </remarks>
        /// <param name="target">The target IDictionary passed in plus all string-typed keys from the addHash.</param>
        /// <param name="addHash">A IDictionary that should be merged partly into target to update it.</param>
        public static void MergeStringKeys(this IDictionary target, IDictionary addHash)
        {
            if (addHash == null || target.Equals(addHash))
            {
                return;
            }

            foreach (object key in addHash.Keys)
            {
                // only merge keys of type string
                if (key is string)
                {
                    target[key] = addHash[key];
                }
            }
        }

        /// <summary>Helper method for debugging of IDictionary content, including type-information. Using this is not performant.</summary>
        /// <remarks>Should only be used for debugging as necessary.</remarks>
        /// <param name="origin">Some Dictionary or Hashtable.</param>
        /// <returns>String of the content of the IDictionary.</returns>
        public static string ToStringFull(this IDictionary origin)
        {
            return SupportClass.DictionaryToString(origin, false);
        }

		/// <summary>Helper method for debugging of List<T> content. Using this is not performant.</summary>
		/// <remarks>Should only be used for debugging as necessary.</remarks>
		/// <param name="data">Any List<T> where T implements .ToString().</param>
		/// <returns>A comma-separated string containing each value's ToString().</returns>
		public static string ToStringFull<T>(this List<T> data)
		{
			if (data == null) return "null";

			string[] sb = new string[data.Count];
			for (int i = 0; i < data.Count; i++)
			{
				object o = data[i];
				sb[i] = (o != null) ? o.ToString() : "null";
			}

			return string.Join(", ", sb);
		}

        /// <summary>Helper method for debugging of object[] content. Using this is not performant.</summary>
        /// <remarks>Should only be used for debugging as necessary.</remarks>
        /// <param name="data">Any object[].</param>
        /// <returns>A comma-separated string containing each value's ToString().</returns>
        public static string ToStringFull(this object[] data)
        {
            if (data == null) return "null";

            string[] sb = new string[data.Length];
            for (int i = 0; i < data.Length; i++)
            {
                object o = data[i];
                sb[i] = (o != null) ? o.ToString() : "null";
            }

            return string.Join(", ", sb);
        }


        /// <summary>
        /// This method copies all string-typed keys of the original into a new Hashtable.
        /// </summary>
        /// <remarks>
        /// Does not recurse (!) into hashes that might be values in the root-hash.
        /// This does not modify the original.
        /// </remarks>
        /// <param name="original">The original IDictonary to get string-typed keys from.</param>
        /// <returns>New Hashtable containing only string-typed keys of the original.</returns>
        public static Hashtable StripToStringKeys(this IDictionary original)
        {
            Hashtable target = new Hashtable();
            if (original != null)
            {
                foreach (object key in original.Keys)
                {
                    if (key is string)
                    {
                        target[key] = original[key];
                    }
                }
            }

            return target;
        }

        /// <summary>
        /// This method copies all string-typed keys of the original into a new Hashtable.
        /// </summary>
        /// <remarks>
        /// Does not recurse (!) into hashes that might be values in the root-hash.
        /// This does not modify the original.
        /// </remarks>
        /// <param name="original">The original IDictonary to get string-typed keys from.</param>
        /// <returns>New Hashtable containing only string-typed keys of the original.</returns>
        public static Hashtable StripToStringKeys(this Hashtable original)
        {
            Hashtable target = new Hashtable();
            if (original != null)
            {
                foreach (DictionaryEntry entry in original)
                {
                    if (entry.Key is string)
                    {
                        target[entry.Key] = original[entry.Key];
                    }
                }
            }

            return target;
        }


        /// <summary>Used by StripKeysWithNullValues.</summary>
        /// <remarks>
        /// By making keysWithNullValue a static variable to clear before using, allocations only happen during the warm-up phase
        /// as the list needs to grow. Once it hit the high water mark for keys you need to remove.
        /// </remarks>
        private static readonly List<object> keysWithNullValue = new List<object>();

        /// <summary>Removes all keys with null values.</summary>
        /// <remarks>
        /// Photon properties are removed by setting their value to null. Changes the original IDictionary!
        /// Uses lock(keysWithNullValue), which should be no problem in expected use cases.
        /// </remarks>
        /// <param name="original">The IDictionary to strip of keys with null value.</param>
        public static void StripKeysWithNullValues(this IDictionary original)
        {
            lock (keysWithNullValue)
            {
                keysWithNullValue.Clear();

                foreach (DictionaryEntry entry in original)
                {
                    if (entry.Value == null)
                    {
                        keysWithNullValue.Add(entry.Key);
                    }
                }

                for (int i = 0; i < keysWithNullValue.Count; i++)
                {
                    var key = keysWithNullValue[i];
                    original.Remove(key);
                }
            }
        }

        /// <summary>Removes all keys with null values.</summary>
        /// <remarks>
        /// Photon properties are removed by setting their value to null. Changes the original IDictionary!
        /// Uses lock(keysWithNullValue), which should be no problem in expected use cases.
        /// </remarks>
        /// <param name="original">The IDictionary to strip of keys with null value.</param>
        public static void StripKeysWithNullValues(this Hashtable original)
        {
            lock (keysWithNullValue)
            {
                keysWithNullValue.Clear();

                foreach (DictionaryEntry entry in original)
                {
                    if (entry.Value == null)
                    {
                        keysWithNullValue.Add(entry.Key);
                    }
                }

                for (int i = 0; i < keysWithNullValue.Count; i++)
                {
                    var key = keysWithNullValue[i];
                    original.Remove(key);
                }
            }
        }


        /// <summary>
        /// Checks if a particular integer value is in an int-array.
        /// </summary>
        /// <remarks>This might be useful to look up if a particular actorNumber is in the list of players of a room.</remarks>
        /// <param name="target">The array of ints to check.</param>
        /// <param name="nr">The number to lookup in target.</param>
        /// <returns>True if nr was found in target.</returns>
        public static bool Contains(this int[] target, int nr)
        {
            if (target == null)
            {
                return false;
            }

            for (int index = 0; index < target.Length; index++)
            {
                if (target[index] == nr)
                {
                    return true;
                }
            }

            return false;
        }
    }
}