using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reactive.Joins;
using System.Reactive.Linq;
using System.Reflection;

namespace System.Reactive
{
	internal class ObservableQuery<TSource> : ObservableQuery, IQbservable<TSource>, IQbservable, IObservable<TSource>
	{
		internal ObservableQuery(IObservable<TSource> source)
		{
			this._source = source;
			this._expression = Expression.Constant(this);
		}

		internal ObservableQuery(Expression expression)
		{
			this._expression = expression;
		}

		public Type ElementType
		{
			get
			{
				return typeof(TSource);
			}
		}

		public GInterface5 Provider
		{
			get
			{
				return Qbservable.Provider;
			}
		}

		public IDisposable Subscribe(IObserver<TSource> observer)
		{
			if (this._source == null)
			{
				Expression<Func<IObservable<TSource>>> expression = Expression.Lambda<Func<IObservable<TSource>>>(new ObservableQuery<TSource>.ObservableRewriter().Visit(this._expression), Array.Empty<ParameterExpression>());
				this._source = expression.Compile()();
			}
			return ((IObservable<TSource>)this._source).Subscribe(observer);
		}

		public override string ToString()
		{
			ConstantExpression constantExpression = this._expression as ConstantExpression;
			if (constantExpression == null || constantExpression.Value != this)
			{
				return this._expression.ToString();
			}
			if (this._source != null)
			{
				return this._source.ToString();
			}
			return "null";
		}

		private class ObservableRewriter : ExpressionVisitor
		{
			protected override Expression VisitConstant(ConstantExpression node)
			{
				ObservableQuery observableQuery = node.Value as ObservableQuery;
				if (observableQuery == null)
				{
					return node;
				}
				object source = observableQuery.Source;
				if (source != null)
				{
					return Expression.Constant(source);
				}
				return this.Visit(observableQuery.Expression);
			}

			protected override Expression VisitMethodCall(MethodCallExpression node)
			{
				MethodInfo method = node.Method;
				if (method.DeclaringType.BaseType == typeof(QueryablePattern))
				{
					if (method.Name == "Then")
					{
						Expression expression = this.Visit(node.Object);
						Expression[] array = node.Arguments.Select((Expression arg) => ObservableQuery<TSource>.ObservableRewriter.Unquote(this.Visit(arg))).ToArray<Expression>();
						return Expression.Call(expression, "Then", method.GetGenericArguments(), array);
					}
					if (method.Name == "And")
					{
						Expression expression2 = this.Visit(node.Object);
						Expression[] array2 = node.Arguments.Select((Expression arg) => this.Visit(arg)).ToArray<Expression>();
						return Expression.Call(expression2, "And", method.GetGenericArguments(), array2);
					}
				}
				else
				{
					IEnumerable<Expression> enumerable = node.Arguments.AsEnumerable<Expression>();
					bool flag = false;
					ParameterInfo parameterInfo = method.GetParameters().FirstOrDefault<ParameterInfo>();
					if (parameterInfo != null)
					{
						Type parameterType = parameterInfo.ParameterType;
						if (parameterType == typeof(GInterface5))
						{
							flag = true;
							if (!(Expression.Lambda<Func<GInterface5>>(this.Visit(node.Arguments[0]), Array.Empty<ParameterExpression>()).Compile()() is ObservableQueryProvider))
							{
								return node;
							}
							enumerable = enumerable.Skip(1);
						}
						else if (typeof(IQbservable).IsAssignableFrom(parameterType))
						{
							flag = true;
						}
					}
					if (flag)
					{
						IList<Expression> list = this.VisitQbservableOperatorArguments(method, enumerable);
						return ObservableQuery<TSource>.ObservableRewriter.FindObservableMethod(method, list);
					}
				}
				return base.VisitMethodCall(node);
			}

			protected override Expression VisitLambda<T>(Expression<T> node)
			{
				return node;
			}

			private IList<Expression> VisitQbservableOperatorArguments(MethodInfo method, IEnumerable<Expression> arguments)
			{
				if (method.Name == "When")
				{
					Expression expression = arguments.Last<Expression>();
					if (expression.NodeType == ExpressionType.NewArrayInit)
					{
						NewArrayExpression newArrayExpression = (NewArrayExpression)expression;
						return new List<Expression> { Expression.NewArrayInit(typeof(Plan<>).MakeGenericType(new Type[] { method.GetGenericArguments()[0] }), newArrayExpression.Expressions.Select((Expression param) => this.Visit(param))) };
					}
				}
				return arguments.Select((Expression arg) => this.Visit(arg)).ToList<Expression>();
			}

