﻿using UnityEngine;
using System.Linq;

namespace Crosstales
{
   /// <summary>Various extension methods.</summary>
   public static class ExtensionMethods
   {
      #region Variables

      private static readonly Vector3 FLAT_VECTOR = new Vector3(1, 0, 1);

      #endregion


      #region Strings

      /// <summary>
      /// Extension method for strings.
      /// Converts a string to title case (first letter uppercase).
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>Converted string in title case.</returns>
      public static string CTToTitleCase(this string str)
      {
         if (str == null)
            return str;
#if UNITY_WSA || UNITY_XBOXONE
         return toTitleCase(str);
#else
         return System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower());
#endif
      }

#if UNITY_WSA || UNITY_XBOXONE
      /// <summary>
      /// Converts to title case: each word starts with an upper case.
      /// </summary>
      private static string toTitleCase(string str)
      {
         if (str.Length == 0)
            return str;

         System.Text.StringBuilder result = new System.Text.StringBuilder(str);

         result[0] = char.ToUpper(result[0]);

         for (int ii = 1; ii < result.Length; ii++)
         {
            if (char.IsWhiteSpace(result[ii - 1]))
               result[ii] = char.ToUpper(result[ii]);
            else
               result[ii] = char.ToLower(result[ii]);
         }

         return result.ToString();
      }
#endif

      /// <summary>
      /// Extension method for strings.
      /// Reverses a string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>Reversed string.</returns>
      public static string CTReverse(this string str)
      {
         if (str == null)
            return str;

         char[] charArray = str.ToCharArray();
         System.Array.Reverse(charArray);

         return new string(charArray);
      }

      /// <summary>
      /// Extension method for strings.
      /// Default: case insensitive 'Replace'.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="oldString">String to replace.</param>
      /// <param name="newString">New replacement string.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>Replaced string.</returns>
      public static string CTReplace(this string str, string oldString, string newString, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         if (str == null)
            return str;

         if (oldString == null)
            return str;

         if (newString == null)
            return str;

         bool matchFound;
         do
         {
            int index = str.IndexOf(oldString, comp);

            matchFound = index >= 0;

            if (matchFound)
            {
               str = str.Remove(index, oldString.Length);

               str = str.Insert(index, newString);
            }
         } while (matchFound);

         return str;
      }

      /// <summary>
      /// Extension method for strings.
      /// Removes characters from a string
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="removeChars">Characters to remove.</param>
      /// <returns>String without the given characters.</returns>
      public static string CTRemoveChars(this string str, params char[] removeChars)
      {
         if (str == null)
            return str;

         if (removeChars == null)
            return str;

         return removeChars.Aggregate(str, (current, rmChar) => current.Replace($"{rmChar}", string.Empty));
      }

      /// <summary>
      /// Extension method for strings.
      /// Default: case insensitive 'Equals'.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="toCheck">String to check.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>True if the string contains the given string.</returns>
      public static bool CTEquals(this string str, string toCheck, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         return str?.Equals(toCheck, comp) == true;

         //if (toCheck == null)
         //    throw new System.ArgumentNullException("toCheck");
      }

      /// <summary>
      /// Extension method for strings.
      /// Default: case insensitive 'Contains'.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="toCheck">String to check.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>True if the string contains the given string.</returns>
      public static bool CTContains(this string str, string toCheck, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         return str?.IndexOf(toCheck, comp) >= 0;
      }

      /// <summary>
      /// Extension method for strings.
      /// Contains any given string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="searchTerms">Search terms separated by the given split-character.</param>
      /// <param name="splitChar">Split-character (optional, default: ' ')</param>
      /// <returns>True if the string contains any parts of the given string.</returns>
      public static bool CTContainsAny(this string str, string searchTerms, char splitChar = ' ')
      {
         if (str == null)
            return false;

         if (string.IsNullOrEmpty(searchTerms))
            return true;

         char[] split = { splitChar };

         return searchTerms.Split(split, System.StringSplitOptions.RemoveEmptyEntries).Any(searchTerm => str.CTContains(searchTerm));
      }

      /// <summary>
      /// Extension method for strings.
      /// Contains all given strings.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="searchTerms">Search terms separated by the given split-character.</param>
      /// <param name="splitChar">Split-character (optional, default: ' ')</param>
      /// <returns>True if the string contains all parts of the given string.</returns>
      public static bool CTContainsAll(this string str, string searchTerms, char splitChar = ' ')
      {
         if (str == null)
            return false;

         if (string.IsNullOrEmpty(searchTerms))
            return true;

         char[] split = { splitChar };

         return searchTerms.Split(split, System.StringSplitOptions.RemoveEmptyEntries).All(searchTerm => str.CTContains(searchTerm));
      }

      /// <summary>
      /// Extension method for strings.
      /// Replaces new lines with a replacement string pattern.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="replacement">Replacement string pattern (optional, default: "#nl#").</param>
      /// <param name="newLine">New line string (optional, default: System.Environment.NewLine).</param>
      /// <returns>Replaced string without new lines.</returns>
      public static string CTRemoveNewLines(this string str, string replacement = "#nl#", string newLine = null)
      {
         return str?.Replace(string.IsNullOrEmpty(newLine) ? System.Environment.NewLine : newLine, replacement);
      }

