using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data.Entity.Core.Objects;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

using Vrh.XmlProcessing;

namespace Vrh.OneReport
{
    /// <summary>
    /// A "Report/RdlDatasets/Dataset" elemet reprezentáló osztály.
    /// </summary>
    public class Dataset : XmlLinqBase
    {
        #region AttributeNames static class
        /// <summary>
        /// Az XML fájlban ilyen attribútum nevek találhatóak egy "Output" elemben.
        /// </summary>
        public static class AttributeNames
        {
            /// <summary>
            /// 'ConnectionString' attribute
            /// </summary>
            public const string CONNECTIONSTRING = "ConnectionString";
             /// <summary>
             /// 'Name' attribútum név a 'Dataset' elemben.
             /// </summary>
            public const string NAME = "Name";

            /// <summary>
            /// 'Name' attribútum név a 'Dataset' elemben.
            /// </summary>
            public const string PLUGINID = "PluginId";

            /// <summary>
            /// 'Type' attribútum név a 'Dataset' elemben.
            /// </summary>
            public const string TYPE = "Type";

            /// <summary>
            /// A meghívandó view neve
            /// </summary>
            public const string PLUGINVIEWNAME = "PluginViewName";
        }
        #endregion AttributeNames static class

        #region Properties

        /// <summary>
        /// A Dataset neve.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// A Dataset típusa.
        /// </summary>
        public DatasetTypes Type { get; set; }

        /// <summary>
        /// A "Command" futtatásához szükséges kapcsolati sztring. 
        /// Már nem egy név, hanem egy feloldott, kész kapcsolati sztring.
        /// </summary>
        public string ConnectionString { get; set; }

        /// <summary>
        /// Egy olyan parancs, mely az adott típusú adatforrás előállításához kell.
        /// </summary>
        public string Command { get; set; }

        /// <summary>
        /// Export után ide kerül, hogy ez a Dataset milyen fájlba lett exportálva.
        /// </summary>
        public string ExportFile { get;set; }

        /// <summary>
        /// Plugin típus esetén a tartalmazó dll teljes elérési útja
        /// </summary>
        public string PluginAssemblyPath { get; set; }

        /// <summary>
        /// Plugin típus esetén a tartalmazó típus teljes neve
        /// </summary>
        public string PluginTypeFullName { get; set; }

        /// <summary>
        /// Plugin típus esetén a tartalmazó dll teljes elérési útja
        /// </summary>
        public string PluginMethodName{ get; set; }

        /// <summary>
        /// Plugin típus esetén a meghívandó view neve
        /// </summary>
        public string PluginViewName { get; set; }

        /// <summary>
        /// Plugin típus esetén a meghívandó metódusnak átadandó xml paraméter struktúrára mutató xml sonnectionstring
        /// </summary>
        public string PluginAssemblyXml { get; set; }

        /// <summary>
        /// A teljes Dataset leírót tartalmazó XElement, amelynek egy child eleme (a neve a plugin belügye) a plugin paraméterei
        /// </summary>
        public XElement DatasetXml { get; set; }

        /// <summary>
        /// DatasetTypes: SQL, Plugin, DataPlugin, SQLTextPlugin,
        /// </summary>
        public DatasetTypes DatasetType { get; set; }

        /// <summary>
        /// A riport adatait előállító plugin kerül ide bele
        /// </summary>
        public object Plugin { get; set; }

        /// <summary>
        /// A plugin-ban levő meghívandó metódus leírója
        /// </summary>
        public MethodInfo GetMethod { get; set; }

        #endregion Properties

