using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;

using VRH.Common;
using VRH.Log4Pro.MultiLanguageManager;
using Vrh.XmlProcessing;

namespace Vrh.iScheduler
{
    /// <summary>
    /// Ütemezés.
    /// </summary>
    public class SchedulerXmlParser : XmlParser
    {
        #region Built'in classes

        #region ElementNames class
        /// <summary>
        /// Az XML fájlban ilyen elem nevek találhatóak egy "WebAppAction" elemben.
        /// </summary>
        public new class ElementNames : XmlLinqBase.ElementNames
        {
            /// <summary>
            /// 'DatabaseConnectionString' elem név.
            /// </summary>
            public const string DATABASECONNECTIONSTRING = "DatabaseConnectionString";

            /// <summary>
            /// 'ObjectType' elem név.
            /// </summary>
            public const string OBJECTTYPE = "ObjectType";

            /// <summary>
            /// 'GroupId' elem név.
            /// </summary>
            public const string GROUPID = "GroupId";

            /// <summary>
            /// 'Manager' elem név.
            /// </summary>
            public const string MANAGER = "Manager";

            /// <summary>
            /// 'Plugin' elem név.
            /// </summary>
            public const string PLUGIN = "Plugin";

            /// <summary>
            /// 'ScheduledObjectAccess' elem név.
            /// </summary>
            public const string SCHEDULEDOBJECTACCESS = "ScheduledObjectAccess";

            /// <summary>
            /// 'ScheduledObjectActions' elem név.
            /// </summary>
            public const string SCHEDULEDOBJECTACTIONS = "ScheduledObjectActions";

            #region ScheduledObjectActions elmeinek neve
            /// <summary>
            /// 'CheckObjects' elem név.
            /// </summary>
            public const string CHECKOBJECTS = "CheckObjects";

            /// <summary>
            /// 'Execute' elem név.
            /// </summary>
            public const string EXECUTE = "Execute";

            /// <summary>
            /// 'ListObjects' elem név.
            /// </summary>
            public const string LISTOBJECTS = "ListObjects";

            /// <summary>
            /// 'ListOperations' elem név.
            /// </summary>
            public const string LISTOPERATIONS = "ListOperations";

            /// <summary>
            /// 'ObjectEditor' elem név.
            /// </summary>
            public const string OBJECTEDITOR = "ObjectEditor";

            /// <summary>
            /// 'ObjectManager' elem név.
            /// </summary>
            public const string OBJECTMANAGER = "ObjectManager";

            #endregion ScheduledObjectActions elmeinek neve

            /// <summary>
            /// 'WebAppAction' elem név.
            /// </summary>
            public const string WEBAPPACTION = "WebAppAction";

            /// <summary>
            /// 'WordCodePrefix' elem név.
            /// </summary>
            //public const string WORDCODEPREFIX = "WordCodePrefix";
        }
        #endregion ElementNames class

        #region AttributeNames static class
        /// <summary>
        /// Az XML fájlban ilyen attribútum nevek találhatóak egy 'Plugin' elemben.
        /// </summary>
        public static class AttributeNames
        {
            /// <summary>
            /// 'Mode' attribútum név a "ScheduledObjectAccess" elemben.
            /// </summary>
            public const string MODE = "Mode";
        }
        #endregion AttributeNames static class

        #endregion Built'in classes

        #region Properties

        /// <summary>
        /// Az XML-ben megadott objektum típus.
        /// </summary>
        public string ObjectType { get; private set; }

        /// <summary>
        /// Az objektum típuson belüli csoportosításnak az azonosítója.
        /// </summary>
        public string ObjectGroupId { get; private set; }

        /// <summary>
        /// Az objektum elérési módja. Ez is az XML-ben található érték.
        /// </summary>
        public AccessMode AccessMode { get; set; }

        /// <summary>
        /// Adatbázishoz való kapcsolódáshoz már feloldott szabványos string.
        /// </summary>
        public string ConnectionString { get; private set; }

