// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Extensions;

namespace Microsoft.AspNetCore.Razor.Language
{
	public abstract class RazorProjectEngine
	{
		public abstract RazorConfiguration Configuration { get; }

		public abstract RazorProjectFileSystem FileSystem { get; }

		public abstract RazorEngine Engine { get; }

		public IReadOnlyList<IRazorEngineFeature> EngineFeatures => Engine.Features;

		public IReadOnlyList<IRazorEnginePhase> Phases => Engine.Phases;

		public abstract IReadOnlyList<IRazorProjectEngineFeature> ProjectFeatures { get; }

		public virtual RazorCodeDocument Process( RazorProjectItem projectItem )
		{
			if ( projectItem == null )
			{
				throw new ArgumentNullException( nameof( projectItem ) );
			}

			var codeDocument = CreateCodeDocumentCore( projectItem );
			ProcessCore( codeDocument );
			return codeDocument;
		}

		public virtual RazorCodeDocument Process( RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers )
		{
			throw new NotImplementedException();
		}

		public virtual RazorCodeDocument ProcessDeclarationOnly( RazorProjectItem projectItem )
		{
			throw new NotImplementedException();
		}

		public virtual RazorCodeDocument ProcessDeclarationOnly( RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers )
		{
			throw new NotImplementedException();
		}

		public virtual RazorCodeDocument ProcessDesignTime( RazorSourceDocument source, string fileKind, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers )
		{
			throw new NotImplementedException();
		}

		public virtual RazorCodeDocument ProcessDesignTime( RazorProjectItem projectItem )
		{
			if ( projectItem == null )
			{
				throw new ArgumentNullException( nameof( projectItem ) );
			}

			var codeDocument = CreateCodeDocumentDesignTimeCore( projectItem );
			ProcessCore( codeDocument );
			return codeDocument;
		}

		protected abstract RazorCodeDocument CreateCodeDocumentCore( RazorProjectItem projectItem );

		protected abstract RazorCodeDocument CreateCodeDocumentDesignTimeCore( RazorProjectItem projectItem );

		protected abstract void ProcessCore( RazorCodeDocument codeDocument );

		internal static RazorProjectEngine CreateEmpty( Action<RazorProjectEngineBuilder> configure = null )
		{
			var builder = new DefaultRazorProjectEngineBuilder( RazorConfiguration.Default, RazorProjectFileSystem.Empty );

			configure?.Invoke( builder );

			return builder.Build();
		}

		internal static RazorProjectEngine Create( Action<RazorProjectEngineBuilder> configure ) => Create( RazorConfiguration.Default, RazorProjectFileSystem.Empty, configure );

		public static RazorProjectEngine Create( RazorConfiguration configuration, RazorProjectFileSystem fileSystem ) => Create( configuration, fileSystem, configure: null );

		public static RazorProjectEngine Create(
			RazorConfiguration configuration,
			RazorProjectFileSystem fileSystem,
			Action<RazorProjectEngineBuilder> configure )
		{
			if ( fileSystem == null )
			{
				throw new ArgumentNullException( nameof( fileSystem ) );
			}

			if ( configuration == null )
			{
				throw new ArgumentNullException( nameof( configuration ) );
			}

			var builder = new DefaultRazorProjectEngineBuilder( configuration, fileSystem );

			// The intialization order is somewhat important.
			//
			// Defaults -> Extensions -> Additional customization
			//
			// This allows extensions to rely on default features, and customizations to override choices made by
			// extensions.
			AddDefaultPhases( builder.Phases );
			AddDefaultFeatures( builder.Features );


			if ( configuration.LanguageVersion.CompareTo( RazorLanguageVersion.Version_3_0 ) >= 0 )
			{
				FunctionsDirective.Register( builder );
				ImplementsDirective.Register( builder );
				InheritsDirective.Register( builder );
				InternalDirective.Register( builder );
				NamespaceDirective.Register( builder );
				AttributeDirective.Register( builder );

				AddComponentFeatures( builder, configuration.LanguageVersion );
			}

			configure?.Invoke( builder );

			return builder.Build();
		}