      /// <summary>
      /// Extension method for strings.
      /// Replaces a given string pattern with new lines in a string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="replacement">Replacement string pattern (optional, default: "#nl#").</param>
      /// <param name="newLine">New line string (optional, default: System.Environment.NewLine).</param>
      /// <returns>Replaced string with new lines.</returns>
      public static string CTAddNewLines(this string str, string replacement = "#nl#", string newLine = null)
      {
         return str?.CTReplace(replacement, string.IsNullOrEmpty(newLine) ? System.Environment.NewLine : newLine);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is numeric.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is numeric.</returns>
      [System.Obsolete("Please use 'CTIsNumeric' instead.")]
      public static bool CTisNumeric(this string str)
      {
         return CTIsNumeric(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is numeric.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is numeric.</returns>
      public static bool CTIsNumeric(this string str)
      {
         return str != null && double.TryParse(str, out double output);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is integer.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is integer.</returns>
      [System.Obsolete("Please use 'CTIsInteger' instead.")]
      public static bool CTisInteger(this string str)
      {
         return CTIsInteger(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is integer.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is integer.</returns>
      public static bool CTIsInteger(this string str)
      {
         if (str == null)
            return false;

         return !str.Contains(".") && long.TryParse(str, out long output);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is an email address.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is an email address.</returns>
      [System.Obsolete("Please use 'CTIsEmail' instead.")]
      public static bool CTisEmail(this string str)
      {
         return CTIsEmail(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is an email address.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is an email address.</returns>
      public static bool CTIsEmail(this string str)
      {
         return str != null && Crosstales.Common.Util.BaseConstants.REGEX_EMAIL.IsMatch(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is a website address.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is a website address.</returns>
      [System.Obsolete("Please use 'CTIsWebsite' instead.")]
      public static bool CTisWebsite(this string str)
      {
         return CTIsWebsite(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is a website address.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is a website address.</returns>
      public static bool CTIsWebsite(this string str)
      {
         return str != null && Crosstales.Common.Util.BaseConstants.REGEX_URL_WEB.IsMatch(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is a creditcard.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is a creditcard.</returns>
      [System.Obsolete("Please use 'CTIsCreditcard' instead.")]
      public static bool CTisCreditcard(this string str)
      {
         return CTIsCreditcard(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is a creditcard.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is a creditcard.</returns>
      public static bool CTIsCreditcard(this string str)
      {
         return str != null && Crosstales.Common.Util.BaseConstants.REGEX_CREDITCARD.IsMatch(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is an IPv4 address.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is an IPv4 address.</returns>
      [System.Obsolete("Please use 'CTIsIPv4' instead.")]
      public static bool CTisIPv4(this string str)
      {
         return CTIsIPv4(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is an IPv4 address.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is an IPv4 address.</returns>
      public static bool CTIsIPv4(this string str)
      {
         return str != null && Crosstales.Common.Util.NetworkHelper.isIPv4(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is alphanumeric.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is alphanumeric.</returns>
      [System.Obsolete("Please use 'CTIsAlphanumeric' instead.")]
      public static bool CTisAlphanumeric(this string str)
      {
         return CTIsAlphanumeric(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string is alphanumeric.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string is alphanumeric.</returns>
      public static bool CTIsAlphanumeric(this string str)
      {
         return str != null && Crosstales.Common.Util.BaseConstants.REGEX_ALPHANUMERIC.IsMatch(str);
      }


      /// <summary>
      /// Extension method for strings.
      /// Checks if the string has line endings.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string has line endings.</returns>
      [System.Obsolete("Please use 'CTHasLineEndings' instead.")]
      public static bool CThasLineEndings(this string str)
      {
         return CTHasLineEndings(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string has line endings.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string has line endings.</returns>
      public static bool CTHasLineEndings(this string str)
      {
         return str != null && Crosstales.Common.Util.BaseConstants.REGEX_LINEENDINGS.IsMatch(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string has invalid characters.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string has invalid characters.</returns>
      [System.Obsolete("Please use 'CTHasInvalidChars' instead.")]
      public static bool CThasInvalidChars(this string str)
      {
         return CTHasInvalidChars(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string has invalid characters.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <returns>True if the string has invalid characters.</returns>
      public static bool CTHasInvalidChars(this string str)
      {
         return str != null && Crosstales.Common.Util.BaseConstants.REGEX_INVALID_CHARS.IsMatch(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string starts with another string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="toCheck">String to check.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>True if the string is integer.</returns>
      public static bool CTStartsWith(this string str, string toCheck, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         if (str == null)
            return false;

         return string.IsNullOrEmpty(toCheck) || str.StartsWith(toCheck, comp);
      }

      /// <summary>
      /// Extension method for strings.
      /// Checks if the string ends with another string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="toCheck">String to check.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>True if the string is integer.</returns>
      public static bool CTEndsWith(this string str, string toCheck, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         if (str == null)
            return false;

         return string.IsNullOrEmpty(toCheck) || str.EndsWith(toCheck, comp);
      }

      /// <summary>
      /// Extension method for strings.
      /// Returns the index of the last occurence of a given string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="toCheck">String for the index.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>The index of the last occurence of the given string if the string is integer.</returns>
      public static int CTLastIndexOf(this string str, string toCheck, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         if (str == null)
            throw new System.ArgumentNullException(nameof(str));

         return string.IsNullOrEmpty(toCheck) ? 0 : str.LastIndexOf(toCheck, comp);
      }

      /// <summary>
      /// Extension method for strings.
      /// Returns the index of the first occurence of a given string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="toCheck">String for the index.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>The index of the first occurence of the given string if the string is integer.</returns>
      public static int CTIndexOf(this string str, string toCheck, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         if (str == null)
            throw new System.ArgumentNullException(nameof(str));

         return string.IsNullOrEmpty(toCheck) ? 0 : str.IndexOf(toCheck, comp);
      }

      /// <summary>
      /// Extension method for strings.
      /// Returns the index of the first occurence of a given string.
      /// </summary>
      /// <param name="str">String-instance.</param>
      /// <param name="toCheck">String for the index.</param>
      /// <param name="startIndex">Start index for the check.</param>
      /// <param name="comp">StringComparison-method (optional, default: StringComparison.OrdinalIgnoreCase)</param>
      /// <returns>The index of the first occurence of the given string if the string is integer.</returns>
      public static int CTIndexOf(this string str, string toCheck, int startIndex, System.StringComparison comp = System.StringComparison.OrdinalIgnoreCase)
      {
         if (str == null)
            throw new System.ArgumentNullException(nameof(str));

         return string.IsNullOrEmpty(toCheck) ? 0 : str.IndexOf(toCheck, startIndex, comp);
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the value of a string to a Base64-string.
      /// </summary>
      /// <param name="str">Input string.</param>
      /// <param name="encoding">Encoding of the string (optional, default: UTF8).</param>
      /// <returns>String value as converted Base64-string.</returns>
      public static string CTToBase64(this string str, System.Text.Encoding encoding = null)
      {
         if (str == null)
            return null;

         System.Text.Encoding _encoding = System.Text.Encoding.UTF8;

         if (encoding != null)
            _encoding = encoding;

         return _encoding.GetBytes(str).CTToBase64();
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the value of a Base64-string to a string.
      /// </summary>
      /// <param name="str">Input Base64-string.</param>
      /// <param name="encoding">Encoding of the string (optional, default: UTF8).</param>
      /// <returns>Base64-string value as converted string.</returns>
      public static string CTFromBase64(this string str, System.Text.Encoding encoding = null)
      {
         if (str == null)
            return null;

         System.Text.Encoding _encoding = System.Text.Encoding.UTF8;

         if (encoding != null)
            _encoding = encoding;

         return _encoding.GetString(str.CTFromBase64ToByteArray());
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the value of a Base64-string to a byte-array.
      /// </summary>
      /// <param name="str">Input Base64-string.</param>
      /// <returns>Base64-Byte-array from the Base64-string.</returns>
      public static byte[] CTFromBase64ToByteArray(this string str)
      {
         return str == null ? null : System.Convert.FromBase64String(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the value of a string to a Hex-string (with Unicode support).
      /// </summary>
      /// <param name="str">Input string.</param>
      /// <param name="addPrefix">Add "0x"-as prefix (optional, default: false).</param>
      /// <returns>String value as converted Hex-string.</returns>
      public static string CTToHex(this string str, bool addPrefix = false)
      {
         if (str == null)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         if (addPrefix)
            sb.Append("0x");

         byte[] bytes = System.Text.Encoding.Unicode.GetBytes(str);
         foreach (byte t in bytes)
         {
            sb.Append(t.ToString("X2"));
         }

         return sb.ToString(); // returns: "48656C6C6F20776F726C64" for "Hello world"
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the Hex-value of a string to a string (with Unicode support).
      /// </summary>
      /// <param name="hexString">Input as Hex-string.</param>
      /// <returns>Hex-string value as converted string.</returns>
      public static string CTHexToString(this string hexString)
      {
         if (hexString == null)
            return null;

         string _hex = hexString;

         if (_hex.StartsWith("0x"))
            _hex = _hex.Substring(2);

         if (hexString.Length % 2 != 0)
            throw new System.FormatException($"String seems to be an invalid hex-code: {hexString}");

         byte[] bytes = new byte[_hex.Length / 2];
         for (int ii = 0; ii < bytes.Length; ii++)
         {
            bytes[ii] = System.Convert.ToByte(hexString.Substring(ii * 2, 2), 16);
         }

         //return System.Text.Encoding.ASCII.GetString(bytes);
         return System.Text.Encoding.Unicode.GetString(bytes); // returns: "Hello world" for "48656C6C6F20776F726C64"
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the Hex-value of a string to a Color32.
      /// </summary>
      /// <param name="hexString">Input as Hex-string.</param>
      /// <returns>Hex-string value as Color32.</returns>
      public static Color32 CTHexToColor32(this string hexString)
      {
         if (hexString == null)
            throw new System.ArgumentNullException(nameof(hexString));

         string _hex = hexString;

         if (_hex.StartsWith("0x"))
            _hex = _hex.Substring(2);

         if (_hex.StartsWith("#"))
            _hex = _hex.Substring(1);

         /*
         Color color = Color.white;
         if (ColorUtility.TryParseHtmlString(_hex, out color))
         {
            Debug.LogWarning($"Could not convert string to color: {hexString}");
         }

         return color;
*/
         if (_hex.Length != 6 && _hex.Length != 8)
            throw new System.FormatException($"String seems to be an invalid color: {_hex}");

         byte r = System.Convert.ToByte(_hex.Substring(0, 2), 16);
         byte g = System.Convert.ToByte(_hex.Substring(2, 2), 16);
         byte b = System.Convert.ToByte(_hex.Substring(4, 2), 16);
         byte a = 0xFF;

         if (_hex.Length == 8)
            a = System.Convert.ToByte(_hex.Substring(6, 2), 16);

         Color32 color = new Color32(r, g, b, a);

         //Debug.Log("Hex orig: '" + _hex + "'");
         //Debug.Log("Color: " + color);

         return color;
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the Hex-value of a string to a Color.
      /// </summary>
      /// <param name="hexString">Input as Hex-string.</param>
      /// <returns>Hex-string value as Color.</returns>
      public static Color CTHexToColor(this string hexString)
      {
         return CTHexToColor32(hexString);
      }

      /// <summary>
      /// Extension method for strings.
      /// Converts the value of a string to a byte-array.
      /// </summary>
      /// <param name="str">Input string.</param>
      /// <param name="encoding">Encoding of the string (optional, default: UTF8).</param>
      /// <returns>Byte-array with the string.</returns>
      public static byte[] CTToByteArray(this string str, System.Text.Encoding encoding = null)
      {
         if (str == null)
            return null;

         System.Text.Encoding _encoding = System.Text.Encoding.UTF8;

         if (encoding != null)
            _encoding = encoding;

         return _encoding.GetBytes(str);
      }

      /// <summary>
      /// Extension method for strings.
      /// Cleans a given text from tags.
      /// </summary>
      /// <param name="str">Input to clean.</param>
      /// <returns>Clean text without tags.</returns>
      public static string CTClearTags(this string str)
      {
         return str != null ? Crosstales.Common.Util.BaseConstants.REGEX_CLEAN_TAGS.Replace(str, string.Empty).Trim() : null;
      }

      /// <summary>
      /// Extension method for strings.
      /// Cleans a given text from multiple spaces.
      /// </summary>
      /// <param name="str">Input to clean.</param>
      /// <returns>Clean text without multiple spaces.</returns>
      public static string CTClearSpaces(this string str)
      {
         return str != null ? Crosstales.Common.Util.BaseConstants.REGEX_CLEAN_SPACES.Replace(str, " ").Trim() : null;
      }

      /// <summary>
      /// Extension method for strings.
      /// Cleans a given text from line endings.
      /// </summary>
      /// <param name="str">Input to clean.</param>
      /// <returns>Clean text without line endings.</returns>
      public static string CTClearLineEndings(this string str)
      {
         return str != null ? Crosstales.Common.Util.BaseConstants.REGEX_LINEENDINGS.Replace(str, string.Empty).Trim() : null;
      }

      #endregion


      #region Arrays

      /// <summary>
      /// Extension method for arrays.
      /// Shuffles an array.
      /// </summary>
      /// <param name="array">Array-instance to shuffle.</param>
      /// <param name="seed">Seed for the PRNG (optional, default: 0 (=standard))</param>
      public static void CTShuffle<T>(this T[] array, int seed = 0)
      {
#if UNITY_WSA
         return;
#else
         if (array == null || array.Length <= 0)
            throw new System.ArgumentNullException(nameof(array));

         System.Random rnd = seed == 0 ? new System.Random() : new System.Random(seed);
         int n = array.Length;
         while (n > 1)
         {
            int k = rnd.Next(n--);
            (array[n], array[k]) = (array[k], array[n]);
         }
#endif
      }

      /// <summary>
      /// Extension method for arrays.
      /// Dumps an array to a string.
      /// </summary>
      /// <param name="array">Array-instance to dump.</param>
      /// <param name="prefix">Prefix for every element (optional, default: empty).</param>
      /// <param name="postfix">Postfix for every element (optional, default: empty).</param>
      /// <param name="appendNewLine">Append new line, otherwise use the given delimiter (optional, default: false).</param>
      /// <param name="delimiter">Delimiter if appendNewLine is false (optional, default: "; ").</param>
      /// <returns>String with lines for all array entries.</returns>
      public static string CTDump<T>(this T[] array, string prefix = "", string postfix = "", bool appendNewLine = true, string delimiter = "; ")
      {
         if (array == null) // || array.Length <= 0)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (T element in array)
         {
            if (0 < sb.Length)
            {
               sb.Append(appendNewLine ? System.Environment.NewLine : delimiter);
            }

            sb.Append(prefix);
            sb.Append(element);
            sb.Append(postfix);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Quaternion-arrays.
      /// Dumps an array to a string.
      /// </summary>
      /// <param name="array">Quaternion-array-instance to dump.</param>
      /// <returns>String with lines for all array entries.</returns>
      public static string CTDump(this Quaternion[] array)
      {
         if (array == null) // || array.Length <= 0)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Quaternion element in array)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
            sb.Append(", ");
            sb.Append(element.z);
            sb.Append(", ");
            sb.Append(element.w);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Vector2-arrays.
      /// Dumps an array to a string.
      /// </summary>
      /// <param name="array">Vector2-array-instance to dump.</param>
      /// <returns>String with lines for all array entries.</returns>
      public static string CTDump(this Vector2[] array)
      {
         if (array == null) // || array.Length <= 0)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Vector2 element in array)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Vector3-arrays.
      /// Dumps an array to a string.
      /// </summary>
      /// <param name="array">Vector3-array-instance to dump.</param>
      /// <returns>String with lines for all array entries.</returns>
      public static string CTDump(this Vector3[] array)
      {
         if (array == null) // || array.Length <= 0)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Vector3 element in array)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
            sb.Append(", ");
            sb.Append(element.z);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Vector4-arrays.
      /// Dumps an array to a string.
      /// </summary>
      /// <param name="array">Vector4-array-instance to dump.</param>
      /// <returns>String with lines for all array entries.</returns>
      public static string CTDump(this Vector4[] array)
      {
         if (array == null) // || array.Length <= 0)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Vector4 element in array)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
            sb.Append(", ");
            sb.Append(element.z);
            sb.Append(", ");
            sb.Append(element.w);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for arrays.
      /// Generates a string array with all entries (via ToString).
      /// </summary>
      /// <param name="array">Array-instance to ToString.</param>
      /// <returns>String array with all entries (via ToString).</returns>
      public static string[] CTToStringArray<T>(this T[] array)
      {
         if (array == null) // || array.Length <= 0)
            throw new System.ArgumentNullException(nameof(array));

         string[] result = new string[array.Length];

         for (int ii = 0; ii < array.Length; ii++)
         {
            result[ii] = null == array[ii] ? "null" : array[ii].ToString();
         }

         return result;
      }

      /// <summary>
      /// Extension method for byte-arrays.
      /// Converts a byte-array to a float-array.
      /// </summary>
      /// <param name="array">Array-instance to convert.</param>
      /// <param name="count">Number of bytes to convert (optional).</param>
      /// <returns>Converted float-array.</returns>
      public static float[] CTToFloatArray(this byte[] array, int count = 0)
      {
         if (array == null) // || array.Length <= 0)
            throw new System.ArgumentNullException(nameof(array));

         int _count = count;

         if (_count <= 0)
            _count = array.Length;

         float[] floats = new float[_count / 2];

         int ii = 0;
         for (int zz = 0; zz < _count; zz += 2)
         {
            floats[ii] = bytesToFloat(array[zz], array[zz + 1]);
            ii++;
         }

         return floats;
      }

      /// <summary>
      /// Extension method for float-arrays.
      /// Converts a float-array to a byte-array.
      /// </summary>
      /// <param name="array">Array-instance to convert.</param>
      /// <param name="count">Number of floats to convert (optional).</param>
      /// <returns>Converted byte-array.</returns>
      public static byte[] CTToByteArray(this float[] array, int count = 0)
      {
         if (array == null) // || array.Length <= 0)
            throw new System.ArgumentNullException(nameof(array));

         int _count = count;

         if (_count <= 0)
            _count = array.Length;

         byte[] bytes = new byte[_count * 2];
         int byteIndex = 0;

         for (int ii = 0; ii < _count; ii++)
         {
            short outsample = (short)(array[ii] * short.MaxValue);

            bytes[byteIndex] = (byte)(outsample & 0xff);

            bytes[byteIndex + 1] = (byte)((outsample >> 8) & 0xff);

            byteIndex += 2;
         }

         return bytes;
      }

      /// <summary>
      /// Extension method for byte-arrays.
      /// Converts a byte-array to a Texture. Supported image formats: PNG and JPG.
      /// </summary>
      /// <param name="data">byte-array-instance to convert.</param>
      /// <param name="supportTexture">Support texture to prevent possible texture garbage (optional).</param>
      /// <returns>Converted Texture.</returns>
      public static Texture2D CTToTexture(this byte[] data, Texture2D supportTexture = null)
      {
         if (data == null)
            throw new System.ArgumentNullException(nameof(data));

         Texture2D tex = supportTexture;

         if (tex == null)
            tex = new Texture2D(1, 1); // note that the size is overridden

         tex.LoadImage(data);

         return tex;
      }

      /// <summary>
      /// Extension method for byte-arrays.
      /// Converts a byte-array to a Sprite. Supported image formats: PNG and JPG.
      /// </summary>
      /// <param name="data">byte-array-instance to convert.</param>
      /// <param name="supportTexture">Support texture to prevent possible texture garbage (optional).</param>
      /// <returns>Converted Sprite.</returns>
      public static Sprite CTToSprite(this byte[] data, Texture2D supportTexture = null)
      {
         if (data == null)
            throw new System.ArgumentNullException(nameof(data));

         if (supportTexture == null)
         {
            Texture2D tex = data.CTToTexture();
            return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(tex.width / 2, tex.height / 2));
         }

         supportTexture = data.CTToTexture(supportTexture);
         return Sprite.Create(supportTexture, new Rect(0, 0, supportTexture.width, supportTexture.height), new Vector2(supportTexture.width / 2, supportTexture.height / 2));
      }

      /// <summary>
      /// Extension method for byte-arrays.
      /// Converts a byte-array to a string.
      /// </summary>
      /// <param name="data">Input string as byte-array.</param>
      /// <param name="encoding">Encoding of the string (optional, default: UTF8).</param>
      /// <returns>Byte-array with the string.</returns>
      public static string CTToString(this byte[] data, System.Text.Encoding encoding = null)
      {
         if (data == null)
            return null;

         System.Text.Encoding _encoding = encoding ?? System.Text.Encoding.UTF8;

         return _encoding.GetString(data);
      }

      /// <summary>
      /// Extension method for byte-arrays.
      /// Converts a byte-array to a Base64-string.
      /// </summary>
      /// <param name="data">Input as byte-array.</param>
      /// <returns>Base64-string from the byte-array.</returns>
      public static string CTToBase64(this byte[] data)
      {
         return data == null ? null : System.Convert.ToBase64String(data);
      }

      /// <summary>
      /// Extension method for 2D-arrays.
      /// Returns the column of a 2D-array as array.
      /// </summary>
      /// <param name="matrix">Input as 2D-array.</param>
      /// <param name="columnNumber">Desired column of the 2D-array</param>
      /// <returns>Column of a 2D-array as array.</returns>
      public static T[] GetColumn<T>(this T[,] matrix, int columnNumber)
      {
         return Enumerable.Range(0, matrix.GetLength(0)).Select(x => matrix[x, columnNumber]).ToArray();
      }

      /// <summary>
      /// Extension method for 2D-arrays.
      /// Returns the row of a 2D-array as array.
      /// </summary>
      /// <param name="matrix">Input as 2D-array.</param>
      /// <param name="columnNumber">Desired row of the 2D-array</param>
      /// <returns>Row of a 2D-array as array.</returns>
      public static T[] GetRow<T>(this T[,] matrix, int rowNumber)
      {
         return Enumerable.Range(0, matrix.GetLength(1)).Select(x => matrix[rowNumber, x]).ToArray();
      }

      #endregion


      #region Lists

      /// <summary>
      /// Extension method for IList.
      /// Shuffles a List.
      /// </summary>
      /// <param name="list">IList-instance to shuffle.</param>
      /// <param name="seed">Seed for the PRNG (optional, default: 0 (=standard))</param>
      public static void CTShuffle<T>(this System.Collections.Generic.IList<T> list, int seed = 0)
      {
#if UNITY_WSA
         return;
#else
         if (list == null)
            throw new System.ArgumentNullException(nameof(list));

         System.Random rnd = seed == 0 ? new System.Random() : new System.Random(seed);
         int n = list.Count;

         while (n > 1)
         {
            int k = rnd.Next(n--);
            (list[n], list[k]) = (list[k], list[n]);
         }
#endif
      }

      /// <summary>
      /// Extension method for IList.
      /// Dumps a list to a string.
      /// </summary>
      /// <param name="list">IList-instance to dump.</param>
      /// <param name="prefix">Prefix for every element (optional, default: empty).</param>
      /// <param name="postfix">Postfix for every element (optional, default: empty).</param>
      /// <param name="appendNewLine">Append new line, otherwise use the given delimiter (optional, default: false).</param>
      /// <param name="delimiter">Delimiter if appendNewLine is false (optional, default: "; ").</param>
      /// <returns>String with lines for all list entries.</returns>
      public static string CTDump<T>(this System.Collections.Generic.IList<T> list, string prefix = "", string postfix = "", bool appendNewLine = true, string delimiter = "; ")
      {
         if (list == null)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (T element in list)
         {
            if (0 < sb.Length)
            {
               sb.Append(appendNewLine ? System.Environment.NewLine : delimiter);
            }

            sb.Append(prefix);
            sb.Append(element);
            sb.Append(postfix);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Quaternion-IList.
      /// Dumps a list to a string.
      /// </summary>
      /// <param name="list">Quaternion-IList-instance to dump.</param>
      /// <returns>String with lines for all list entries.</returns>
      public static string CTDump(this System.Collections.Generic.IList<Quaternion> list)
      {
         if (list == null)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Quaternion element in list)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
            sb.Append(", ");
            sb.Append(element.z);
            sb.Append(", ");
            sb.Append(element.w);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Vector2-IList.
      /// Dumps a list to a string.
      /// </summary>
      /// <param name="list">Vector2-IList-instance to dump.</param>
      /// <returns>String with lines for all list entries.</returns>
      public static string CTDump(this System.Collections.Generic.IList<Vector2> list)
      {
         if (list == null)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Vector2 element in list)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Vector3-IList.
      /// Dumps a list to a string.
      /// </summary>
      /// <param name="list">Vector3-IList-instance to dump.</param>
      /// <returns>String with lines for all list entries.</returns>
      public static string CTDump(this System.Collections.Generic.IList<Vector3> list)
      {
         if (list == null)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Vector3 element in list)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
            sb.Append(", ");
            sb.Append(element.z);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for Vector4-IList.
      /// Dumps a list to a string.
      /// </summary>
      /// <param name="list">Vector4-IList-instance to dump.</param>
      /// <returns>String with lines for all list entries.</returns>
      public static string CTDump(this System.Collections.Generic.IList<Vector4> list)
      {
         if (list == null)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (Vector4 element in list)
         {
            if (0 < sb.Length)
               sb.Append(System.Environment.NewLine);

            sb.Append(element.x);
            sb.Append(", ");
            sb.Append(element.y);
            sb.Append(", ");
            sb.Append(element.z);
            sb.Append(", ");
            sb.Append(element.w);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for IList.
      /// Generates a string list with all entries (via ToString).
      /// </summary>
      /// <param name="list">IList-instance to ToString.</param>
      /// <returns>String list with all entries (via ToString).</returns>
      public static System.Collections.Generic.List<string> CTToString<T>(this System.Collections.Generic.IList<T> list)
      {
         if (list == null)
            throw new System.ArgumentNullException(nameof(list));

         System.Collections.Generic.List<string> result = new System.Collections.Generic.List<string>(list.Count);
         result.AddRange(list.Select(element => null == element ? "null" : element.ToString()));

         return result;
      }

      #endregion


      #region Dictionaries

      /// <summary>
      /// Extension method for IDictionary.
      /// Dumps a dictionary to a string.
      /// </summary>
      /// <param name="dict">IDictionary-instance to dump.</param>
      /// <param name="prefix">Prefix for every element (optional, default: empty).</param>
      /// <param name="postfix">Postfix for every element (optional, default: empty).</param>
      /// <param name="appendNewLine">Append new line, otherwise use the given delimiter (optional, default: false).</param>
      /// <param name="delimiter">Delimiter if appendNewLine is false (optional, default: "; ").</param>
      /// <returns>String with lines for all dictionary entries.</returns>
      public static string CTDump<K, V>(this System.Collections.Generic.IDictionary<K, V> dict, string prefix = "", string postfix = "", bool appendNewLine = true, string delimiter = "; ")
      {
         if (dict == null)
            return null;

         System.Text.StringBuilder sb = new System.Text.StringBuilder();

         foreach (System.Collections.Generic.KeyValuePair<K, V> kvp in dict)
         {
            if (0 < sb.Length)
            {
               sb.Append(appendNewLine ? System.Environment.NewLine : delimiter);
            }

            sb.Append(prefix);
            sb.Append("Key = ");
            sb.Append(kvp.Key);
            sb.Append(", Value = ");
            sb.Append(kvp.Value);
            sb.Append(postfix);
         }

         return sb.ToString();
      }

      /// <summary>
      /// Extension method for IDictionary.
      /// Adds a dictionary to an existing one.
      /// </summary>
      /// <param name="dict">IDictionary-instance.</param>
      /// <param name="collection">Dictionary to add.</param>
      public static void CTAddRange<K, V>(this System.Collections.Generic.IDictionary<K, V> dict, System.Collections.Generic.IDictionary<K, V> collection)
      {
         if (dict == null)
            throw new System.ArgumentNullException(nameof(dict));

         if (collection == null)
            throw new System.ArgumentNullException(nameof(collection));

         foreach (System.Collections.Generic.KeyValuePair<K, V> item in collection)
         {
            if (!dict.ContainsKey(item.Key))
            {
               dict.Add(item.Key, item.Value);
            }
            else
            {
               // handle duplicate key issue here
               Debug.LogWarning($"Duplicate key found: {item.Key}");
            }
         }
      }

      #endregion


      #region Streams

      /// <summary>
      /// Extension method for Stream.
      /// Reads the full content of a Stream.
      /// </summary>
      /// <param name="input">Stream-instance to read.</param>
      /// <returns>Byte-array of the Stream content.</returns>
      public static byte[] CTReadFully(this System.IO.Stream input)
      {
         if (input == null)
            throw new System.ArgumentNullException(nameof(input));

         using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
         {
            input.CopyTo(ms);
            return ms.ToArray();
         }
      }

      #endregion


      #region Color

      /// <summary>
      /// Extension method for Color32.
      /// Converts the value of a color to a RGB Hex-string.
      /// </summary>
      /// <param name="input">Color to convert.</param>
      /// <returns>Color value as Hex (format "RRGGBB").</returns>
      public static string CTToHexRGB(this Color32 input)
      {
         return CTToHexRGB((Color)input);
      }

      /// <summary>
      /// Extension method for Color.
      /// Converts the value of a color to a RGB Hex-string.
      /// </summary>
      /// <param name="input">Color to convert.</param>
      /// <returns>Color value as Hex (format "RRGGBB").</returns>
      public static string CTToHexRGB(this Color input)
      {
         if (input == null)
            throw new System.ArgumentNullException(nameof(input));

         string hexColor = ColorUtility.ToHtmlStringRGB(input);

         //if (hexColor.Length != 6)
         //   Debug.LogError("HEX invalid: " + hexColor);

         return hexColor;

         /*
         Color32 color = input;

         string hexColor = $"{(color.r + 0x01):X2}{(color.g + 0x01):X2}{(color.b + 0x01):X2}";

         Debug.LogWarning("HEX: " + hexColor);

         if (hexColor.Length != 6)
            Debug.LogError("HEX invalid: " + hexColor);

         return hexColor;
         */
      }

      /// <summary>
      /// Extension method for Color32.
      /// Converts the value of a color to a RGBA Hex-string.
      /// </summary>
      /// <param name="input">Color to convert.</param>
      /// <returns>Color value as Hex (format "RRGGBBAA").</returns>
      public static string CTToHexRGBA(this Color32 input)
      {
         return CTToHexRGBA((Color)input);
      }

      /// <summary>
      /// Extension method for Color.
      /// Converts the value of a color to a RGBA Hex-string.
      /// </summary>
      /// <param name="input">Color to convert.</param>
      /// <returns>Color value as Hex (format "RRGGBBAA").</returns>
      public static string CTToHexRGBA(this Color input)
      {
         if (input == null)
            throw new System.ArgumentNullException(nameof(input));

         return ColorUtility.ToHtmlStringRGBA(input);
      }

      /// <summary>
      /// Extension method for Color32.
      /// Convert it to a Vector3.
      /// </summary>
      /// <param name="color">Color-instance to convert.</param>
      /// <returns>Vector3 from color.</returns>
      public static Vector3 CTVector3(this Color32 color)
      {
         return CTVector3((Color)color);
      }

      /// <summary>
      /// Extension method for Color.
      /// Convert it to a Vector3.
      /// </summary>
      /// <param name="color">Color-instance to convert.</param>
      /// <returns>Vector3 from color.</returns>
      public static Vector3 CTVector3(this Color color)
      {
         if (color == null)
            throw new System.ArgumentNullException(nameof(color));

         return new Vector3(color.r, color.g, color.b);
      }

      /// <summary>
      /// Extension method for Color32.
      /// Convert it to a Vector4.
      /// </summary>
      /// <param name="color">Color-instance to convert.</param>
      /// <returns>Vector4 from color.</returns>
      public static Vector4 CTVector4(this Color32 color)
      {
         return CTVector4((Color)color);
      }

      /// <summary>
      /// Extension method for Color.
      /// Convert it to a Vector4.
      /// </summary>
      /// <param name="color">Color-instance to convert.</param>
      /// <returns>Vector4 from color.</returns>
      public static Vector4 CTVector4(this Color color)
      {
         if (color == null)
            throw new System.ArgumentNullException(nameof(color));

         return new Vector4(color.r, color.g, color.b, color.a);
      }

      #endregion


      #region Vector2

      /// <summary>
      /// Allows you to multiply two Vector2s together, something Unity sorely lacks by default.
      /// </summary>
      /// <param name="a">First vector</param>
      /// <param name="b">Second vector</param>
      /// <returns>The ax*bx, ay*by result.</returns>
      public static Vector2 CTMultiply(this Vector2 a, Vector2 b)
      {
         return new Vector2(a.x * b.x, a.y * b.y);
      }

      #endregion


      #region Vector3

      /// <summary>
      /// Allows you to multiply two Vector3s together, something Unity sorely lacks by default.
      /// </summary>
      /// <param name="a">First vector</param>
      /// <param name="b">Second vector</param>
      /// <returns>The ax*bx, ay*by, az*bz result.</returns>
      public static Vector3 CTMultiply(this Vector3 a, Vector3 b)
      {
         return new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
      }

      /// <summary>
      /// Returns a Vector3 with a 0 y-axis. This is useful for keeping entities oriented perpendicular to the ground.
      /// </summary>
      public static Vector3 CTFlatten(this Vector3 a)
      {
         return a.CTMultiply(FLAT_VECTOR);
      }

      /// <summary>
      /// Extension method for Vector3.
      /// Convert it to a Quaternion.
      /// </summary>
      /// <param name="eulerAngle">Vector3-instance to convert.</param>
      /// <returns>Quaternion from euler angles.</returns>
      public static Quaternion CTQuaternion(this Vector3 eulerAngle)
      {
         return Quaternion.Euler(eulerAngle);
      }

      /// <summary>
      /// Extension method for Vector3.
      /// Convert it to a Color.
      /// </summary>
      /// <param name="rgb">Vector3-instance to convert (RGB = xyz).</param>
      /// <param name="alpha">Alpha-value of the color (optional, default: 1).</param>
      /// <returns>Color from RGB.</returns>
      public static Color CTColorRGB(this Vector3 rgb, float alpha = 1f)
      {
         return new Color(Mathf.Clamp01(rgb.x), Mathf.Clamp01(rgb.y), Mathf.Clamp01(rgb.z), Mathf.Clamp01(alpha));
      }

      #endregion


      #region Vector4

      /// <summary>
      /// Allows you to multiply two Vector4s together, something Unity sorely lacks by default.
      /// </summary>
      /// <param name="a">First vector</param>
      /// <param name="b">Second vector</param>
      /// <returns>The ax*bx, ay*by, az*bz, aw*bw result.</returns>
      public static Vector4 CTMultiply(this Vector4 a, Vector4 b)
      {
         return new Vector4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);
      }

      /// <summary>
      /// Extension method for Vector4.
      /// Convert it to a Quaternion.
      /// </summary>
      /// <param name="angle">Vector4-instance to convert.</param>
      /// <returns>Quaternion from Vector4.</returns>
      public static Quaternion CTQuaternion(this Vector4 angle)
      {
         return new Quaternion(angle.x, angle.y, angle.z, angle.w);
      }

      /// <summary>
      /// Extension method for Vector4.
      /// Convert it to a Color.
      /// </summary>
      /// <param name="rgba">Vector4-instance to convert (RGBA = xyzw).</param>
      /// <returns>Color from RGBA.</returns>
      public static Color CTColorRGBA(this Vector4 rgba)
      {
         return new Color(Mathf.Clamp01(rgba.x), Mathf.Clamp01(rgba.y), Mathf.Clamp01(rgba.z), Mathf.Clamp01(rgba.w));
      }

      #endregion


      #region Quaternion

      /// <summary>
      /// Extension method for Quaternion.
      /// Convert it to a Vector3.
      /// </summary>
      /// <param name="angle">Quaternion-instance to convert.</param>
      /// <returns>Vector3 from Quaternion.</returns>
      public static Vector3 CTVector3(this Quaternion angle)
      {
         return angle.eulerAngles;
      }

      /// <summary>
      /// Extension method for Quaternion.
      /// Convert it to a Vector4.
      /// </summary>
      /// <param name="angle">Quaternion-instance to convert.</param>
      /// <returns>Vector4 from Quaternion.</returns>
      public static Vector4 CTVector4(this Quaternion angle)
      {
         return new Vector4(angle.x, angle.y, angle.z, angle.w);
      }

      #endregion


      #region Canvas

      /// <summary>
      /// Extension method for Canvas.
      /// Convert current resolution scale.
      /// </summary>
      /// <param name="canvas">Canvas to convert.</param>
      /// <returns>Vector3 with the correct scale.</returns>
      public static Vector3 CTCorrectLossyScale(this Canvas canvas)
      {
         if (canvas == null)
            throw new System.ArgumentNullException(nameof(canvas));

         if (!Application.isPlaying)
            return Vector3.one;

         if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
         {
            UnityEngine.UI.CanvasScaler scaler = canvas.GetComponent<UnityEngine.UI.CanvasScaler>();
            if (scaler && scaler.enabled)
            {
               scaler.enabled = false;
               Vector3 before = canvas.GetComponent<RectTransform>().lossyScale;
               scaler.enabled = true;
               Vector3 after = canvas.GetComponent<RectTransform>().lossyScale;

               return new Vector3(after.x / before.x, after.y / before.y, after.z / before.z);
            }

            return Vector3.one;
         }

         return canvas.GetComponent<RectTransform>().lossyScale;
      }

      #endregion


      #region RectTransform

      /// <summary>
      /// Extension method for RectTransform.
      /// Sets the local corners of a RectTransform to a given array.
      /// </summary>
      /// <param name="transform">RectTransform-instance.</param>
      /// <param name="fourCornersArray">Corners for the RectTransform.</param>
      /// <param name="canvas">Relevant canvas.</param>
      /// <param name="inset">Inset from the corners (optional, default: 0).</param>
      /// <param name="corrected">Automatically adjust scaling (optional, default: false).</param>
      public static void CTGetLocalCorners(this RectTransform transform, Vector3[] fourCornersArray, Canvas canvas, float inset = 0, bool corrected = false)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         if (fourCornersArray == null)
            throw new System.ArgumentNullException(nameof(fourCornersArray));

         if (canvas == null)
            throw new System.ArgumentNullException(nameof(canvas));

         transform.GetLocalCorners(fourCornersArray);

         if (corrected)
         {
            Vector3 uis = canvas.CTCorrectLossyScale();
            fourCornersArray[0].x /= uis.x;
            fourCornersArray[0].y /= uis.y;
            fourCornersArray[1].x /= uis.x;
            fourCornersArray[1].y /= uis.y;
            fourCornersArray[2].x /= uis.x;
            fourCornersArray[2].y /= uis.y;
            fourCornersArray[3].x /= uis.x;
            fourCornersArray[3].y /= uis.y;
         }

         if (inset != 0)
         {
            Vector3 uis = canvas.CTCorrectLossyScale();
            fourCornersArray[0].x += inset * uis.x;
            fourCornersArray[0].y += inset * uis.y;
            fourCornersArray[1].x += inset * uis.x;
            fourCornersArray[1].y -= inset * uis.y;
            fourCornersArray[2].x -= inset * uis.x;
            fourCornersArray[2].y -= inset * uis.y;
            fourCornersArray[3].x -= inset * uis.x;
            fourCornersArray[3].y += inset * uis.y;
         }
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Returns the local corners of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform-instance.</param>
      /// <param name="canvas">Relevant canvas.</param>
      /// <param name="inset">Inset from the corners (optional, default: 0).</param>
      /// <param name="corrected">Automatically adjust scaling (optional, default: false).</param>
      /// <returns>Array of the four local corners of the RectTransform.</returns>
      public static Vector3[] CTGetLocalCorners(this RectTransform transform, Canvas canvas, float inset = 0, bool corrected = false)
      {
         Vector3[] fourCornersArray = new Vector3[4];

         CTGetLocalCorners(transform, fourCornersArray, canvas, inset, corrected);

         return fourCornersArray;
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Sets the world corners of a RectTransform to a given array.
      /// </summary>
      /// <param name="transform">RectTransform-instance.</param>
      /// <param name="fourCornersArray">Corners for the RectTransform.</param>
      /// <param name="canvas">Relevant canvas.</param>
      /// <param name="inset">Inset from the corners (optional, default: 0).</param>
      /// <param name="corrected">Automatically adjust scaling (optional, default: false).</param>
      public static void CTGetScreenCorners(this RectTransform transform, Vector3[] fourCornersArray, Canvas canvas, float inset = 0, bool corrected = false)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         if (fourCornersArray == null)
            throw new System.ArgumentNullException(nameof(fourCornersArray));

         if (canvas == null)
            throw new System.ArgumentNullException(nameof(canvas));

         // if screen space overlay mode then world corners are already in screen space
         // if screen space camera mode then screen settings are in world and need to be converted to screen
         transform.GetWorldCorners(fourCornersArray);

         if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
         {
            for (int ii = 0; ii < 4; ii++)
            {
               fourCornersArray[ii] = canvas.worldCamera.WorldToScreenPoint(fourCornersArray[ii]);
               fourCornersArray[ii].z = 0;
            }
         }

         if (corrected)
         {
            Vector3 uis = canvas.CTCorrectLossyScale();
            fourCornersArray[0].x /= uis.x;
            fourCornersArray[0].y /= uis.y;
            fourCornersArray[1].x /= uis.x;
            fourCornersArray[1].y /= uis.y;
            fourCornersArray[2].x /= uis.x;
            fourCornersArray[2].y /= uis.y;
            fourCornersArray[3].x /= uis.x;
            fourCornersArray[3].y /= uis.y;
         }

         if (inset != 0)
         {
            Vector3 uis = canvas.CTCorrectLossyScale();
            fourCornersArray[0].x += inset * uis.x;
            fourCornersArray[0].y += inset * uis.y;
            fourCornersArray[1].x += inset * uis.x;
            fourCornersArray[1].y -= inset * uis.y;
            fourCornersArray[2].x -= inset * uis.x;
            fourCornersArray[2].y -= inset * uis.y;
            fourCornersArray[3].x -= inset * uis.x;
            fourCornersArray[3].y += inset * uis.y;
         }
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Returns the screen (world) corners of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform-instance.</param>
      /// <param name="canvas">Relevant canvas.</param>
      /// <param name="inset">Inset from the corners (optional, default: 0).</param>
      /// <param name="corrected">Automatically adjust scaling (optional, default: false).</param>
      /// <returns>Array of the four screen (world) corners of the RectTransform.</returns>
      public static Vector3[] CTGetScreenCorners(this RectTransform transform, Canvas canvas, float inset = 0, bool corrected = false)
      {
         Vector3[] fourCornersArray = new Vector3[4];

         CTGetScreenCorners(transform, fourCornersArray, canvas, inset, corrected);

         return fourCornersArray;
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Returns the bounds of a RectTransform including the children.
      /// </summary>
      /// <param name="transform">RectTransform to calculate the bounds.</param>
      /// <param name="uiScaleFactor">Scale of the UI (optional, default: 1.0).</param>
      /// <returns>Bounds of the RectTransform.</returns>
      public static Bounds CTGetBounds(this RectTransform transform, float uiScaleFactor = 1f)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         //Debug.Log($"Root: {transform.anchoredPosition}");
         Rect rect;
         Bounds bounds = new Bounds(transform.anchoredPosition, new Vector3((rect = transform.rect).width, rect.height, 0.0f) * uiScaleFactor);

         if (transform.childCount > 0)
         {
            foreach (Bounds childBounds in from RectTransform child in transform select new Bounds(child.anchoredPosition, new Vector3(child.rect.width, child.rect.height, 0.0f) * uiScaleFactor))
            {
               bounds.Encapsulate(childBounds);
            }
         }

         return bounds;
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Sets the Left-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to set the Left-property.</param>
      /// <param name="value">Value for the Left-property.</param>
      public static void CTSetLeft(this RectTransform transform, float value)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         transform.offsetMin = new Vector2(value, transform.offsetMin.y);
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Sets the Right-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to set the Right-property.</param>
      /// <param name="value">Value for the Right-property.</param>
      public static void CTSetRight(this RectTransform transform, float value)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         transform.offsetMax = new Vector2(value, transform.offsetMax.y);
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Sets the Top-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to set the Top-property.</param>
      /// <param name="value">Value for the Top-property.</param>
      public static void CTSetTop(this RectTransform transform, float value)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         transform.offsetMax = new Vector2(transform.offsetMax.x, value);
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Sets the Bottom-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to set the Bottom-property.</param>
      /// <param name="value">Value for the Bottom-property.</param>
      public static void CTSetBottom(this RectTransform transform, float value)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         transform.offsetMin = new Vector2(transform.offsetMin.x, value);
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Gets the Left-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to get the Left-property.</param>
      /// <returns>Left-property of the RectTransform.</returns>
      public static float CTGetLeft(this RectTransform transform)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         return transform.offsetMin.x;
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Gets the Right-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to get the Right-property.</param>
      /// <returns>Right-property of the RectTransform.</returns>
      public static float CTGetRight(this RectTransform transform)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         return transform.offsetMax.x;
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Gets the Top-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to get the Top-property.</param>
      /// <returns>Top-property of the RectTransform.</returns>
      public static float CTGetTop(this RectTransform transform)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         return transform.offsetMax.y;
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Gets the Bottom-property of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to get the Bottom-property.</param>
      /// <returns>Bottom-property of the RectTransform.</returns>
      public static float CTGetBottom(this RectTransform transform)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         return transform.offsetMin.y;
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Gets the Left/Right/Top/Bottom-properties of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to get the Left/Right/Top/Bottom-properties.</param>
      /// <returns>Left/Right/Top/Bottom-properties of the RectTransform as Vector4.</returns>
      public static Vector4 CTGetLRTB(this RectTransform transform)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         Vector2 offsetMax;
         return new Vector4(transform.offsetMin.x, (offsetMax = transform.offsetMax).x, offsetMax.y, transform.offsetMin.y);
      }

      /// <summary>
      /// Extension method for RectTransform.
      /// Sets the Left/Right/Top/Bottom-properties of a RectTransform.
      /// </summary>
      /// <param name="transform">RectTransform to set the Left/Right/Top/Bottom-properties.</param>
      /// <param name="lrtb">Left/Right/Top/Bottom-properties as Vector4.</param>
      public static void CTSetLRTB(this RectTransform transform, Vector4 lrtb)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         transform.offsetMin = new Vector2(lrtb.x, lrtb.w);
         transform.offsetMax = new Vector2(lrtb.y, lrtb.z);
      }

      #endregion


      #region Component

      /// <summary>
      /// Extension method for Component.
      /// Recursively searches all children of a parent Component for specific named GameObjects
      /// </summary>
      /// <param name="component">Parent of the current children.</param>
      /// <param name="name">Name of the GameObject.</param>
      /// <param name="maxDepth">Maximal depth of the search (default 0, optional).</param>
      /// <returns>List of GameObjects with the given name or empty list.</returns>
      public static System.Collections.Generic.List<GameObject> CTFindAll(this Component component, string name, int maxDepth = 0)
      {
         if (component == null)
            throw new System.ArgumentNullException(nameof(component));

         if (name == null)
            throw new System.ArgumentNullException(nameof(name));

         System.Collections.Generic.List<GameObject> children = new System.Collections.Generic.List<GameObject>();
         System.Collections.Generic.List<Transform> childrenTf = getAllChildren(component.transform, maxDepth);

         foreach (var child in childrenTf)
         {
            children.Add(child.gameObject);
         }

         return children.Where(child => child.name == name).ToList();
      }


      /// <summary>
      /// Extension method for Component.
      /// Recursively searches all children of a parent Component for specific named GameObjects
      /// </summary>
      /// <param name="component">Parent of the current children.</param>
      /// <param name="name">Name of the GameObject.</param>
      /// <returns>List of GameObjects with the given name or empty list.</returns>
      public static System.Collections.Generic.List<T> CTFindAll<T>(this Component component, string name) where T : Component
      {
         if (component == null)
            throw new System.ArgumentNullException(nameof(component));

         if (name == null)
            throw new System.ArgumentNullException(nameof(name));

         T[] children = component.GetComponentsInChildren<T>();

         return children.Where(child => child.name == name).ToList();
      }

      #endregion


      #region MonoBehaviour

      /// <summary>
      /// Extension method for MonoBehaviour.
      /// Recursively searches all children of a parent MonoBehaviour for specific named GameObject
      /// </summary>
      /// <param name="mb">Parent of the current children.</param>
      /// <param name="name">Name of the GameObject.</param>
      /// <returns>GameObject with the given name or null.</returns>
      public static GameObject CTFind(this MonoBehaviour mb, string name)
      {
         if (mb == null)
            throw new System.ArgumentNullException(nameof(mb));

         return mb.transform.CTFind(name).gameObject;
      }

      /// <summary>
      /// Extension method for MonoBehaviour.
      /// Recursively searches all children of a parent MonoBehaviour for specific named GameObject and returns a component.
      /// </summary>
      /// <param name="mb">Parent of the current children.</param>
      /// <param name="name">Name of the GameObject.</param>
      /// <returns>Component with the given type or null.</returns>
      public static T CTFind<T>(this MonoBehaviour mb, string name)
      {
         if (mb == null)
            throw new System.ArgumentNullException(nameof(mb));

         return mb.transform.CTFind<T>(name);
      }

      #endregion


      #region GameObject

      /// <summary>
      /// Extension method for GameObject.
      /// Recursively searches all children of a parent GameObject for specific named GameObject
      /// </summary>
      /// <param name="go">Parent of the current children.</param>
      /// <param name="name">Name of the GameObject.</param>
      /// <returns>GameObject with the given name or null.</returns>
      public static GameObject CTFind(this GameObject go, string name)
      {
         if (go == null)
            throw new System.ArgumentNullException(nameof(go));

         return go.transform.CTFind(name).gameObject;
      }

      /// <summary>
      /// Extension method for GameObject.
      /// Recursively searches all children of a parent GameObject for specific named GameObject and returns a component.
      /// </summary>
      /// <param name="go">Parent of the current children.</param>
      /// <param name="name">Name of the GameObject.</param>
      /// <returns>Component with the given type or null.</returns>
      public static T CTFind<T>(this GameObject go, string name)
      {
         if (go == null)
            throw new System.ArgumentNullException(nameof(go));

         return go.transform.CTFind<T>(name);
      }

      /// <summary>
      /// Extension method for GameObject.
      /// Returns the bounds of a GameObject including the children.
      /// </summary>
      /// <param name="go">GameObject to calculate the bounds.</param>
      /// <returns>Bounds of the GameObject.</returns>
      public static Bounds CTGetBounds(this GameObject go)
      {
         if (go == null)
            throw new System.ArgumentNullException(nameof(go));

         Renderer[] renderers = go.GetComponentsInChildren<Renderer>();

         if (renderers.Length == 0)
            return new Bounds(go.transform.position, Vector3.zero);

         Bounds b = renderers[0].bounds;
         foreach (Renderer r in renderers)
         {
            b.Encapsulate(r.bounds);
         }

         return b;
      }

      #endregion


      #region Transform

      /// <summary>
      /// Extension method for Transform.
      /// Recursively searches all children of a parent transform for specific named transform
      /// </summary>
      /// <param name="transform">Parent of the current children.</param>
      /// <param name="name">Name of the transform.</param>
      /// <returns>Transform with the given name or null.</returns>
      public static Transform CTFind(this Transform transform, string name)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         if (name == null)
            throw new System.ArgumentNullException(nameof(name));

         return deepSearch(transform, name);
      }

      /// <summary>
      /// Extension method for Transform.
      /// Recursively searches all children of a parent transform for specific named transform and returns a component.
      /// </summary>
      /// <param name="transform">Parent of the current children.</param>
      /// <param name="name">Name of the transform.</param>
      /// <returns>Component with the given type or null.</returns>
      public static T CTFind<T>(this Transform transform, string name)
      {
         if (transform == null)
            throw new System.ArgumentNullException(nameof(transform));

         Transform tf = transform.CTFind(name);

         return tf != null ? tf.gameObject.GetComponent<T>() : default;
      }

      #endregion


      #region Sprite

      /// <summary>
      /// Extension method for Sprite.
      /// Converts a Sprite to a PNG byte-array.
      /// </summary>
      /// <param name="sprite">Sprite to convert.</param>
      /// <returns>Converted Sprite as PNG byte-array.</returns>
      public static byte[] CTToPNG(this Sprite sprite)
      {
         if (sprite == null)
            throw new System.ArgumentNullException(nameof(sprite));

         return sprite.texture.CTToPNG();
      }

      /// <summary>
      /// Extension method for Sprite.
      /// Converts a Sprite to a JPG byte-array.
      /// </summary>
      /// <param name="sprite">Sprite to convert.</param>
      /// <returns>Converted Sprite as JPG byte-array.</returns>
      public static byte[] CTToJPG(this Sprite sprite)
      {
         if (sprite == null)
            throw new System.ArgumentNullException(nameof(sprite));

         return sprite.texture.CTToJPG();
      }

      /// <summary>
      /// Extension method for Sprite.
      /// Converts a Sprite to a TGA byte-array.
      /// </summary>
      /// <param name="sprite">Sprite to convert.</param>
      /// <returns>Converted Sprite as TGA byte-array.</returns>
      public static byte[] CTToTGA(this Sprite sprite)
      {
         if (sprite == null)
            throw new System.ArgumentNullException(nameof(sprite));

         return sprite.texture.CTToTGA();
      }

      /// <summary>
      /// Extension method for Sprite.
      /// Converts a Sprite to a EXR byte-array.
      /// </summary>
      /// <param name="sprite">Sprite to convert.</param>
      /// <returns>Converted Sprite as EXR byte-array.</returns>
      public static byte[] CTToEXR(this Sprite sprite)
      {
         if (sprite == null)
            throw new System.ArgumentNullException(nameof(sprite));

         return sprite.texture.CTToEXR();
      }

      #endregion


      #region Texture

      /// <summary>
      /// Extension method for Texture.
      /// Converts a Texture to a PNG byte-array.
      /// </summary>
      /// <param name="texture">Texture to convert.</param>
      /// <returns>Converted Texture as PNG byte-array.</returns>
      public static byte[] CTToPNG(this Texture2D texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         return texture.EncodeToPNG();
      }

      /// <summary>
      /// Extension method for Texture.
      /// Converts a Texture to a JPG byte-array.
      /// </summary>
      /// <param name="texture">Texture to convert.</param>
      /// <returns>Converted Texture as JPG byte-array.</returns>
      public static byte[] CTToJPG(this Texture2D texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         return texture.EncodeToJPG();
      }

      /// <summary>
      /// Extension method for Texture.
      /// Converts a Texture to a TGA byte-array.
      /// </summary>
      /// <param name="texture">Texture to convert.</param>
      /// <returns>Converted Texture as TGA byte-array.</returns>
      public static byte[] CTToTGA(this Texture2D texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         return texture.EncodeToTGA();
      }

      /// <summary>
      /// Extension method for Texture.
      /// Converts a Texture to a EXR byte-array.
      /// </summary>
      /// <param name="texture">Texture to convert.</param>
      /// <returns>Converted Texture as EXR byte-array.</returns>
      public static byte[] CTToEXR(this Texture2D texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         return texture.EncodeToEXR();
      }

      /// <summary>
      /// Extension method for Texture.
      /// Converts a Texture to a Sprite.
      /// </summary>
      /// <param name="texture">Texture to convert.</param>
      /// <param name="pixelsPerUnit">Pixels per unit for the Sprite (optional, default: 100).</param>
      /// <returns>Converted Texture as Sprite.</returns>
      public static Sprite CTToSprite(this Texture2D texture, float pixelsPerUnit = 100f)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         return Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
      }

      /// <summary>
      /// Extension method for Texture.
      /// Rotates a Texture by 90 degrees.
      /// </summary>
      /// <param name="texture">Texture to rotate.</param>
      /// <returns>Rotated Texture.</returns>
      public static Texture2D CTRotate90(this Texture2D texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         Color32[] origpix = texture.GetPixels32(0);
         Color32[] newpix = new Color32[texture.width * texture.height];

         for (int cc = 0; cc < texture.height; cc++)
         {
            for (int rr = 0; rr < texture.width; rr++)
            {
               newpix[texture.width * texture.height - (texture.height * rr + texture.height) + cc] =
                  origpix[texture.width * texture.height - (texture.width * cc + texture.width) + rr];
            }
         }

         Texture2D newtex = new Texture2D(texture.height, texture.width, texture.format, false);
         newtex.SetPixels32(newpix, 0);
         newtex.Apply();

         return newtex;
      }

      /// <summary>
      /// Extension method for Texture.
      /// Rotates a Texture by 180 degrees.
      /// </summary>
      /// <param name="texture">Texture to rotate.</param>
      /// <returns>Rotated Texture.</returns>
      public static Texture2D CTRotate180(this Texture2D texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         Color32[] origpix = texture.GetPixels32(0);
         Color32[] newpix = new Color32[texture.width * texture.height];

         for (int ii = 0; ii < origpix.Length; ii++)
         {
            newpix[origpix.Length - ii - 1] = origpix[ii];
         }

         Texture2D newtex = new Texture2D(texture.width, texture.height, texture.format, false);
         newtex.SetPixels32(newpix, 0);
         newtex.Apply();

         return newtex;
      }

      /// <summary>
      /// Extension method for Texture.
      /// Rotates a Texture by 270 degrees.
      /// </summary>
      /// <param name="texture">Texture to rotate.</param>
      /// <returns>Rotated Texture.</returns>
      public static Texture2D CTRotate270(this Texture2D texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         Color32[] origpix = texture.GetPixels32(0);
         Color32[] newpix = new Color32[texture.width * texture.height];

         int ii = 0;
         for (int cc = 0; cc < texture.height; cc++)
         {
            for (int rr = 0; rr < texture.width; rr++)
            {
               newpix[texture.width * texture.height - (texture.height * rr + texture.height) + cc] = origpix[ii];
               ii++;
            }
         }

         Texture2D newtex = new Texture2D(texture.height, texture.width, texture.format, false);
         newtex.SetPixels32(newpix, 0);
         newtex.Apply();

         return newtex;
      }


      /// <summary>
      /// Extension method for Texture.
      /// Convert a Texture to a Texture2D
      /// </summary>
      /// <param name="texture">Texture to convert.</param>
      /// <returns>Converted Texture2D.</returns>
      public static Texture2D CTToTexture2D(this Texture texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         return Texture2D.CreateExternalTexture(
            texture.width,
            texture.height,
            TextureFormat.RGB24,
            false, false,
            texture.GetNativeTexturePtr());
      }
#if CT_WEBCAM
      /// <summary>
      /// Extension method for WebCamTexture.
      /// Convert a WebCamTexture to a Texture2D
      /// </summary>
      /// <param name="texture">WebCamTexture to convert.</param>
      /// <returns>Converted Texture2D.</returns>
      public static Texture2D CTToTexture2D(this WebCamTexture texture)
      {
         if (texture == null)
            throw new System.ArgumentNullException(nameof(texture));

         Texture2D texture2D = new Texture2D(texture.width, texture.height);

         if (texture.isPlaying)
         {
            Color32[] data = new Color32[texture.width * texture.height];
            texture.GetPixels32(data);

            texture2D.SetPixels32(data, 0);
            texture2D.Apply();
         }

         return texture2D;
      }
#endif
      /// <summary>
      /// Extension method for Texture.
      /// Flips a Texture2D horizontally
      /// </summary>
      /// <param name="texture">Texture to flip.</param>
      /// <returns>Horizontally flipped Texture2D.</returns>
      public static Texture2D CTFlipHorizontal(this Texture2D texture)
      {
         Texture2D flipped = new Texture2D(texture.width, texture.height);

         int width = texture.width;
         int height = texture.height;


         for (int xx = 0; xx < width; xx++)
         {
            for (int yy = 0; yy < height; yy++)
            {
               flipped.SetPixel(width - xx - 1, yy, texture.GetPixel(xx, yy));
            }
         }

         flipped.Apply();

         return flipped;
      }

      /// <summary>
      /// Extension method for Texture.
      /// Flips a Texture2D vertically
      /// </summary>
      /// <param name="texture">Texture to flip.</param>
      /// <returns>Vertically flipped Texture2D.</returns>
      public static Texture2D CTFlipVertical(this Texture2D texture)
      {
         Texture2D flipped = new Texture2D(texture.width, texture.height);

         int width = texture.width;
         int height = texture.height;

         for (int xx = 0; xx < width; xx++)
         {
            for (int yy = 0; yy < height; yy++)
            {
               flipped.SetPixel(xx, height - yy - 1, texture.GetPixel(xx, yy));
            }
         }

         flipped.Apply();

         return flipped;
      }

      #endregion


      #region AudioSource

      /// <summary>
      /// Extension method for AudioSource.
      /// Determines if an AudioSource has an active clip.
      /// </summary>
      /// <param name="source">AudioSource to check.</param>
      /// <returns>True if the AudioSource has an active clip.</returns>
      public static bool CTHasActiveClip(this AudioSource source)
      {
         if (source == null)
            return false;

         if (source.clip == null)
            return false;

         bool loop;
         int timeSamples;

         return (source.isPlaying ||
                 (loop = source.loop) ||
                 (!loop && (timeSamples = source.timeSamples) > 0 && timeSamples < source.clip.samples - 1024));
      }

      #endregion


      #region C# specific

#if (!UNITY_WSA && !UNITY_WEBGL && !UNITY_XBOXONE) || UNITY_EDITOR
      /// <summary>
      /// Extension method for Thread.
      /// Aborts a Thread safely and optional silently
      /// </summary>
      /// <param name="thread">Thread to abort.</param>
      /// <param name="silent">Silently abort the Thread (optional, default: true).</param>
      public static void CTAbort(this System.Threading.Thread thread, bool silent = true)
      {
         if (thread == null)
            return;

         if (thread.IsAlive)
         {
            try
            {
               thread.Abort();
            }
            catch (System.Exception ex)
            {
               if (!silent)
                  Debug.LogWarning(ex);
            }
         }
      }
#endif

      #endregion

      #region Unity specific

      /// <summary>
      /// Extension method for Renderer.
      /// Determines if the renderer is visible from a certain camera.
      /// </summary>
      /// <param name="renderer">Renderer to test the visibility.</param>
      /// <param name="camera">Camera for the test.</param>
      /// <returns>True if the renderer is visible by the given camera.</returns>
      public static bool CTIsVisibleFrom(this Renderer renderer, Camera camera)
      {
         if (renderer == null)
            throw new System.ArgumentNullException(nameof(renderer));

         if (camera == null)
            throw new System.ArgumentNullException(nameof(camera));

         Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera);
         return GeometryUtility.TestPlanesAABB(planes, renderer.bounds);
      }

      #endregion


      #region Private methods

      private static Transform deepSearch(Transform parent, string name)
      {
         Transform tf = parent.Find(name);

         if (tf != null)
            return tf;

         foreach (Transform child in parent)
         {
            tf = deepSearch(child, name);
            if (tf != null)
               return tf;
         }

         return null;
      }

      private static System.Collections.Generic.List<Transform> getAllChildren(this Transform parent, int maxDepth = 0, System.Collections.Generic.List<Transform> transformList = null, int depth = 0)
      {
         if (transformList == null) transformList = new System.Collections.Generic.List<Transform>();

         if (maxDepth != 0)
            depth++;

         if (depth <= maxDepth)
         {
            foreach (Transform child in parent)
            {
               transformList.Add(child);
               child.getAllChildren(maxDepth, transformList, depth);
            }
         }

         return transformList;
      }

      private static float bytesToFloat(byte firstByte, byte secondByte)
      {
         // convert two bytes to one short (little endian) and convert it to range from -1 to (just below) 1
         return (short)((secondByte << 8) | firstByte) / Crosstales.Common.Util.BaseConstants.FLOAT_32768;
      }

      #endregion

      /*
      /// <summary>
      /// Perform a deep Copy of the object.
      /// </summary>
      /// <typeparam name="T">The type of object being copied.</typeparam>
      /// <param name="source">The object instance to copy.</param>
      /// <returns>The copied object.</returns>
      public static T Clone<T>(this T source)
      {
          if (!typeof(T).IsSerializable)
          {
              throw new ArgumentException("The type must be serializable.", "source");
          }

          // Don't serialize a null object, simply return the default for that object
          if (Object.ReferenceEquals(source, null))
          {
              return default(T);
          }

          IFormatter formatter = new BinaryFormatter();
          Stream stream = new MemoryStream();
          using (stream)
          {
              formatter.Serialize(stream, source);
              stream.Seek(0, SeekOrigin.Begin);
              return (T)formatter.Deserialize(stream);
          }
      }
      */
      /*
          /// <summary>
          /// Clone a List with elememts containing a copy constructor.
          /// </summary>
          /// <param name="list">List-instance to clone.</param>
          /// <returns>Clones list.</returns>
          public static List<T> CTClone<T>(this List<T> listToClone) where T : ICopyable
          {
              List<T> newList = new List<T>(listToClone.Count);
  
              listToClone.ForEach((item) =>
              {
                  newList.Add(new T(item));
              });
  
              return newList;
  
              //return listToClone.Select(item => (T)item.Clone()).ToList();
          }
        */

      /*
      public static string[] CTToUppercase(string[] array)
      {
          if (array == null || array.Length <= 0)
              throw new ArgumentNullException("array");

          string[] result = new string[array.Length];

          for (int ii = 0; ii < array.Length; ii++)
          {
              result[ii] = array[ii].ToUpper();
          }

          return result;
      }

      public static string[] CTToLowercase(string[] array)
      {
          if (array == null || array.Length <= 0)
              throw new ArgumentNullException("array");

          string[] result = new string[array.Length];

          for (int ii = 0; ii < array.Length; ii++)
          {
              result[ii] = array[ii].ToLower();
          }

          return result;
      }
  */
   }
}
// © 2016-2024 crosstales LLC (https://www.crosstales.com)