        /// <summary>
        /// A xml szövegek fordításakor használatos szókód előzmény.
        /// A fordításhoz a WordCodePrefix + "." + WordCode szókódot fogja használni.
        /// </summary>
        //public string WordCodePrefix { get; private set; }

        /// <summary>
        /// Plugin elérési mód esetén az adatok itt találhatóak.
        /// </summary>
        public PluginElement Plugin { get; private set; }

        /// <summary>
        /// WebAppAction elérési mód esetén az adatok itt találhatóak.
        /// </summary>
        public WebAppActionElement WebAppAction { get; private set; }

        /// <summary>
        /// Az időzített objektum akcióiban használható változók szótára.
        /// </summary>
        public VariableDictionary UrlVars { get; set; }

        #region URLs for the actions of the scheduled object

        #region CheckObjects property
        /// <summary>
        /// Az ütemezendő objektum létezésének ellenőrzése.
        /// Nem kötelező, ha null, akkor nem lesz konzisztencia nyomógomb.
        /// </summary>
        public UrlElement CheckObjects
        {
            get
            {
                if (!m_IsInitializedCheckObjects)
                {
                    if (this.AccessMode == AccessMode.Plugin)
                    {
                        m_CheckObjects = new UrlElement
                        {
                            Area = SchConst.AREA,
                            Controller = SchConst.CTRL,
                            Action = SchConst.Actions.CHECKOBJECTS,
                            UrlParameters = new List<UrlElement.UrlParameter>
                            {
                                new UrlElement.UrlParameter { Name = "xml", Value = this.XmlParserConnectionString },
                                new UrlElement.UrlParameter { Name = "groupId", Value = this.ObjectGroupId },
                                new UrlElement.UrlParameter { Name = "list", Value = $"@{SchConst.UrlVars.OBJECTLIST}@"},
                            }
                        };
                    }
                    else
                    {
                        XElement actionsXE = this.RootElement.Element(ElementNames.SCHEDULEDOBJECTACTIONS);
                        if (actionsXE != null)
                        {
                            XElement xe = actionsXE.Element(ElementNames.CHECKOBJECTS);
                            if (xe != null)
                            {
                                m_CheckObjects = new UrlElement(xe);
                            }
                        }
                    }
                    m_IsInitializedCheckObjects = true;
                }
                return m_CheckObjects;
            }
        }
        private UrlElement m_CheckObjects = null;
        private bool m_IsInitializedCheckObjects = false;
        #endregion CheckObjects property

