using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using DevExpress.Utils.Animation;
using PXP.Crawler.API.Data;
using PXP.Crawler.API.Data.ActionsDataSetTableAdapters;
using PXP.Crawler.API.Model;
using PXP.Crawler.API.Properties;
using NodePinDirPXP.Crawler.API.Utils;
using PXP.Crawler.API.Utils;

namespace PXP.Crawler.API
{
    public class PluginManager : IDisposable
    {
        #region Variables

        public static PluginManager Current;

        internal string pluginsPath = "";
        internal string refresPath = "";
            

        internal ActionsDataSet actionsDS;
        internal ActionsTableAdapter actionsTA;
        internal ActionMetaTableAdapter actionsMetaTA;
        internal ActionMetaDataTableAdapter actionsMetaDataTA;

        private SortedDictionary<string, SortedDictionary<string, NodeInfo>> nodeTypes = new SortedDictionary<string, SortedDictionary<string, NodeInfo>>();
        private SortedDictionary<string, NodeInfo> allTypes = new SortedDictionary<string, NodeInfo>();

        private static bool asmResolveSet = false;
        
        #endregion

        #region Constructor / Destructor

        public PluginManager(string plugsPath)
        {
            Current = this;
            pluginsPath = plugsPath;
            actionsDS = new ActionsDataSet();
            actionsTA = new ActionsTableAdapter();
            actionsMetaTA = new ActionMetaTableAdapter();
            actionsMetaDataTA = new ActionMetaDataTableAdapter();

            if (!asmResolveSet)
            {
                AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AsmResolve;
            }
        }

        internal static Assembly LoadAssembly(string aPath)
        {
            if (!File.Exists(aPath))
                return null;

            string adPath = aPath.Substring(0, aPath.LastIndexOf('.')) + ".pdb";

            byte[] aData = File.ReadAllBytes(aPath);

            if (File.Exists(adPath))
            {
                byte[] aDebugData = File.ReadAllBytes(adPath);
                return Assembly.Load(aData, aDebugData, SecurityContextSource.CurrentAssembly);
            }
            else
            {
                return Assembly.Load(aData);
            }

        }

        private static Assembly AppDomain_AsmResolve(object sender, ResolveEventArgs args)
        {
            string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll", "");

            if (dllName.EndsWith("resources")) 
                return null;

            dllName += ".dll";

            string exePath = Application.ExecutablePath.Substring(0, Application.ExecutablePath.LastIndexOf('\\')) + "\\";
            string plugsPath = exePath + "Plugins\\";

            if (File.Exists(plugsPath + dllName))
            {
                return LoadAssembly(plugsPath + dllName);
            }

            if (!File.Exists(exePath + dllName))
                return null;

            return LoadAssembly(exePath + dllName);
        }


        public virtual void Dispose()
        {
            actionsDS.Dispose();
            actionsTA.Dispose();
            actionsMetaTA.Dispose();
            actionsMetaDataTA.Dispose();
        }

        #endregion

        #region Methods

        public NodeInfo FindNodeInfo(string typeName)
        {
            if (allTypes.ContainsKey(typeName))
                return allTypes[typeName];
            return null;
        }

        public List<string> GetCategoryNames()
        {
            List<string> cats = new List<string>();

            foreach (string catName in nodeTypes.Keys)
            {
                cats.Add(catName);
            }

            return cats;
        }

        public SortedDictionary<string, NodeInfo> GetCategoryTypes(string category)
        {
            if (nodeTypes.ContainsKey(category))
            {
                return nodeTypes[category];
            }
            else
            {
                return new SortedDictionary<string, NodeInfo>();
            }
        }

        public void RegisterNamespace(string nsName)
        {
            foreach (Type t in Misc.GetNamespaceTypes(nsName))
            {
                // NodeInfo nInfo = t.GetCustomAttribute<NodeInfo>();
                RegisterNodeType(t);
            }
        }

        public void RegisterNodeType(Type nodeType, NodeInfo nodeInfo = null)
        {
            NodeInfo nInfo = nodeInfo;
            if (nInfo == null)
            {
                object[] niAttObjs = nodeType.GetCustomAttributes(typeof (NodeInfo), false);
                if (niAttObjs.Length == 1)
                {
                    nInfo = (NodeInfo)niAttObjs[0];
                    nInfo.nodeType = nodeType;
                }
                else
                {
                    return;
                }
            }

            object[] niIconObjs = nodeType.GetCustomAttributes(typeof (NodeInfo.NodeInfoIcon), false);
            if (niIconObjs.Length > 0)
            {
                NodeInfo.NodeInfoIcon niIcon = (NodeInfo.NodeInfoIcon) niIconObjs[0];
                nInfo.icon = niIcon;
                niIcon.Load();
            }
            else
            {
                NodeInfo.NodeInfoIcon niIcon = new NodeInfo.NodeInfoIcon(typeof(Resources), "DefaultNode");
                nInfo.icon = niIcon;
                niIcon.Load();
            }

            string category = nInfo.Category.Trim();
            if (category.Length == 0)
                category = "General";


            if (!nodeTypes.ContainsKey(category))
                nodeTypes.Add(category, new SortedDictionary<string, NodeInfo>());


            SortedDictionary<string, NodeInfo> cat = nodeTypes[category];
            if (cat.ContainsKey(nInfo.nodeType.FullName))
            {
                Exception snExc = new Exception("Node with same name is already registered!");
                snExc.HelpLink = "err1";
            }
            else
            {
                cat.Add(nInfo.nodeType.FullName, nInfo);
            }

            if (!allTypes.ContainsKey(nodeType.FullName))
            {
                allTypes.Add(nodeType.FullName, nInfo);                
            }

        }

