/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

#if NETCOREAPP2_0 || NETSTANDARD2_0
using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
#endif

namespace React.AspNet
{
	/// <summary>
	/// Enables serving static JavaScript files compiled via Babel. Wraps around StaticFileMiddleware.
	/// </summary>
	public class BabelFileMiddleware
	{
		private readonly RequestDelegate _next;
		private readonly IWebHostEnvironment _hostingEnv;
		private readonly ILoggerFactory _loggerFactory;
		private readonly BabelFileOptions _options;

		/// <summary>
		/// Creates a new instance of the BabelFileMiddleware.
		/// </summary>
		/// <param name="next">The next middleware in the pipeline.</param>
		/// <param name="options">The configuration options.</param>
		/// <param name="hostingEnv">The hosting environment.</param>
		/// <param name="loggerFactory">An <see cref="ILoggerFactory"/> instance used to create loggers.</param>
		public BabelFileMiddleware(RequestDelegate next, BabelFileOptions options, IWebHostEnvironment hostingEnv, ILoggerFactory loggerFactory)
		{
			if (next == null)
				throw new ArgumentNullException("next");

			_next = next;
			_hostingEnv = hostingEnv;
			_loggerFactory = loggerFactory;

			// Default values
			_options = options ?? new BabelFileOptions();
		}

		/// <summary>
		/// Processes a request to determine if it matches a known JavaScript file, and if so, transforms
		/// it via Babel and serves it
		/// </summary>
		/// <param name="context">ASP.NET HTTP context</param>
		public async Task Invoke(HttpContext context)
		{
			if (!context.Request.Path.HasValue || !_options.Extensions.Any(context.Request.Path.Value.EndsWith))
			{
				// Not a request for a JavaScript file, so just pass through to the next middleware
				await _next(context);
				return;
			}

			var internalStaticMiddleware = CreateFileMiddleware(ReactEnvironment.Current.Babel);
			await internalStaticMiddleware.Invoke(context);
		}

		/// <summary>
		/// Creates the internal <see cref="StaticFileMiddleware"/> used to serve files.
		/// </summary>
		/// <param name="babel"></param>
		/// <returns></returns>
		private StaticFileMiddleware CreateFileMiddleware(IBabel babel)
		{
			return new StaticFileMiddleware(
				_next,
				_hostingEnv,
				Options.Create(new StaticFileOptions
				{
					ContentTypeProvider = _options.StaticFileOptions.ContentTypeProvider,
					DefaultContentType = _options.StaticFileOptions.DefaultContentType,
					OnPrepareResponse = _options.StaticFileOptions.OnPrepareResponse,
					RequestPath = _options.StaticFileOptions.RequestPath,
					ServeUnknownFileTypes = _options.StaticFileOptions.ServeUnknownFileTypes,
					FileProvider = new BabelFileSystem(
						babel,
						_options.StaticFileOptions.FileProvider ?? _hostingEnv.WebRootFileProvider,
						_options.Extensions
					)
				}),
				_loggerFactory
			);
		}
	}
}
