// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using System.Collections.Generic;
using Unity.Scripting.Compilation;
using UnityEditor.Scripting.ScriptCompilation;

namespace UnityEditor.Compilation
{
    public enum AssemblyBuilderStatus
    {
        NotStarted = 0,
        IsCompiling = 1,
        Finished = 2,
    }

    [Flags]
    public enum AssemblyBuilderFlags
    {
        None = 0,
        EditorAssembly = 1,
        DevelopmentBuild = 2,
    }

    [Flags]
    public enum ReferencesOptions
    {
        None = 0,
        UseEngineModules = 1
    }

    public class AssemblyBuilder
    {
        public event Action<string> buildStarted;
        public event Action<string, CompilerMessage[]> buildFinished;

        public string[] scriptPaths { get; private set; }
        public string assemblyPath { get; private set; }
        public string[] additionalDefines { get; set; }
        public string[] additionalReferences { get; set; }
        public string[] excludeReferences { get; set; }
        public ScriptCompilerOptions compilerOptions { get; set; }
        public ReferencesOptions referencesOptions { get; set; }

        public AssemblyBuilderFlags flags { get; set; }
        public BuildTargetGroup buildTargetGroup { get; set; }
        public BuildTarget buildTarget { get; set; }

        public string[] defaultReferences
        {
            get
            {
                return GetDefaultReferences(EditorCompilationInterface.Instance);
            }
        }

        public string[] defaultDefines
        {
            get
            {
                return GetDefaultDefines(EditorCompilationInterface.Instance);
            }
        }


        CompilationTask compilationTask;

        public AssemblyBuilder(string assemblyPath, params string[] scriptPaths)
        {
            if (string.IsNullOrEmpty(assemblyPath))
                throw new ArgumentException("assemblyPath cannot be null or empty");

            if (scriptPaths == null || scriptPaths.Length == 0)
                throw new ArgumentException("scriptPaths cannot be null or empty");

            this.scriptPaths = scriptPaths;
            this.assemblyPath = assemblyPath;

            compilerOptions = new ScriptCompilerOptions();
            flags = AssemblyBuilderFlags.None;
            referencesOptions = ReferencesOptions.None;
            buildTargetGroup = EditorUserBuildSettings.activeBuildTargetGroup;
            buildTarget = EditorUserBuildSettings.activeBuildTarget;
        }

        public bool Build()
        {
            return Build(EditorCompilationInterface.Instance);
        }

        internal bool Build(EditorCompilation editorCompilation)
        {
            if (editorCompilation.IsCompilationTaskCompiling())
                return false;

            if (status != AssemblyBuilderStatus.NotStarted)
                throw new Exception(string.Format("Cannot start AssemblyBuilder with status {0}. Expected {1}", status, AssemblyBuilderStatus.NotStarted));

            var scriptAssembly = editorCompilation.CreateScriptAssembly(this);


            compilationTask = new CompilationTask(new[] { scriptAssembly }, scriptAssembly.OutputDirectory, this,
                EditorScriptCompilationOptions.BuildingEmpty, CompilationTaskOptions.StopOnFirstError, 1,
                editorCompilation.ILPostProcessing, new CompilerFactory(new CompilerFactoryHelper()));

            compilationTask.OnCompilationTaskStarted += (context) =>
            {
                editorCompilation.InvokeCompilationStarted(context);
            };

            compilationTask.OnCompilationTaskFinished += (context) =>
            {
                editorCompilation.InvokeCompilationFinished(context);
            };

            compilationTask.OnCompilationStarted += (assembly, phase) =>
            {
                editorCompilation.InvokeAssemblyCompilationStarted(assemblyPath);
                OnCompilationStarted(assembly, phase);
            };

            compilationTask.OnCompilationFinished += (assembly, messages) =>
            {
                editorCompilation.InvokeAssemblyCompilationFinished(assemblyPath, messages);
                OnCompilationFinished(assembly, messages);
            };

            compilationTask.Poll();

            editorCompilation.AddAssemblyBuilder(this);

            return true;
        }

        internal string[] GetDefaultReferences(EditorCompilation editorCompilation)
        {
            return editorCompilation.GetAssemblyBuilderDefaultReferences(this);
        }

        internal string[] GetDefaultDefines(EditorCompilation editorCompilation)
        {
            return editorCompilation.GetAssemblyBuilderDefaultDefines(this);
        }

        public AssemblyBuilderStatus status
        {
            get
            {
                if (compilationTask == null)
                    return AssemblyBuilderStatus.NotStarted;

                if (compilationTask.IsCompiling)
                    return compilationTask.Poll() ? AssemblyBuilderStatus.Finished : AssemblyBuilderStatus.IsCompiling;

                return AssemblyBuilderStatus.Finished;
            }
        }

        void OnCompilationStarted(ScriptAssembly assembly, int phase)
        {
            if (buildStarted == null)
                return;

            try
            {
                buildStarted(assemblyPath);
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogException(e);
            }
        }

        void OnCompilationFinished(ScriptAssembly assembly, List<UnityEditor.Scripting.Compilers.CompilerMessage> messages)
        {
            if (buildFinished == null)
                return;

            var convertedMessages = EditorCompilation.ConvertCompilerMessages(messages);

            try
            {
                buildFinished(assemblyPath, convertedMessages);
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogException(e);
            }
        }
    }
}
