/// <summary>
 /// Returns the steam controller routes for the provided <paramref name="controller"/>.
 /// </summary>
 /// <param name="controller">
 /// The controller type.
 /// </param>
 /// <param name="settings">
 /// The settings.
 /// </param>
 /// <returns>
 /// The steam controller routes for the provided <paramref name="controller"/>.
 /// </returns>
 public static IDictionary<string, ControllerRoute> GetRoutes(Type controller, WebStreamsSettings settings)
 {
     var methodRoutePrefix = JoinRouteParts(settings.RoutePrefix, GetRoutePrefix(controller));
     return GetStreamMethods(controller)
         .Select(method => GetControllerMethod(method, settings, methodRoutePrefix))
         .ToDictionary(_ => _.Route, _ => _);
 }
 /// <summary>
 /// Initializes static members of the <see cref="WebStreamsSettings"/> class.
 /// </summary>
 static WebStreamsSettings()
 {
     Default = new WebStreamsSettings();
 }
        /// <summary>
        /// Returns the <see cref="ControllerRoute.Invoker"/> for the provided <paramref name="method"/>.
        /// </summary>
        /// <param name="method">The method.</param>
        /// <param name="methodRoutePrefix">The route prefix for the controller.</param>
        /// <returns>
        /// The <see cref="ControllerRoute"/> for the provided <paramref name="method"/>.
        /// </returns>
        private static ControllerRoute GetControllerMethod(MethodInfo method, WebStreamsSettings settings, string methodRoutePrefix)
        {
            var observableParameters = new HashSet<string>();

            // Define the parameters of the resulting invoker.
            var controllerParameter = Expression.Parameter(typeof(object), "controller");
            var parametersParameter = Expression.Parameter(typeof(IDictionary<string, string>), "parameters");
            var getObservableParameter = Expression.Parameter(typeof(Func<string, IObservable<string>>), "getObservable");

            // Reflect the methods being which are used below.
            var tryGetValue = typeof(IDictionary<string, string>).GetMethod("TryGetValue");
            var deserialize = typeof(JsonConvert).GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type), typeof(JsonSerializerSettings) });
            var invokeFunc = typeof(Func<string, IObservable<string>>).GetMethod("Invoke");

            // Construct expressions to retrieve each of the controller method's parameters.
            var parameterDictionaryVar = Expression.Variable(typeof(string), "paramVal");
            var allVariables = new List<ParameterExpression> { parameterDictionaryVar };
            var parameters = new List<ParameterExpression>();
            var parameterAssignments = new List<Expression>();
            foreach (var parameter in method.GetParameters())
            {
                // Resulting parameter value.
                var paramType = parameter.ParameterType;
                var paramVar = Expression.Variable(paramType, parameter.Name);
                parameters.Add(paramVar);
                Expression parameterAssignment;

                // If the parameter is an observable, get the incoming stream using the "getObservable" parameter.
                var serializerSettingsConst = Expression.Constant(settings.JsonSerializerSettings);
                if (paramType.IsGenericType && paramType.GetGenericTypeDefinition() == typeof(IObservable<>))
                {
                    // Add the observable parameter to the stream controller definition.
                    observableParameters.Add(parameter.Name);

                    // This is an incoming observable, get the proxy observable to pass in.
                    var incomingObservable = Expression.Call(getObservableParameter, invokeFunc, new Expression[] { Expression.Constant(parameter.Name) });

                    // Select the proxy observable into the correct shape.
                    var paramTypeArg = paramType.GenericTypeArguments[0];
                    var selectIncomingObservable = typeof(Observable).GetGenericMethod(
                        "Select",
                        new[] { typeof(IObservable<string>), typeof(Func<,>).MakeGenericType(typeof(string), paramTypeArg) },
                        new[] { typeof(string), paramTypeArg });
                    var next = Expression.Parameter(typeof(string), "next");
                    var observableType = paramTypeArg;
                    var selector =
                        Expression.Lambda(
                            Expression.Convert(
                                Expression.Call(null, deserialize, new Expression[] { next, Expression.Constant(observableType), serializerSettingsConst }),
                                observableType),
                            new[] { next });

                    // Pass the converted observable in for the current parameter.
                    parameterAssignment = Expression.Assign(
                        paramVar,
                        Expression.Call(null, selectIncomingObservable, new Expression[] { incomingObservable, selector }));
                }
                else
                {
                    // Try to get the parameter from the parameters dictionary and convert it if neccessary.
                    Expression convertParam;
                    var tryGetParam = Expression.Call(
                        parametersParameter,
                        tryGetValue,
                        new Expression[] { Expression.Constant(parameter.Name), parameterDictionaryVar });

                    if (paramType == typeof(string))
                    {
                        // Strings need no conversion, just pluck the value from the parameter list.
                        convertParam = parameterDictionaryVar;
                    }
                    else
                    {
                        // Determine whether or not the standard
                        if (paramType.ShouldUseStaticTryParseMthod())
                        {
                            // Parse the value using the "TryParse" method of the parameter type.
                            var tryParseMethod = paramType.GetMethod("TryParse", new[] { typeof(string), paramType.MakeByRefType() });
                            var tryParseExp = Expression.Call(tryParseMethod, parameterDictionaryVar, paramVar);

                            // Use the default value if parsing failed.
                            convertParam = Expression.Block(tryParseExp, paramVar);
                        }
                        else
                        {
                            // Determine whether or not the value is a JSON primitive.
                            var contract = JsonSerializer.Create(settings.JsonSerializerSettings).ContractResolver.ResolveContract(paramType);
                            var isPrimitive = contract.GetType() == typeof(JsonPrimitiveContract);

                            // Use the provided serializer to deserialize the parameter value.
                            var paramTypeConst = Expression.Constant(paramType);
                            if (isPrimitive)
                            {
                                // String-based primitives such as DateTime need to be wrapped in quotes before deserialization.
                                var quoteConst = Expression.Constant("\"");
                                var stringConcatMethod = typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object), typeof(object) });
                                var quotedParameterValue = Expression.Call(
                                    null,
                                    stringConcatMethod,
                                    new Expression[] { quoteConst, parameterDictionaryVar, quoteConst });

                                // Deserialize the quoted value.
                                var deserialized = Expression.Call(
                                    null,
                                    deserialize,
                                    new Expression[] { quotedParameterValue, paramTypeConst, serializerSettingsConst });
                                convertParam = Expression.Convert(deserialized, paramType);
                            }
                            else
                            {
                                // Serialize raw value for non-primitive types, bools, and numeric types.
                                var deserialized = Expression.Call(
                                    null,
                                    deserialize,
                                    new Expression[] { parameterDictionaryVar, paramTypeConst, serializerSettingsConst });
                                convertParam = Expression.Convert(deserialized, paramType);
                            }
                        }
                    }

                    parameterAssignment = Expression.IfThen(tryGetParam, Expression.Assign(paramVar, convertParam));
                }

                parameterAssignments.Add(parameterAssignment);
            }

            // Cast the controller into its native type and invoke it to get the outgoing observable.
            if (method.ReflectedType == null)
            {
                throw new NullReferenceException("method.ReflectedType is null");
            }

            var controller = (!method.IsStatic) ? Expression.Convert(controllerParameter, method.ReflectedType) : null;
            var outgoingObservable = Expression.Call(controller, method, parameters);

            // Convert the outgoing observable into an observable of strings.
            var selectToString = typeof(Observable).GetGenericMethod(
                "Select",
                new[] { method.ReturnType, typeof(Func<,>).MakeGenericType(method.ReturnType.GenericTypeArguments[0], typeof(string)) },
                new[] { method.ReturnType.GenericTypeArguments[0], typeof(string) });
            Expression<Func<object, string>> serialize = input => JsonConvert.SerializeObject(input, settings.JsonSerializerSettings);
            var outgoingStringObservable = Expression.Call(null, selectToString, new Expression[] { outgoingObservable, serialize });

            parameterAssignments.Add(outgoingStringObservable);
            allVariables.AddRange(parameters);
            var body = Expression.Block(allVariables.ToArray(), parameterAssignments);

            var lambda = Expression.Lambda<ControllerRoute.Invoker>(body, controllerParameter, parametersParameter, getObservableParameter);
            var route = JoinRouteParts(methodRoutePrefix, GetRouteSuffixTemplate(method));
            return new ControllerRoute(route, method.DeclaringType, lambda.Compile(), observableParameters);
        }