using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rest.Net
{
    public class RouteMatcher
    {
        /// <summary>
        /// Check to see if a url matches a given route syntax.
        /// * = any path segment will match
        /// ** = anything from this point forward is accepted
        /// {} = route parameter
        /// ! = must not match
        /// || = logical or
        /// && = logical and
        /// 
        /// Note: 
        ///     If route parameter doesn't end with /, then the parameter will be the rest of the url.
        /// Example:
        /// </summary>
        /// <param name="url"></param>
        /// <param name="syntax"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static bool Matches(string url, string syntax, out string[] arguments)
        {
            //Set the arguments to null, initially.
            arguments = null;

            //Trim the url and the syntax.
            url = url.Trim();
            syntax = syntax.Trim();

            //Split the url and the syntax into path segments
            var urlSegments = url.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();
            var syntaxSegments = syntax.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();

            //If we have no syntax segments this is not a match.
            if (syntaxSegments.Length == 0)
                return false;

            //If we have segments and the url does not then this may not be a match.
            if (urlSegments.Length == 0 && syntaxSegments[0] == "**")
                return true;
            else if (urlSegments.Length == 0 && syntaxSegments.Length == 1 && syntaxSegments[0] == "*")
                return true;

            //Count the number of arguments in the syntax and set it.
            arguments = new string[syntax.Count('{')];
            int argumentIndex = 0;

            //Check each segment against the url.
            var max = Math.Min(urlSegments.Length, syntaxSegments.Length);
            for (int i = 0; i < max; i++)
            {
                var syntaxSegment = syntaxSegments[i];

                if (syntaxSegment == "**")
                {
                    return true;
                }
                else if (syntaxSegment.StartsWith("{") && syntaxSegment.EndsWith("}") && i + 1 >= syntaxSegments.Length && syntax.EndsWith("/") == false)
                {
                    //Special case, syntax ends with a parameter, so recombine the rest of the url and add it as a parameter.
                    var key = syntaxSegment.Substring(1, syntaxSegment.Length - 2);
                    var builder = new StringBuilder();

                    for (int i2 = i; i2 < urlSegments.Length; i2++)
                        builder.Append(urlSegments[i2]).Append(i2 + 1 < urlSegments.Length ? "/" : "");

                    arguments[argumentIndex++] = builder.ToString();

                    return true;
                }
                else if (SegmentMatches(urlSegments[i], syntaxSegment, arguments, ref argumentIndex) == false)
                {
                    return false;
                }
            }

            if (urlSegments.Length > syntaxSegments.Length)
                return false;
            else if (syntaxSegments.Length > urlSegments.Length)
                return false;
            else
                return true;
        }

        private static bool SegmentMatches(string segment, string syntax, string[] arguments, ref int argumentIndex)
        {
            //Split the syntax into conditions
            string[] conditions = syntax.Split(' ').Where(condition => condition.Length > 0).ToArray();

            //Based off the matches, see if the segment matches.
            bool match = false;
            for (int i = 0; i < conditions.Length; i++)
            {
                var condition = conditions[i];

                if (condition == "*")
                {
                    match = true;
                }
                else if (condition == "**")
                {
                    return true;
                }
                else if (condition.StartsWith("!"))
                {
                    if (condition.Substring(1) == segment)
                        match = false;
                    else
                        match = true;
                }
                else if (condition.StartsWith("{") && condition.EndsWith("}"))
                {
                    var key = condition.Substring(1, condition.Length - 2);

                    arguments[argumentIndex++] = segment;

                    match = true;
                }
                else if (condition == "&&")
                {
                    if (match == false)
                        return false;
                }
                else if (condition == "||")
                {
                    if (match)
                        return true;
                }
                else if (condition == segment)
                {
                    return true;
                }
            }

            return match;
        }
    }
}
