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

namespace Wayne.Lib
{
    /// <summary>
    /// A simple string pattern matching used for message bus and actor system topics and addresses.
    /// It is used to match a address to a pattern.
    /// </summary>
    public static class Any
    {
        public static int Id = int.MinValue;
        public static string IdReplacement = "$";
        public static string String = "*";
        public static string Wildcard = "#";

        public static bool IsResolvedAddress(string address)
        {
            return !address.Contains(Any.String);
        }

        public static string Convert(int id)
        {
            if (id == Id)
            {
                return IdReplacement;
            }
            return id.ToString(System.Globalization.CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pattern"></param>
        /// <param name="address"></param>
        /// <returns></returns>
        public static int[] GetIds(string pattern, string address)
        {
            ParamGuard.AssertIsNotNull(pattern, "pattern");
            ParamGuard.AssertIsNotNull(address, "address");

            string[] actorAddressSplit = address.Split('.');
            string[] actorAddressPatternSplit = pattern.Split('.');

            if (actorAddressSplit.Length != actorAddressPatternSplit.Length)
            {
                return new int[0];
            }
            List<int> result = new List<int>();
            for (int i = 0; i < actorAddressSplit.Length; i++)
            {
                string actualElement = actorAddressSplit[i];
                string patternElement = actorAddressPatternSplit[i];

                if (actualElement != patternElement)
                {
                    if (patternElement == "$")
                    {
                        int intResult;
                        if (TryParseInt(actualElement, out intResult))
                        {
                            result.Add(intResult);
                        }
                    }
                }
            }
            return result.ToArray();
        }

        /// <summary>
        /// Tries to parse an integer just like the full framework's int.TryParse()
        /// </summary>
        /// <param name="possibleInt">String containing integer</param>
        /// <param name="i">Contains the integer values if the operation is successful</param>
        /// <returns>True if the parse was successful, false otherwise</returns>
        private static bool TryParseInt(string possibleInt, out int i)
        {
            if (!string.IsNullOrEmpty(possibleInt))
            {
                try
                {
                    if (possibleInt.All(char.IsNumber))
                    {
                        i = int.Parse(possibleInt);
                        return true;
                    }
                }
                catch (FormatException)
                { }

            }
            i = 0;
            return false;
        }

        public static string Convert(string val)
        {
            if (val == String)
                return String;
            return val;
        }

        /// <summary>
        /// Address pattern format:
        ///  * = anything within '.' characters
        ///  # = anything
        ///  $ = numbers
        /// </summary>
        /// <param name="address"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        public static bool Match(string address, string pattern)
        {
            ParamGuard.AssertIsNotNull(address, "address");
            ParamGuard.AssertIsNotNull(pattern, "pattern");

            if (pattern == Wildcard)
            {
                return true;
            }

            string[] actorAddressSplit = address.Split('.');
            string[] actorAddressPatternSplit = pattern.Split('.');

            if (actorAddressSplit.Length != actorAddressPatternSplit.Length)
            {
                return false;
            }

            for (int i = 0; i < actorAddressSplit.Length; i++)
            {
                string actualElement = actorAddressSplit[i];
                string patternElement = actorAddressPatternSplit[i];

                if (actualElement != patternElement)
                {
                    switch (patternElement)
                    {
                        case "*":
                            continue;
                        case "$":
                            if (!actualElement.All(Char.IsNumber))
                            {
                                return false;
                            }
                            continue;
                        default:
                            return false;
                    }
                }
            }

            return true;
        }
    }
}