        public void DataLoad(CrawlerDataType dataType)
        {
            Dictionary<string, string> parameters = null;
            DataLoad(dataType, ref parameters);
        }

        public void DataLoad(CrawlerDataType dataType,  ref Dictionary<string, string> parameters)
        {
            int dataID = Null.IntFromDBNull(ref parameters, "DataID", -1);
            switch (dataType)
            {
                    case CrawlerDataType.Actions:
                        actionsTA.Fill(actionsDS.Actions);
                    break;
                    case CrawlerDataType.ActionMeta:
                        int actionType = Null.IntFromDBNull(ref parameters, "ActionType", 0);
                        if (actionType > 0)
                        {
                            actionsMetaTA.FillByActionType(actionsDS.ActionMeta, actionType);
                        }
                        else
                        {
                            actionsMetaTA.Fill(actionsDS.ActionMeta);                        
                        }
                    break;
            }
        }

        public void Initialize()
        {
            DirectoryInfo plDirInfo = new DirectoryInfo(pluginsPath);
            foreach (FileInfo plFileInfo in plDirInfo.GetFiles("*.dll"))
            {
                byte[] asmData = File.ReadAllBytes(plFileInfo.FullName);
                byte[] asmPdbData = File.ReadAllBytes(plFileInfo.FullName.Replace(".dll", ".pdb"));

                Assembly plAsm = Assembly.Load(asmData, asmPdbData);
                //Assembly.LoadFile(plFileInfo.FullName);
                Type[] plTypes = plAsm.GetTypes();
                foreach (Type plType in plTypes)
                {
                    if (plType.IsClass)
                    {
                        object[] niAttObjs = plType.GetCustomAttributes(typeof (NodeInfo), false);
                        if (niAttObjs.Length == 1)
                        {
                            NodeInfo nInfo = (NodeInfo) niAttObjs[0];
                            nInfo.nodeType = plType;
                            RegisterNodeType(plType, nInfo);
                        }
                    }
                }
            }
        }

        public void InitializeDomain(ProjectDomain domain)
        {
            DirectoryInfo plDirInfo = new DirectoryInfo(pluginsPath);
            foreach (FileInfo plFileInfo in plDirInfo.GetFiles("*.dll"))
            {
                byte[] asmData = File.ReadAllBytes(plFileInfo.FullName);
                byte[] asmPdbData = File.ReadAllBytes(plFileInfo.FullName.Replace(".dll", ".pdb"));

                Assembly plAsm = domain.domain.Load(AssemblyName.GetAssemblyName(plFileInfo.FullName)); //Assembly.Load(asmData, asmPdbData);
                //Assembly.LoadFile(plFileInfo.FullName);
                Type[] plTypes = plAsm.GetTypes();
                foreach (Type plType in plTypes)
                {
                    if (plType.IsClass)
                    {
                        object[] niAttObjs = plType.GetCustomAttributes(typeof(NodeInfo), false);
                        if (niAttObjs.Length == 1)
                        {
                            NodeInfo nInfo = (NodeInfo)niAttObjs[0];
                            nInfo.nodeType = plType;
                            RegisterNodeType(plType, nInfo);
                        }
                    }
                }
            }
        }

        #endregion

        #region Static Methods

        public static void SetConnectionString(string connString)
        {
            string deffConnStr = Properties.Settings.Default.pxp_crawler_ConnectionString;
            if (Properties.Settings.Default.pxp_crawler_ConnectionString != connString)
            {
                Properties.Settings.Default.PropertyValues["pxp_crawler_ConnectionString"].PropertyValue = connString;
            }
        }

        #endregion

        #region Properties

        public string ReferencesPath
        {
            get { return refresPath; }
            set { refresPath = value; }
            }

        public SortedDictionary<string, SortedDictionary<string, NodeInfo>> NodeTypes
        {
            get
            {
                return nodeTypes;
            }
        }

        #endregion

    }
}
