using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CSharp;
using PXP.Crawler.API.Utils;

namespace PXP.Crawler.API.Model
{
    [Serializable]
    public class ProjectCompiler
    {
        #region Declarations

        public struct RefferenceAsmInfo
        {
            public string Name;
            public string Location;

            public RefferenceAsmInfo(string name, string loc)
            {
                Name = name;
                Location = loc;
            }
        }

        #endregion

        #region Variables

        internal Project project;

        private AppDomain appDomain;

        internal static string referencesPath = "";

        #endregion

        #region Constructor

        public ProjectCompiler(Project proj)
        {
            project = proj;            
        }

        #endregion

        #region Properties

        public Project Project
        {
            get
            {
                return project;
            }
        }
        
        public static string ReferencesPath
        {
            get
            {
                return referencesPath;
            }
            set
            {
                referencesPath = value;
                referencesPath = referencesPath.TrimEnd('\\') + '\\';
            }
        }

        #endregion

        #region Compiler

        private CSharpCodeProvider codeProvider;

        private void CompileScript(string scrCode)
        {

        }

        #endregion

        #region Methods

        public void CreateDomain()
        {
            appDomain = AppDomain.CreateDomain("ProjectAppDomain");
        }

        public CompilerResults BuildProject()
        {
            project.Log("Project build started...");

            project.domain.Unload();
            project.DeleteAsmData();
            project.guid = Guid.NewGuid();

            List<ProjectItem> scrItems = project.GetProjectItemsOfType(ProjectItem.ProjectItemType.Script);
            string[] scrCodeSources = new string[scrItems.Count];
            int sci = 0;
            int csi = 0;

            foreach (ProjectItem scItem in scrItems)
            {
                //scrCodeSources[sci] = "// PIID: " + scItem.projectObjectID + "\n\r" + scItem.data;
                scrCodeSources[sci] = scItem.data;
                scItem.startIndex = csi;
                csi += scItem.data.Length;
                sci++;
            }
            
            List<RefferenceAsmInfo> refAsms = new List<RefferenceAsmInfo>();
            refAsms.Add(new RefferenceAsmInfo("Microsoft.CSharp", "Microsoft.CSharp.dll"));
            refAsms.Add(new RefferenceAsmInfo("System", "System.dll"));
            refAsms.Add(new RefferenceAsmInfo("System.Core", "System.Core.dll"));
            refAsms.Add(new RefferenceAsmInfo("System.Data", "System.Data.dll"));
            refAsms.Add(new RefferenceAsmInfo("System.Data.DataSetExtensions", "System.Data.DataSetExtensions.dll"));
            refAsms.Add(new RefferenceAsmInfo("System.Drawing", "System.Drawing.dll"));
            refAsms.Add(new RefferenceAsmInfo("System.Windows.Forms", "System.Windows.Forms.dll"));
            refAsms.Add(new RefferenceAsmInfo("System.Xml", "System.Xml.dll"));
            refAsms.Add(new RefferenceAsmInfo("System.Xml.Linq", "System.Xml.Linq.dll"));
            refAsms.Add(new RefferenceAsmInfo("Skybound.Gecko", referencesPath + "Skybound.Gecko.dll"));
            refAsms.Add(new RefferenceAsmInfo("ScintillaNET", referencesPath + "ScintillaNET.dll"));            
            refAsms.Add(new RefferenceAsmInfo("PXP.Crawler.API", referencesPath + "PXP.Crawler.API.dll"));

            var settings = new Dictionary<string, string>();
            settings.Add("CompilerVersion", "v4.0");
            codeProvider = new CSharpCodeProvider(settings);

            string pBinPath = referencesPath + "ProjectsBin";
            if (!Directory.Exists(pBinPath))
                Directory.CreateDirectory(pBinPath);

            string dllTempPath = pBinPath + "\\" + "PBIN_" + project.guid.ToString() + ".dll";
            string dllPdbTempPath = pBinPath + "\\" + "PBIN_" + project.guid.ToString() + ".pdb";

            var parameters = new CompilerParameters
            {
                GenerateInMemory = false,
                GenerateExecutable = false,
                IncludeDebugInformation = true,
                TreatWarningsAsErrors = false,
                OutputAssembly = dllTempPath
            };

            foreach (RefferenceAsmInfo raInfo in refAsms)
            {
                parameters.ReferencedAssemblies.Add(raInfo.Location);
            }

            CompilerResults compResults = codeProvider.CompileAssemblyFromSource(parameters, scrCodeSources);
            List<ProjectItem> epItems = new List<ProjectItem>();
            if (compResults.Errors.Count == 0)
            {
                project.assemblyData = File.ReadAllBytes(dllTempPath);
                project.assemblyDebugData = File.ReadAllBytes(dllPdbTempPath);
                project.WriteAsmData();
                project.Log("Project build succeded...");
            }
            else
            {
                foreach (CompilerError cerr in compResults.Errors)
                {
                    string cef = cerr.FileName.Substring(0, cerr.FileName.Length - 3);
                    string ceiStr = cef.Substring(cef.LastIndexOf('.') + 1);
                    epItems.Add(scrItems[Conv.StringToInt(ceiStr, 0)]);
                    cerr.FileName = ceiStr;
                }
            }
            OnCompileResult(compResults, scrItems);
            

            if (File.Exists(dllTempPath))
                File.Delete(dllTempPath);
            if (File.Exists(dllPdbTempPath))
                File.Delete(dllPdbTempPath);
            

            project.domain.Reload();

            return compResults;
        }

        #endregion

        #region Events

        public event ProjectCompilerResultEventHandler CompileResult;

        private void OnCompileResult(CompilerResults cr, List<ProjectItem> pis)
        {
            if (CompileResult != null)
                CompileResult(this, new ProjectCompilerResultEventArgs(this, cr, pis));
        }

        #endregion
    }



    public class ProjectCompilerResultEventArgs
    {
        public ProjectCompiler Compiler
        {
            get; 
            private set;
        }
        public List<ProjectItem> ProjectItems
        {
            get;
            private set;
        }

        public CompilerResults CompilerResults
        {
            get;
            private set;
        }

        public ProjectCompilerResultEventArgs(ProjectCompiler pc, CompilerResults cr, List<ProjectItem> pis)
        {
            Compiler = pc;
            CompilerResults = cr;
            ProjectItems = pis;
        }
    }

    public delegate void ProjectCompilerResultEventHandler(object sender, ProjectCompilerResultEventArgs e);
}