			private static MethodCallExpression FindObservableMethod(MethodInfo method, IList<Expression> arguments)
			{
				Type type;
				ILookup<string, MethodInfo> lookup;
				if (method.DeclaringType == typeof(Qbservable))
				{
					type = typeof(Observable);
					lookup = ObservableQuery<TSource>.ObservableRewriter.ObservableMethods.Value;
				}
				else
				{
					type = method.DeclaringType;
					if (type.IsDefined(typeof(LocalQueryMethodImplementationTypeAttribute), false))
					{
						type = ((LocalQueryMethodImplementationTypeAttribute)type.GetCustomAttributes(typeof(LocalQueryMethodImplementationTypeAttribute), false)[0]).TargetType;
					}
					lookup = ObservableQuery<TSource>.ObservableRewriter.GetMethods(type);
				}
				Type[] typeArgs = (method.IsGenericMethod ? method.GetGenericArguments() : null);
				MethodInfo methodInfo = lookup[method.Name].FirstOrDefault((MethodInfo candidateMethod) => ObservableQuery<TSource>.ObservableRewriter.ArgsMatch(candidateMethod, arguments, typeArgs));
				if (methodInfo == null)
				{
					throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Providers.NO_MATCHING_METHOD_FOUND, method.Name, type.Name));
				}
				if (typeArgs != null)
				{
					methodInfo = methodInfo.MakeGenericMethod(typeArgs);
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				int i = 0;
				int num = parameters.Length;
				while (i < num)
				{
					arguments[i] = ObservableQuery<TSource>.ObservableRewriter.Unquote(arguments[i]);
					i++;
				}
				return Expression.Call(null, methodInfo, arguments);
			}

			private static ILookup<string, MethodInfo> GetMethods(Type type)
			{
				return type.GetMethods(BindingFlags.Static | BindingFlags.Public).ToLookup((MethodInfo m) => m.Name);
			}

			private static bool ArgsMatch(MethodInfo method, IList<Expression> arguments, Type[] typeArgs)
			{
				ParameterInfo[] array = method.GetParameters();
				if (array.Length != arguments.Count)
				{
					return false;
				}
				if (!method.IsGenericMethod && typeArgs != null && typeArgs.Length != 0)
				{
					return false;
				}
				if (method.IsGenericMethodDefinition)
				{
					if (typeArgs == null)
					{
						return false;
					}
					if (method.GetGenericArguments().Length != typeArgs.Length)
					{
						return false;
					}
					array = method.MakeGenericMethod(typeArgs).GetParameters();
				}
				int i = 0;
				int count = arguments.Count;
				while (i < count)
				{
					Type parameterType = array[i].ParameterType;
					Expression expression = arguments[i];
					if (!parameterType.IsAssignableFrom(expression.Type))
					{
						expression = ObservableQuery<TSource>.ObservableRewriter.Unquote(expression);
						if (!parameterType.IsAssignableFrom(expression.Type))
						{
							return false;
						}
					}
					i++;
				}
				return true;
			}

			private static Expression Unquote(Expression expression)
			{
				while (expression.NodeType == ExpressionType.Quote)
				{
					expression = ((UnaryExpression)expression).Operand;
				}
				return expression;
			}

			private static readonly ObservableQuery<TSource>.ObservableRewriter.Lazy<ILookup<string, MethodInfo>> ObservableMethods = new ObservableQuery<TSource>.ObservableRewriter.Lazy<ILookup<string, MethodInfo>>(() => ObservableQuery<TSource>.ObservableRewriter.GetMethods(typeof(Observable)));

			private class Lazy<T>
			{
				public Lazy(Func<T> factory)
				{
					this._factory = factory;
				}

				public T Value
				{
					get
					{
						Func<T> factory = this._factory;
						lock (factory)
						{
							if (!this._initialized)
							{
								this._value = this._factory();
								this._initialized = true;
							}
						}
						return this._value;
					}
				}

				private readonly Func<T> _factory;

				private T _value;

				private bool _initialized;
			}
		}
	}
}