        #region Constructor
        /// <summary>
        /// Példány létrehozása egy XElement alapján.
        /// </summary>
        /// <param name="reportid">A riport id-ja; exception üzenetben kerül CSAK felhasználásra</param>
        /// <param name="datasetxelement">A "Dataset" elem.</param>
        /// /// <param name="datasourcelist">Alapértelmezett adatok a plugin típusú működéshez</param>
        /// <param name="defaultconnectionstringstorename">az alapértelmezett kapcsolat leíró arra az esetre, ha a datasetxml-ben nincs megadva</param>
        public Dataset(string reportid,XElement datasetxelement,List<PluginDescriptor> datasourcelist,string defaultconnectionstringstorename)
        {
            if (datasetxelement != null)
            {
                this.Name = GetValue(AttributeNames.NAME, datasetxelement, "", true, true);
                this.DatasetType = GetEnumValue(datasetxelement.Attribute(AttributeNames.TYPE), DatasetTypes.DataPlugin);
                switch (this.DatasetType)
                {
                    default: throw new ApplicationException($"Unknown Dataset type! Type:{this.Type}. Report Id:{reportid}.");
                    case DatasetTypes.SQL:
                        {
                            this.Type = DatasetTypes.SQL;
                            var connectionstringstorename = GetValue(AttributeNames.CONNECTIONSTRING, datasetxelement, (string)null);
                            if (string.IsNullOrWhiteSpace(connectionstringstorename)) { connectionstringstorename = defaultconnectionstringstorename;}
                            this.ConnectionString = ConnectionStringStore.GetSQL(connectionstringstorename);
                            this.Command = GetValue(datasetxelement, "", true, true);
                            break;
                        }
                    case DatasetTypes.DataPlugin:
                    case DatasetTypes.SQLTextPlugin:
                        {
                            var pluginid = GetValue(datasetxelement.Attribute(AttributeNames.PLUGINID), "");
                            var plugindescriptor = string.IsNullOrWhiteSpace(pluginid) ? null : datasourcelist.Find(ds => ds.Id == pluginid);
                            if (plugindescriptor==null) throw new ApplicationException($"No Plugin specified or the referenced plugin name does not exist! Type:{this.Type}. Report Id:{reportid}. Plugin name:{pluginid}.");
                            this.Type = plugindescriptor.Type==PluginTypes.DataPlugin?DatasetTypes.DataPlugin:DatasetTypes.SQLTextPlugin;
                            this.Command = "";
                            this.ConnectionString = plugindescriptor.ConnectionString;
                            this.PluginAssemblyPath = plugindescriptor.PluginAssemblyPath;
                            this.PluginTypeFullName = plugindescriptor.PluginTypeFullName;
                            this.PluginAssemblyXml = plugindescriptor.PluginAssemblyXml;
                            this.PluginMethodName = plugindescriptor.PluginMethodName;
                            this.PluginViewName = GetValue(AttributeNames.PLUGINVIEWNAME, datasetxelement, (string)null);
                            if (string.IsNullOrWhiteSpace(this.PluginViewName)) { this.PluginViewName = this.Name; }
                            this.Plugin = GetPlugin(this.DatasetType, this.PluginAssemblyPath, this.PluginTypeFullName, this.PluginMethodName, new object[] { this.ConnectionString }, out MethodInfo sqlgetmethod);
                            this.GetMethod = sqlgetmethod;
                            this.DatasetXml = datasetxelement;
                            break;
                        }
                }
            }
        }
        private object GetPlugin(DatasetTypes datasettype, string assemblypath, string plugintypename, string pluginmethodname, object[] plugintypeconstructorparameters, out MethodInfo getmethod)
        {
            try
            {
                object plugin = null;
                getmethod = null;
                //Assembly asm = Assembly.LoadFile(assemblypath);
                Assembly asm = Assembly.LoadFrom(assemblypath);
                if (string.IsNullOrWhiteSpace(plugintypename)) plugintypename = typeof(IOneReport).FullName;
                if (string.IsNullOrWhiteSpace(pluginmethodname)) pluginmethodname = datasettype == DatasetTypes.DataPlugin ? nameof(IOneReport.GetData) : datasettype == DatasetTypes.SQLTextPlugin ? nameof(IOneReport.GetSqlText) : null;

                var ptype = asm.GetExportedTypes().FirstOrDefault(x => x.FullName == plugintypename && x.IsAssignableFrom(x));
                if (ptype == null) { throw new ApplicationException("Type not found!"); }
                plugin = Activator.CreateInstance(ptype, plugintypeconstructorparameters);
                if (plugin == null) { throw new ApplicationException("Type constructor failed!"); }
                getmethod = ptype.GetMethod(pluginmethodname);
                if (getmethod == null) { throw new ApplicationException("Method not found!"); }
                return plugin;
            }
            catch (Exception ex)
            {
                if (string.IsNullOrWhiteSpace(pluginmethodname)) { pluginmethodname = nameof(IOneReport.GetSqlText); }
                throw new ApplicationException($"ERROR Not found!!! the implementation of the referred interface or method in the assembly! Assembly='{this.PluginAssemblyPath}', type='{plugintypename}',method='{pluginmethodname}'", ex);
                return null;
            }
        }
        //private void LoadPluginBAK(string assemblypath, string plugintypename, string pluginmethodname, out IOneReport plugin, out MethodInfo getsqlmethod)
        //{
        //    try
        //    {
        //        plugin = null;
        //        getsqlmethod = null;
        //        //Assembly asm = Assembly.LoadFile(assemblypath);
        //        Assembly asm = Assembly.LoadFrom(assemblypath);
        //        if (!string.IsNullOrWhiteSpace(plugintypename))
        //        {
        //            System.Type type = asm.GetExportedTypes().FirstOrDefault(x => x.FullName == plugintypename && x.IsAssignableFrom(x));
        //            if (type == null) { throw new ApplicationException(""); }
        //            plugin = (IOneReport)Activator.CreateInstance(type);
        //            if (string.IsNullOrWhiteSpace(pluginmethodname)) { pluginmethodname = nameof(IOneReport.GetSqlText); }
        //            getsqlmethod = type.GetMethod(pluginmethodname);
        //            if (getsqlmethod == null) { throw new ApplicationException(""); }
        //        }
        //        else
        //        {
        //            plugintypename = typeof(IOneReport).FullName;
        //            pluginmethodname = nameof(IOneReport.GetSqlText);
        //            System.Type type = asm.GetExportedTypes().FirstOrDefault(x => typeof(IOneReport).IsAssignableFrom(x));
        //            if (type == null) { throw new ApplicationException(""); }
        //            getsqlmethod = null;
        //            plugin = (IOneReport)Activator.CreateInstance(type);
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        if (string.IsNullOrWhiteSpace(pluginmethodname)) { pluginmethodname = nameof(IOneReport.GetSqlText); }
        //        throw new ApplicationException($"The implementation of the referred interface or method in the assembly, or the assembly (DLL) itself was not found! Assembly='{this.PluginAssemblyPath}', type='{plugintypename}',method='{pluginmethodname}'", ex);
        //    }
        //}
        #endregion Constructor
    }
}