        #region ListObjects property
        /// <summary>
        /// Akció, mely megadja milyen ütemezendő objektumok lehetségesek.
        /// </summary>
        public UrlElement ListObjects
        {
            get
            {
                if (m_ListObjects == null)
                {
                    if (this.AccessMode == AccessMode.Plugin)
                    {
                        m_ListObjects = new UrlElement 
                        {
                            Area = SchConst.AREA,
                            Controller = SchConst.CTRL,
                            Action = SchConst.Actions.LISTOBJECTS,
                            UrlParameters = new List<UrlElement.UrlParameter> 
                            {
                                new UrlElement.UrlParameter { Name = "xml", Value = this.XmlParserConnectionString },
                                new UrlElement.UrlParameter { Name = "groupId", Value = this.ObjectGroupId },
                            }
                        };
                    }
                    else
                    {
                        XElement actionsXE = this.RootElement.Element(ElementNames.SCHEDULEDOBJECTACTIONS);
                        if (actionsXE == null)
                        {
                            throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.SCHEDULEDOBJECTACTIONS));
                        }
                        XElement xe = actionsXE.Element(ElementNames.LISTOBJECTS);
                        if (xe == null)
                        {
                            throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.LISTOBJECTS));
                        }
                        m_ListObjects = new UrlElement(xe);
                    }
                }
                return m_ListObjects;
            }
        }
        private UrlElement m_ListObjects = null;
        #endregion ListObjects property 

        #region ListOperations property
        /// <summary>
        /// Akció, mely megadja milyen műveletek végezhetőek el az objektumokon.
        /// </summary>
        public UrlElement ListOperations
        {
            get
            {
                if (m_ListOperations == null)
                {
                    if (this.AccessMode == AccessMode.Plugin)
                    {
                        m_ListOperations = new UrlElement
                        {
                            Area = SchConst.AREA,
                            Controller = SchConst.CTRL,
                            Action = SchConst.Actions.LISTOPERATIONS,
                            UrlParameters = new List<UrlElement.UrlParameter>
                            {
                                new UrlElement.UrlParameter { Name = "xml", Value = this.XmlParserConnectionString }
                            }
                        };
                    }
                    else
                    {
                        XElement actionsXE = this.RootElement.Element(ElementNames.SCHEDULEDOBJECTACTIONS);
                        if (actionsXE == null)
                        {
                            throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.SCHEDULEDOBJECTACTIONS));
                        }
                        XElement xe = actionsXE.Element(ElementNames.LISTOPERATIONS);
                        if (xe == null)
                        {
                            throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.LISTOPERATIONS));
                        }
                        m_ListOperations = new UrlElement(xe);
                    }
                }
                return m_ListOperations;
            }
        }
        private UrlElement m_ListOperations = null;
        #endregion ListOperations property

        #region ObjectManager property
        /// <summary>
        /// Az ütemezendő objektumhoz tartozó kezelő felület hívása.
        /// Nem kötelező, ha null, akkor nem lesz objektum manager gomb.
        /// </summary>
        public UrlElement ObjectManager
        {
            get
            {
                if (!m_IsInitializedObjectManager)
                {
                    XElement actionsXE = this.RootElement.Element(ElementNames.SCHEDULEDOBJECTACTIONS);
                    if (actionsXE != null)
                    {
                        XElement xe = actionsXE.Element(ElementNames.OBJECTMANAGER);
                        if (xe != null)
                        {
                            m_ObjectManager = new UrlElement(xe);
                        }
                    }
                    m_IsInitializedObjectManager = true;
                }
                return m_ObjectManager;
            }
        }
        private UrlElement m_ObjectManager = null;
        private bool m_IsInitializedObjectManager = false;
        #endregion ObjectManager property

        #region ObjectEditor property
        /// <summary>
        /// Az ütemezendő objektum szerkesztését eredményező akció.
        /// Nem kötelező, ha null, akkor nem lesz objektum szerkesztő gomb.
        /// </summary>
        public UrlElement ObjectEditor
        {
            get
            {
                if (!m_IsInitializedObjectEditor)
                {
                    XElement actionsXE = this.RootElement.Element(ElementNames.SCHEDULEDOBJECTACTIONS);
                    if (actionsXE != null)
                    {
                        XElement xe = actionsXE.Element(ElementNames.OBJECTEDITOR);
                        if (xe != null)
                        {
                            m_ObjectEditor = new UrlElement(xe);
                        }
                    }
                    m_IsInitializedObjectEditor = true;
                }
                return m_ObjectEditor;
            }
        }
        private UrlElement m_ObjectEditor = null;
        private bool m_IsInitializedObjectEditor = false;
        #endregion ObjectEditor property

        #region Execute property
        /// <summary>
        /// Az ütemezett objektumon végrehajtandó művelet URL-je.
        /// Ezt használja a ScheduleExecute is.
        /// </summary>
        public UrlElement Execute
        {
            get
            {
                if (m_Execute == null)
                {
                    XElement actionsXE = this.RootElement.Element(ElementNames.SCHEDULEDOBJECTACTIONS);
                    if (actionsXE == null)
                    {
                        throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.SCHEDULEDOBJECTACTIONS));
                    }
                    XElement xe = actionsXE.Element(ElementNames.EXECUTE);
                    if (xe == null)
                    {
                        throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.EXECUTE));
                    }
                    m_Execute = new UrlElement(xe);
                }
                return m_Execute;
            }
        }
        private UrlElement m_Execute = null;
        #endregion Execute property 

        #endregion URLs for the actions of the scheduled object

        #region Manager property
        /// <summary>
        /// A megjelenítő felülethez beállított értékek.
        /// </summary>
        public ManagerElement Manager
        {
            get
            {
                if (m_Manager == null)
                {
                    XElement managerXE = this.RootElement.Element(ElementNames.MANAGER);
                    if (managerXE == null)
                    {
                        throw new ApplicationException(string.Format(
                            Messages.ERR_MISSINGELEMENT, ElementNames.MANAGER
                        ));
                    }
                    m_Manager = new ManagerElement(managerXE, this.LCID);
                    //m_Manager = new ManagerElement(managerXE, this.LCID, this.WordCodePrefix);

                    #region Beépített gombok URL-jének beállítása
                    if (m_Manager.Buttons != null && m_Manager.Buttons.Any())
                    {
                        var sysButtons = m_Manager.Buttons.Where(x => x.ButtonType == ButtonTypes.Sys);
                        if (sysButtons != null && sysButtons.Any())
                        {
                            foreach (var bttn in sysButtons)
                            {
                                bttn.Url = new UrlElement() { Area = SchConst.AREA, Controller = SchConst.CTRL };
                                switch (bttn.Name)
                                {
                                    #region ConsistencyCheck
                                    case ButtonElement.SysButtonNames.ConsistencyCheck:
                                        bttn.Url.Action = SchConst.Actions.CONSISTENCYCHECK;
                                        bttn.Url.UrlParameters = new List<UrlElement.UrlParameter>
                                    {
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.OBJECTTYPE,
                                            Value = this.ObjectType,
                                        },
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.GROUPID,
                                            Value = this.ObjectGroupId,
                                        },
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.CONNECTIONSTRING,
                                            Value = this.ConnectionString,
                                        }
                                    };
                                        break;
                                    #endregion ConsistencyCheck

                                    #region NewSchedule
                                    case ButtonElement.SysButtonNames.NewSchedule:
                                        //bttn.IsDialog = true;
                                        bttn.Url.Action = SchConst.Actions.EDITOR;
                                        bttn.Url.UrlParameters = new List<UrlElement.UrlParameter>
                                    {
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.XML,
                                            Value = this.XmlParserConnectionString,
                                        },
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.GROUPID,
                                            Value = this.ObjectGroupId,
                                        }
                                    };
                                        break;
                                    #endregion NewSchedule

                                    #region GoToListView
                                    case ButtonElement.SysButtonNames.GoToListView:
                                        bttn.Url.Action = SchConst.Actions.MANAGER;
                                        bttn.Url.UrlParameters = new List<UrlElement.UrlParameter>
                                    {
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.XML,
                                            Value = this.XmlParserConnectionString,
                                        },
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.GROUPID,
                                            Value = this.ObjectGroupId,
                                        },
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.DISPLAY,
                                            Value = DisplayMode.List.ToString(),
                                        }
                                    };
                                        break;
                                    #endregion GoToListView

                                    #region GoToMonthView (Calendar)
                                    case ButtonElement.SysButtonNames.GoToMonthView:
                                        bttn.Url.Action = SchConst.Actions.MANAGER;
                                        bttn.Url.UrlParameters = new List<UrlElement.UrlParameter>
                                    {
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.XML,
                                            Value = this.XmlParserConnectionString,
                                        },
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.GROUPID,
                                            Value = this.ObjectGroupId,
                                        },
                                        new UrlElement.UrlParameter()
                                        {
                                            Name = SchConst.Params.DISPLAY,
                                            Value = DisplayMode.Calendar.ToString(),
                                        }
                                    };
                                        break;
                                    #endregion GoToMonthView (Calendar)

                                    #region ObjectManager
                                    case ButtonElement.SysButtonNames.ObjectManager:
                                        bttn.Url = this.ObjectManager;
                                        break;
                                    #endregion ObjectManager

                                    default:
                                        break;
                                }
                            }
                        }
                    }
                    #endregion Beépített gombok URL-jének beállítása
                }
                return m_Manager;
            }
        }
        private ManagerElement m_Manager = null;
        #endregion Manager property

        /// <summary>
        /// Az XML fájl fizikai elérési útvonala.
        /// </summary>
        public string XmlParserConnectionString { get; private set; }

        /// <summary>
        /// A paraméterező fájl feldolgozása közben keletkezett hibák felsorolása.
        /// </summary>
        public List<string> ErrorList { get; private set; } = new List<string>();

        /// <summary>
        /// Jelöli, hogy a használt típus beépített-e.
        /// </summary>
        public bool IsEmbededObjectType { get; private set; } = false;

        /// <summary>
        /// Ha IsEmbededObjectType igaz, akkor megmutatja, hogy melyik beépített típusról van szó.
        /// </summary>
        public EmbededObjectTypes EmbededObjectType { get; set; }

        #endregion Properties

        #region Constructor
        /// <summary>
        /// Paraméterező XML fájl feldolgozása.
        /// </summary>
        /// <param name="xmlc">XmlParser kapcsolati objektum.</param>
        /// <param name="appPath">>Az alkalmazás alap mappája. Ez alatt van az App_Path.</param>
        /// <param name="lcid">A környezetben érvényes nyelvi kód. Ha üres, akkor az XmlParser-ben beállított lesz érvényes.</param>
        /// <param name="otherVars">A környezetben az XmlParser részére létrehozott változók.</param>
        /// <param name="groupId">URL paraméterben érkezhet, amely felülírja az XML-ben megadottat.</param>
        public SchedulerXmlParser(
            XmlConnection xmlc,
            string appPath,
            string lcid,
            Dictionary<string, string> otherVars,
            string groupId = null) : base(xmlc, appPath, lcid, otherVars)
        {
            try
            {
                this.UrlVars = new VariableDictionary(this.LCID);
                this.XmlParserConnectionString = xmlc.ToString();

                #region ObjectType beállítása és a beépítettség ellenőrzése, 
                this.ObjectType = GetValue(this.RootElement.Element(ElementNames.OBJECTTYPE), "");
                if (String.IsNullOrWhiteSpace(this.ObjectType))
                {
                    AddError($"The \"{ElementNames.OBJECTTYPE}\" element is missing or empty!");
                }
                if (Enum.TryParse(this.ObjectType, true, out EmbededObjectTypes eot))
                {
                    this.IsEmbededObjectType = true;
                    this.EmbededObjectType = eot;
                }
                #endregion ObjectType beállítása és a beépítettség ellenőrzése

                #region ObjectGroupId beállítása és ellenőrzése
                // Ha paraméterben érkezik, akkor az lesz a groupId
                if (string.IsNullOrWhiteSpace(groupId))
                {
                    this.ObjectGroupId = GetValue(this.RootElement.Element(ElementNames.GROUPID), "");
                    if (string.IsNullOrWhiteSpace(this.ObjectGroupId) || this.ObjectGroupId == SchConst.Groups.All)
                    {
                        AddError($"The GroupId element is missing, empty or equal to \"{SchConst.Groups.All}\" !");
                    }
                }
                else
                {
                    this.ObjectGroupId = groupId;
                }
                #endregion ObjectGroupId beállítása és ellenőrzése

                #region ConnectionString beállítása
                string csName = GetValue(this.RootElement.Element(ElementNames.DATABASECONNECTIONSTRING), "");
                if (string.IsNullOrWhiteSpace(csName))
                {
                    csName = SchConst.DEFAULT_SQLCONNECTIONSTRINGNAME;
                }
                try
                {
                    this.ConnectionString = ConnectionStringStore.GetSQL(csName);
                }
                catch (Exception ex)
                {
                    this.ErrorList.AddRange(VrhCommon.ErrorListBuilder(ex));
                }
                #endregion ConnectionString beállítása

                #region ObjectAccessMode ellenőrzése és beolvasása
                XElement soaXE = this.RootElement.Element(ElementNames.SCHEDULEDOBJECTACCESS);
                if (soaXE == null)
                {
                    AddError(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.SCHEDULEDOBJECTACCESS));
                }
                else
                {
                    if (this.IsEmbededObjectType)
                    {
                        this.AccessMode = AccessMode.Plugin;
                    }
                    else
                    {
                        XAttribute modeXA = soaXE.Attribute(AttributeNames.MODE);
                        if (modeXA == null)
                        {
                            AddError(string.Format(Messages.ERR_MISSINGATTRIBUTE, AttributeNames.MODE, ElementNames.SCHEDULEDOBJECTACCESS));
                        }
                        else
                        {
                            if (string.IsNullOrWhiteSpace(modeXA.Value))
                            {
                                AddError(string.Format(Messages.ERR_REQUIREDATTRIBUTE, AttributeNames.MODE, ElementNames.SCHEDULEDOBJECTACCESS));
                            }
                            else
                            {
                                this.AccessMode = GetEnumValue(modeXA, AccessMode.Plugin);
                            }
                        }
                    }
                }
                #endregion ObjectAccessMode ellenőrzése és beolvasása

                #region Plugin vagy WebAppAction beállítása
                // Az elemeket a ScheduledObjectAccess (soaXE) elemben kell keresni.
                if (this.AccessMode == AccessMode.Plugin)
                {
                    XElement pluginXE = soaXE.Element(ElementNames.PLUGIN);
                    if (pluginXE == null)
                    {
                        throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.PLUGIN));
                    }
                    try
                    {
                        this.Plugin = new PluginElement(pluginXE);
                    }
                    catch (Exception ex)
                    {
                        this.ErrorList.AddRange(VrhCommon.ErrorListBuilder(ex));
                    }
                }
                else
                {
                    XElement webappXE = soaXE.Element(ElementNames.WEBAPPACTION);
                    if (webappXE == null)
                    {
                        throw new ApplicationException(string.Format(Messages.ERR_MISSINGELEMENT, ElementNames.PLUGIN));
                    }
                    try
                    {
                        this.WebAppAction = new WebAppActionElement(webappXE);
                    }
                    catch (Exception ex)
                    {
                        this.ErrorList.AddRange(VrhCommon.ErrorListBuilder(ex));
                    }
                }
                #endregion Plugin vagy WebAppAction beállítása

                #region UrlVars feltöltése és a tudható értékek beállítása
                this.UrlVars.Add(SchConst.UrlVars.Dict());
                this.UrlVars[SchConst.UrlVars.GROUPID] = this.ObjectGroupId;
                this.UrlVars[SchConst.UrlVars.OBJECTTYPE] = this.ObjectType;
                this.UrlVars[SchConst.UrlVars.XMLCS] = this.XmlParserConnectionString;
                #endregion UrlVars feltöltése és a tudható értékek beállítása
            }
            catch (Exception ex)
            {
                this.ErrorList.AddRange(VrhCommon.ErrorListBuilder(ex));
            }
            if (this.ErrorList.Any())
            {
                this.ErrorList.Insert(0, $"Error occured when the {nameof(SchedulerXmlParser)} initialization!");
            }
        }
        #endregion Constructor

        #region Private methods

        private void AddError(string message)
        {
            this.ErrorList.Add(message);
        }

        #endregion Private methods
    }
}