using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Configuration;
using System.Web.Razor;

namespace Muban.Compiler
{
    public class CompilerService : ICompilerService, IDisposable
    {
        const string NAMESPACE = "Muban.Dynamic";
        static readonly string[] ASSEMBLIES;

        class DistinctComparer : IEqualityComparer<string>
        {
            public bool Equals(string x, string y)
            {
                return string.Equals(Path.GetFileName(x), Path.GetFileName(y), StringComparison.OrdinalIgnoreCase);
            }

            public int GetHashCode(string obj)
            {
                return 0;
            }
        }

        static CompilerService()
        {
            var ass = new List<string>(AppDomain.CurrentDomain.GetAssemblies().Where(i => !i.IsDynamic).Select(i => i.Location).Union(AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().Where(i => !i.IsDynamic).Select(i => i.Location)));
            if (Parser.CONFIG != null && Parser.CONFIG.Assemblies != null)
            {
                foreach (AssemblyInfo i in Parser.CONFIG.Assemblies)
                    ass.Add(i.Assembly);
            }
            var path = AppDomain.CurrentDomain.BaseDirectory;
            foreach (var i in Directory.GetFiles(path, "*.dll"))
                ass.Add(i);
            path = AppDomain.CurrentDomain.RelativeSearchPath;
            if (!string.IsNullOrWhiteSpace(path))
            {
                foreach (var i in Directory.GetFiles(path, "*.dll"))
                    ass.Add(i);
            }
            ASSEMBLIES = ass.Distinct(new DistinctComparer()).ToArray();
        }

        public CompilerService(CodeDomProvider provider, RazorCodeLanguage language)
        {
            this.Provider = provider;
            this.Language = language;
        }

        public CodeDomProvider Provider { get; private set; }

        public RazorCodeLanguage Language { get; private set; }

        protected virtual CodeCompileUnit CompileUnit(string className, TextReader input, Type templateType)
        {
            var host = new RazorEngineHost(this.Language);
            host.DefaultBaseClass = templateType.FullName;
            host.DefaultClassName = className;
            host.DefaultNamespace = NAMESPACE;
            host.NamespaceImports.Add("System");
            host.NamespaceImports.Add("System.Linq");
            host.NamespaceImports.Add("System.Collections");
            host.NamespaceImports.Add("System.Collections.Generic");
            if (Parser.CONFIG != null && Parser.CONFIG.Namespaces != null)
            {
                foreach (NamespaceInfo i in Parser.CONFIG.Namespaces)
                    host.NamespaceImports.Add(i.Namespace);
            }
            return new RazorTemplateEngine(host).GenerateCode(input, className, NAMESPACE, null).GeneratedCode;
        }

        public Type CompileType(TypeContext context)
        {
            var unit = this.CompileUnit(context.ClassName, context.Input, context.TemplateType);
            var options = new CompilerParameters
            {
                GenerateInMemory = true,
                GenerateExecutable = false,
                IncludeDebugInformation = false,
                CompilerOptions = Parser.CONFIG != null && !string.IsNullOrWhiteSpace(Parser.CONFIG.CompilerOptions) ? Parser.CONFIG.CompilerOptions : "/target:library /optimize",
                TreatWarningsAsErrors = false
            };
            options.ReferencedAssemblies.AddRange(ASSEMBLIES);
            var result = this.Provider.CompileAssemblyFromDom(options, unit);
            if (result.Errors.HasErrors)
            {
                var error = result.Errors.OfType<CompilerError>().Where(i => !i.IsWarning).FirstOrDefault();
                if (error != null) throw new Exception(error.ErrorText); //抛出错误
            }
            return result.CompiledAssembly.GetType(NAMESPACE + "." + context.ClassName);
        }

        void IDisposable.Dispose()
        {
            this.Provider.Dispose();
        }
    }
}