		private static void AddDefaultPhases( IList<IRazorEnginePhase> phases )
		{
			phases.Add( new DefaultRazorParsingPhase() );
			phases.Add( new DefaultRazorSyntaxTreePhase() );
			phases.Add( new DefaultRazorTagHelperBinderPhase() );
			phases.Add( new DefaultRazorIntermediateNodeLoweringPhase() );
			phases.Add( new DefaultRazorDocumentClassifierPhase() );
			phases.Add( new DefaultRazorDirectiveClassifierPhase() );
			phases.Add( new DefaultRazorOptimizationPhase() );
			phases.Add( new DefaultRazorCSharpLoweringPhase() );
		}

		private static void AddDefaultFeatures( ICollection<IRazorFeature> features )
		{
			// General extensibility
			//features.Add(new DefaultRazorDirectiveFeature());
			//features.Add(new DefaultMetadataIdentifierFeature());

			// Options features
			features.Add( new DefaultRazorParserOptionsFactoryProjectFeature() );
			features.Add( new DefaultRazorCodeGenerationOptionsFactoryProjectFeature() );

			// Legacy options features
			//
			// These features are obsolete as of 2.1. Our code will resolve this but not invoke them.
			features.Add( new DefaultRazorCodeGenerationOptionsFeature( designTime: false ) );

			// Syntax Tree passes
			features.Add( new HtmlNodeOptimizationPass() );

			// Intermediate Node Passes
			features.Add( new DefaultDocumentClassifierPass() );
			features.Add( new MetadataAttributePass() );
			features.Add( new DirectiveRemovalOptimizationPass() );
			features.Add( new PreallocatedTagHelperAttributeOptimizationPass() );

			// Default Code Target Extensions
			var targetExtensionFeature = new DefaultRazorTargetExtensionFeature();
			features.Add( targetExtensionFeature );
			targetExtensionFeature.TargetExtensions.Add( new MetadataAttributeTargetExtension() );
			targetExtensionFeature.TargetExtensions.Add( new DefaultTagHelperTargetExtension() );
			targetExtensionFeature.TargetExtensions.Add( new PreallocatedAttributeTargetExtension() );

			// Default configuration
			var configurationFeature = new DefaultDocumentClassifierPassFeature();
			features.Add( configurationFeature );
			configurationFeature.ConfigureClass.Add( ( document, @class ) =>
			{
				@class.ClassName = "Template";
				@class.Modifiers.Add( "public" );
			} );

			configurationFeature.ConfigureNamespace.Add( ( document, @namespace ) =>
			{
				@namespace.Content = "Razor";
			} );

			configurationFeature.ConfigureMethod.Add( ( document, method ) =>
			{
				method.MethodName = "ExecuteAsync";
				method.ReturnType = $"global::{typeof( Task ).FullName}";

				method.Modifiers.Add( "public" );
				method.Modifiers.Add( "async" );
				method.Modifiers.Add( "override" );
			} );
		}

		private static void AddComponentFeatures( RazorProjectEngineBuilder builder, RazorLanguageVersion razorLanguageVersion )
		{
			// Directives (conditional on file kind)
			ComponentCodeDirective.Register( builder );
			ComponentPageDirective.Register( builder );
			ComponentConstrainedTypeParamDirective.Register( builder );
			ComponentPreserveWhitespaceDirective.Register( builder );

			// Document Classifier
			builder.Features.Add( new ComponentDocumentClassifierPass() );

			// Directive Classifier
			builder.Features.Add( new ComponentWhitespacePass() );

			// Optimization
			builder.Features.Add( new ComponentComplexAttributeContentPass() );
			builder.Features.Add( new ComponentLoweringPass() );
			builder.Features.Add( new ComponentEventHandlerLoweringPass() );
			builder.Features.Add( new ComponentKeyLoweringPass() );
			builder.Features.Add( new ComponentSplatLoweringPass() );
			builder.Features.Add( new ComponentBindLoweringPass() );
			builder.Features.Add( new ComponentTemplateDiagnosticPass() );
			builder.Features.Add( new ComponentGenericTypePass() );
			builder.Features.Add( new ComponentChildContentDiagnosticPass() );
			builder.Features.Add( new ComponentMarkupDiagnosticPass() );
			builder.Features.Add( new ComponentMarkupBlockPass() );
			builder.Features.Add( new ComponentMarkupEncodingPass() );
		}
	}
}
