using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using NodePinDirPXP.Crawler.API.Utils;
using PXP.Crawler.API.Model.Core;
using PXP.Crawler.API.Utils;

namespace PXP.Crawler.API.Model
{
    [Serializable]
    public class Project : IDisposable
    {
        #region Variables

        private int currentProgramID = 0;
        internal ListEx<Program> programs = new ListEx<Program>();
        internal string name = "New Project";
        internal ProjectItem rootProjectItem;
        private bool ignorProgramsListChange = false;
        internal ProjectCompiler comp;
        internal Sett externalSettings;
        internal int currDomainID = 0;
        internal ProjectDomain domain;
        internal byte[] assemblyData = new byte[0];
        internal byte[] assemblyDebugData = new byte[0];
        internal Guid guid = Guid.NewGuid();

        #endregion


        #region Constructor / Destructor

        public Project()
        {
            domain = new ProjectDomain(this);
            domain.Init();

            //PluginManager.Current.RegisterNodeType(typeof(ProgramNode));
            PluginManager.Current.RegisterNamespace("PXP.Crawler.API.Model.Core");
            
            comp = new ProjectCompiler(this);


            rootProjectItem = new ProjectItem();
            rootProjectItem.type = ProjectItem.ProjectItemType.Project;
            rootProjectItem.project = this;
            rootProjectItem.Name = name;
        }

        public virtual void Dispose()
        {
            while (programs.Count > 0)
            {
                Program cp = programs[0];
                programs.RemoveAt(0);
                cp.Dispose();
            }


            if (domain != null)
            {
                domain.Dispose();
                domain = null;
            }            

            DeleteAsmData();
        }

        #endregion

        #region Internal Events

        #endregion

        #region Properties

        [Browsable(false)]
        public Sett ExternalSettings
        {
            get
            {
                return externalSettings;
            }
            set
            {
                externalSettings = value;
            }
        }

        public ProjectCompiler Compiler
        {
            get { return comp; }
        }

        public ProjectItem RootProjectItem
        {
            get { return rootProjectItem; }
            set { rootProjectItem = value; }
        }

