using System;
using System.Linq;
using UnityEngine;
using Random = C5.C5Random;
using System.Collections.Generic;
using System.Collections;
using System.IO;

public static class ExtensionMethods
{

	#region Random

	public static bool NextBool (this Random r)
	{
		return r.Next (2) == 0;
	}

	public static double NextDouble (this Random r, double max)
	{
		return r.NextDouble () * max;
	}

	public static double NextDouble (this Random r, double min, double max)
	{
		return min + r.NextDouble () * (max - min);
	}

	public static float NextFloat (this Random r)
	{
		return (float)r.NextDouble ();
	}

	public static float NextFloat (this Random r, float max)
	{
		return r.NextFloat () * max;
	}

	public static float NextFloat (this Random r, float min, float max)
	{
		return min + r.NextFloat () * (max - min);
	}

	public static float NextFloatDual (this Random r, float min, float max)
	{
		var delta = max - min;
		var value = r.NextFloat (-delta, delta);
		float result;
		if (value < 0) {
			result = -min + value;
		} else {
			result = min + value;
		}
		return result;
	}

	public static T NextElement<T> (this Random r, IEnumerable<T> elements)
	{
		if (elements == null || elements.Any () == false) {
			throw new ArgumentException ("The given list is null or has no elements.");
		}

		var count = elements.Count ();

		int index = r.Next (count);

		var result = elements.ElementAt (index);

		return result;
	}

	public static T NextElement<T> (this Random r, IList<T> elements)
	{
		if (elements == null || elements.Count == 0) {
			throw new ArgumentException ("The given list is null or has no elements.");
		}

		return elements [r.Next (elements.Count)];
	}

	public static T NextElement<T> (this Random r, IEnumerable<T> elements, Func<T, float> chance)
	{
		if (elements == null || elements.Any () == false) {
			throw new ArgumentException ("The given list is null");
		}

		if (chance == null) {
			throw new ArgumentException ("The given chance function is null");
		}
	
		float totalChance = elements.Sum (chance);

		if (totalChance == 0.0) {
			throw new Exception ("No element with a chance other than 0.0");
		}

		float selection = r.NextFloat (totalChance);
	
		float currentSum = 0f;
		foreach (var element in elements) {
			currentSum += chance (element);
			if (currentSum > selection) {
				return element;
			}
		}
	
		throw new Exception ("Error in NextElement - unexpected end reached.");
	}

	public static T NextElement<T> (this Random r, IList<T> elements, Func<T, float> chance)
	{
		if (elements.Count == 0) {
			throw new ArgumentException ("The given list is null or has no elements.");
		}
	
		float totalChance = elements.Sum (chance);
		float selection = r.NextFloat (totalChance);
	
		float currentSum = 0f;
		for (int i = 0; i < elements.Count; i++) {
			currentSum += chance (elements [i]);
			if (currentSum > selection) {
				return elements [i];
			}
		}
	
		throw new Exception ("Error in NextElement - unexpected end reached.");
	}

	public static Vector2 NextInsideUnitCircle (this Random r)
	{
		var angle = r.NextFloat () * Mathf.PI * 2;
		var radius = Mathf.Sqrt (r.NextFloat ());
		var x = radius * Mathf.Cos (angle);
		var y = radius * Mathf.Sin (angle);
		return new Vector2 (x, y);
	}

	#endregion

	public static T[] CopyAndResize<T> (this T[] arr, int addCount, int startIndex = 0)
	{
		var result = new T[arr.Length + addCount];
		Array.Copy (arr, 0, result, startIndex, arr.Length);
		return result;
	}

	public static T[] CopyAndRemoveFirst<T> (this T[] arr, int removeCount)
	{
		var result = new T[arr.Length - removeCount];
		Array.Copy (arr, removeCount, result, 0, arr.Length - removeCount);
		return result;
	}

	#region MonoBehaviour

	public static void StartCoroutine (this MonoBehaviour mb, IEnumerable coroutine)
	{
		mb.StartCoroutine (coroutine.GetEnumerator ());
	}

	public static void ExecuteDelayedByFrames (this MonoBehaviour mb, int framesDelay, Action action)
	{
		mb.StartCoroutine (ExecuteDelayedByFramesCoroutine (framesDelay, action));
	}

	public static void ExecuteDelayedByTime (this MonoBehaviour mb, Action action, float time)
	{
		mb.StartCoroutine (ExecuteDelayedByTimeCoroutine (time, action));
	}

	private static IEnumerator ExecuteDelayedByFramesCoroutine (int framesDelay, Action action)
	{
		for (int i = 0; i < framesDelay; i++) {
			yield return null;
		}
		action ();
	}

	private static IEnumerator ExecuteDelayedByTimeCoroutine (float timeDelay, Action action)
	{
		yield return new WaitForSeconds (timeDelay);
		action ();
	}

	#endregion

	#region RectTransform

	private static Vector3[] cornerCoords = new Vector3[4];

	public static Vector3 GetWorldCenter (this RectTransform tr)
	{
		tr.GetWorldCorners (cornerCoords);
		var width = cornerCoords [3].x - cornerCoords [0].x;
		var height = cornerCoords [1].y - cornerCoords [0].y;
		var result = cornerCoords [0] + new Vector3 (width / 2f, height / 2f);
		return result;
	}

	public static Vector2 GetLocalCenter (this RectTransform tr)
	{
		var delta = new Vector2 ((0.5f - tr.pivot.x) * tr.sizeDelta.x, (0.5f - tr.pivot.y) * tr.sizeDelta.y);
		return tr.localPosition + (Vector3)delta;
	}

	public static void ChangePivot (this RectTransform tr, Vector2 newPivot)
	{
		var delta = newPivot - tr.pivot;
		var distance = new Vector2 (delta.x * tr.sizeDelta.x, delta.y * tr.sizeDelta.y);
		tr.localPosition += (Vector3)distance;

		tr.pivot = newPivot;
	}

	#endregion

	#region Strings

	public static string ToUpperFirst (this string text)
	{
		if (string.IsNullOrEmpty (text)) {
			return string.Empty;
		}
		return char.ToUpper (text [0]) + text.Substring (1).ToLower ();
	}

	#endregion

	#region Streams

	public static void WriteInt (this Stream stream, int data)
	{
		stream.WriteByte ((byte)((data >> 24) & 255));
		stream.WriteByte ((byte)((data >> 16) & 255));
		stream.WriteByte ((byte)((data >> 8) & 255));
		stream.WriteByte ((byte)((data >> 0) & 255));
	}

	#endregion
}

public static class GameObjectExtensionMethods
{
	public static void SetLayerRecursively (this GameObject go, int layerNumber)
	{
		foreach (Transform trans in go.GetComponentsInChildren<Transform>(true)) {
			trans.gameObject.layer = layerNumber;
		}
	}

	public static void SetLayerRecursively (this GameObject go, string layerName)
	{
		GameObjectExtensionMethods.SetLayerRecursively (go, LayerMask.NameToLayer (layerName));
	}
}