        public ListEx<Program> Programs
        {
            get
            {
                return programs;
            }
        }


        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                rootProjectItem.Name = name;
            }
        }

        #endregion

        #region Methods

        public void WriteAsmData()
        {
            string bPath = Application.ExecutablePath.Substring(0, Application.ExecutablePath.LastIndexOf('\\')) + "\\";

            string apath = bPath + "PBIN_" + guid.ToString();
            
            DeleteAsmData();

            if (assemblyData.Length > 0)
            {
                if (!File.Exists(apath + ".dll"))
                    File.WriteAllBytes(apath + ".dll", assemblyData);
                if (!File.Exists(apath + ".pdb"))
                    File.WriteAllBytes(apath + ".pdb", assemblyDebugData);                
            }
        }

        public void DeleteAsmData()
        {
            string bPath = Application.ExecutablePath.Substring(0, Application.ExecutablePath.LastIndexOf('\\')) + "\\";

            string apath = bPath + "PBIN_" + guid.ToString();

            try
            {
                if (File.Exists(apath + ".dll"))
                    File.Delete(apath + ".dll");
            }
            catch (UnauthorizedAccessException uaExc)
            {
                
            }
            try
            {
                if (File.Exists(apath + ".pdb"))
                    File.Delete(apath + ".pdb");
            }
            catch (UnauthorizedAccessException uaExc)
            {
                
            }
        }

        public int GetNewDomainID()
        {
            currDomainID++;
            return currDomainID;
        }

        public Program FindProgramByID(int projID)
        {
            foreach (Program pr in programs)
            {
                if (pr.id == projID)
                    return pr;
            }
            return null;
        }

        public List<ProjectItem> GetProjectItemsOfType(ProjectItem.ProjectItemType piType)
        {
            rootProjectItem.path = rootProjectItem.name.Replace('.', '_').Replace(' ', '_');
            return GetProjectItemsOfType(rootProjectItem, piType);
        }

        private List<ProjectItem> GetProjectItemsOfType(ProjectItem parent, ProjectItem.ProjectItemType piType)
        {
            List<ProjectItem> projectItems = new List<ProjectItem>();

            if (parent.type == piType)
                projectItems.Add(parent);

            foreach (ProjectItem piCh in parent.items)
            {
                piCh.path = parent.path + "." + piCh.name.Replace('.', '_').Replace(' ', '_');
                projectItems.AddRange(GetProjectItemsOfType(piCh, piType));
            }

            return projectItems;
        }

        public int GetNewProgramID()
        {
            currentProgramID++;
            return currentProgramID;
        }

        public string GetXml()
        {
            StringBuilder sb = new StringBuilder();
            TextWriter tw = new StringWriter(sb, CultureInfo.InvariantCulture);
            XmlWriterSettings xws = new XmlWriterSettings()
            {
                Indent = true,
                Encoding = Encoding.UTF8,
                NewLineHandling = NewLineHandling.Entitize
            };
            XmlWriter xw = XmlTextWriter.Create(tw, xws);
            XmlDocument xDoc = new XmlDocument();
            XmlDeclaration xDec = xDoc.CreateXmlDeclaration("1.0", "utf-8", "yes");
            xDoc.AppendChild(xDec);
            PutXml(xDoc);
            xDoc.WriteTo(xw);
            xw.Close();
            xw.Dispose();
            tw.Dispose();

            return sb.ToString();
        }

        public void WriteXml(Stream str)
        {            
            TextWriter tw = new StreamWriter(str, Encoding.UTF8);
            XmlWriterSettings xws = new XmlWriterSettings()
            {
                Indent = true,
                Encoding = Encoding.UTF8,
                NewLineHandling = NewLineHandling.Entitize
            };
            XmlWriter xw = XmlTextWriter.Create(tw, xws);
            XmlDocument xDoc = new XmlDocument();
            XmlDeclaration xDec = xDoc.CreateXmlDeclaration("1.0", "utf-8", "yes");
            xDoc.AppendChild(xDec);
            PutXml(xDoc);
            xDoc.WriteTo(xw);
            xw.Close();
            xw.Dispose();
            tw.Dispose();
        }
        
        internal void PutXml(XmlDocument xmlDocument)
        {
            XmlDocument xDoc = xmlDocument;

            XmlElement projEl = xDoc.CreateElement("project");
            xDoc.AppendChild(projEl);

            Misc.CreateAppendXmlElement(projEl, "name", name);

            string asmDataStr = Convert.ToBase64String(assemblyData);
            string asmDebugDataStr = Convert.ToBase64String(assemblyDebugData);

            //Misc.CreateAppendXmlElement(projEl, "guid", guid.ToString());
            Misc.CreateAppendXmlElement(projEl, "asm_data", asmDataStr);
            Misc.CreateAppendXmlElement(projEl, "asm_pdb_data", asmDebugDataStr);

            XmlElement programsEl = Misc.CreateAppendXmlElement(projEl, "programs");
            projEl.AppendChild(programsEl);
            
            foreach (Program prog in programs)
            {
                prog.PutXml(programsEl);
            }

            XmlElement rootItemEl = Misc.CreateAppendXmlElement(projEl, "root_item");
            rootProjectItem.PutXml(rootItemEl);
        }

        internal void SetXml(XmlElement projEl)
        {
            name = Misc.GetChildElementXml(projEl, "name");

            currentProgramID = 0;

            string guidStr = Misc.GetChildElementXml(projEl, "guid", guid.ToString());
            //guid = Guid.Parse(guidStr);

            domain.Unload();            

            string asmDataStr = Misc.GetChildElementXml(projEl, "asm_data");
            string asmDebugDataStr = Misc.GetChildElementXml(projEl, "asm_pdb_data");

            assemblyData = Convert.FromBase64String(asmDataStr);
            assemblyDebugData = Convert.FromBase64String(asmDebugDataStr);
            WriteAsmData();

            XmlElement progsEl = projEl["programs"];
            foreach (XmlElement progEl in progsEl.ChildNodes)
            {
                Program prog = new Program(this);
                prog.SetXml(progEl);
                programs.Add(prog);

                if (currentProgramID < prog.id)
                    currentProgramID = prog.id;

            }

            XmlElement rootItemEl = projEl["root_item"];
            rootProjectItem.SetXml(rootItemEl);

            

            domain.Reload();
        }

        public void SaveToFile(string filePath)
        {
            if (File.Exists(filePath))
                File.Delete(filePath);

            using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                WriteXml(fs);
            }
        }

        public static Project LoadFromFile(string filePath)
        {            
            if (File.Exists(filePath))
            {                
                Project proj = new Project();

                XmlDocument xDoc = new XmlDocument();
                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (TextReader tr = new StreamReader(fs, Encoding.UTF8))
                    {
                        using (XmlReader xr = XmlReader.Create(tr, new XmlReaderSettings() {  }))
                        {
                            xDoc.Load(xr);
                        }                        
                    }
                }

                XmlElement projEl = xDoc["project"];
                proj.SetXml(projEl);

                return proj;
            }
            else
            {
                return null;
            }
        }

        public Program GetProgramByID(int programID)
        {
            foreach (Program pr in programs)
                if (pr.id == programID)
                    return pr;return null;
        }

        #endregion

        #region Events

        public event ProjectLogMessageEventHandler LogMessage;

        public void Log(string msg, string type = "info", Program prog = null)
        {
            if (LogMessage != null)
                LogMessage(this, new ProjectLogMessageEventArgs(prog, msg, type, this));
        }

        #endregion
    }

    public class ProjectLogMessageEventArgs : MarshalByRefObject
    {
        internal Program program;
        internal Project project;
        internal string type;
        internal string msg;

        public ProjectLogMessageEventArgs(Program program, string msg, string type = "info", Project project = null)
        {
            this.program = program;
            if (program == null)
            {
                this.project = project;
            }
            else
            {
                this.project = this.program.project;                
            }
            this.type = type;
            this.msg = msg;
        }

        public Program Program
        {
            get { return program; }
        }

        public Project Project
        {
            get { return project; }
        }

        public string Type
        {
            get { return type; }
        }

        public string Message
        {
            get { return msg; }
        }
    }

    public delegate void ProjectLogMessageEventHandler(object sender, ProjectLogMessageEventArgs e);
}
