using System.Text;
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Xml.Linq;
using Hangfire;
using Microsoft.Web.Administration;
using Vrh.Logger;
using System.Configuration;
using System.Reflection;

namespace Vrh.Web.WebServerHostedServiceStarterNS
{
    /// <summary>
    /// TODO: ezt egy olyan helyre kellene rakni, ahol mindenhova referálható (az Vrh.Interfaces.dll egyre jobb ötletnek tűnik! Kár hogy nem hallgattam Antira!)
    /// </summary>
    public interface IWebServerHostedServiceStarter 
    {
        void Start();
        void Stop();
    }
    public class WebServerHostedServiceStarter : IRegisteredObject, IProcessHostPreloadClient
    {
        /// <summary>
        /// Egy hosted service elindítását, paraméterezését, konfigurálását és a külső iniciátor meghívást végzi.
        /// </summary>
        /// <param name="tt">
        /// az indító és leállító metódusokat tartalmazó osztály; 
        /// IWebServerHostedServiceStarter interfészt meg kell valósítsa, bár ezt nem ellenőrzi, csak azt, 
        /// hogy az ezt jelentő 'public static void Start();' és 'public static void Stop();' metódusok megvannak-e.
        /// </param>
        public static void Start(Type tt)
        {
            var le = new DCLogEntry(LogLevel.Information, $"{typeof(WebServerHostedServiceStarter).FullName}.{nameof(Start)}");
            try
            {
                //if (!tt.GetInterfaces().Contains(typeof(IWebServerHostedServiceStarter))) throw new InterfaceErrorApplicationException(tt.FullName);

                _SetupAutoStart<WebServerHostedServiceStarter>(le: le);

                var startmethod =  tt.GetMethod(nameof(IWebServerHostedServiceStarter .Start), BindingFlags.Public | BindingFlags.Static);
                var stopmethod = tt.GetMethod(nameof(IWebServerHostedServiceStarter .Stop), BindingFlags.Public | BindingFlags.Static);
                if (startmethod == null || startmethod.ReturnType != typeof(void) || startmethod.GetParameters().Count() > 0) throw new InterfaceErrorApplicationException(tt.FullName,nameof(IWebServerHostedServiceStarter .Start));
                if (stopmethod == null || stopmethod.ReturnType != typeof(void) || stopmethod.GetParameters().Count() > 0) throw new InterfaceErrorApplicationException(tt.FullName, nameof(IWebServerHostedServiceStarter .Stop));
                IISHostedServiceStarterInstance._RegisterExternalInitializer((Action)Delegate.CreateDelegate(typeof(Action), startmethod), (Action)Delegate.CreateDelegate(typeof(Action), stopmethod), le);

                IISHostedServiceStarterInstance._StartExternalInitializers(le);
            }
            catch (InterfaceErrorApplicationException ex) { le.AddExceptionResult(ex); le.SetLogLevel(LogLevel.Error); }
            catch (Exception ex) { le.AddExceptionResult(ex); le.SetLogLevel(LogLevel.Error); }
            finally { le.Write(); }
        }

        /// <summary>
        /// Egy hosted service elindítását, paraméterezését, konfigurálását és külső iniciátorok meghívást végzi.
        /// </summary>
        /// <param name="externalinitializerstartmethod">az indító metódus</param>
        /// <param name="externalinitializerstopmethod">a leállító metódus</param>
        public static void Start(List<Type> ttList)
        {
            var le = new DCLogEntry(LogLevel.Information, $"{typeof(WebServerHostedServiceStarter).FullName}.{nameof(Start)}");
            try
            {
                //if (!tt.GetInterfaces().Contains(typeof(IInterface))) throw new Exception ($"{tt.FullName} does not implement {nameof(IInterface)}!");

                _SetupAutoStart<WebServerHostedServiceStarter>(le: le);
                foreach (var tt in ttList)
                {
                    try 
                    {
                        var startmethod = tt.GetMethod(nameof(IWebServerHostedServiceStarter .Start), BindingFlags.Public | BindingFlags.Static);
                        var stopmethod = tt.GetMethod(nameof(IWebServerHostedServiceStarter .Stop), BindingFlags.Public | BindingFlags.Static);
                        if (startmethod == null || startmethod.ReturnType != typeof(void) || startmethod.GetParameters().Count() > 0) throw new InterfaceErrorApplicationException(tt.FullName, nameof(IWebServerHostedServiceStarter .Start));
                        if (stopmethod == null || stopmethod.ReturnType != typeof(void) || stopmethod.GetParameters().Count() > 0) throw new InterfaceErrorApplicationException(tt.FullName, nameof(IWebServerHostedServiceStarter .Stop));
                        IISHostedServiceStarterInstance._RegisterExternalInitializer((Action)Delegate.CreateDelegate(typeof(Action), startmethod), (Action)Delegate.CreateDelegate(typeof(Action), stopmethod), le);
                    }
                    catch (InterfaceErrorApplicationException ex) { le.AddExceptionResult(ex); le.SetLogLevel(LogLevel.Error); }
                }
                IISHostedServiceStarterInstance._StartExternalInitializers(le);
            }
            catch (Exception ex) { le.AddExceptionResult(ex); le.SetLogLevel(LogLevel.Error); }
            finally { le.Write(); }
        }

        /// <summary>
        /// Egy hosted service elindítását, paraméterezését, konfigurálását és a külső iniciátor meghívást végzi.
        /// </summary>
        /// <param name="externalinitializerstartmethod">az indító metódus</param>
        /// <param name="externalinitializerstopmethod">a leállító metódus</param>
        public static void Start(Action externalinitializerstartmethod, Action externalinitializerstopmethod)
        {
            var le = new DCLogEntry(LogLevel.Information, $"{typeof(WebServerHostedServiceStarter).FullName}.{nameof(Start)}");
            try
            {
                _SetupAutoStart<WebServerHostedServiceStarter>(le: le);
                IISHostedServiceStarterInstance._RegisterExternalInitializer(externalinitializerstartmethod, externalinitializerstopmethod, le);
                IISHostedServiceStarterInstance._StartExternalInitializers(le);
            }
            catch (Exception ex) { le.AddExceptionResult(ex); le.SetLogLevel(LogLevel.Error); }
            finally { le.Write(); }
        }

        /// <summary>
        /// Több hosted service elindítását, paraméterezését, konfigurálását és a külső iniciátorok meghívást végzi.
        /// </summary>
        public static void Start(List<Tuple<Action, Action>> externalinitializerList)
        {
            var le = new DCLogEntry(LogLevel.Information, $"{typeof(WebServerHostedServiceStarter).FullName}.{nameof(Start)}");
            try
            {
                _SetupAutoStart<WebServerHostedServiceStarter>(le: le);
                foreach (var ei in externalinitializerList) IISHostedServiceStarterInstance._RegisterExternalInitializer(ei.Item1, ei.Item2, le);
                IISHostedServiceStarterInstance._StartExternalInitializers(le);
            }
            catch (Exception ex) { le.AddExceptionResult(ex); le.SetLogLevel(LogLevel.Error); }
            finally { le.Write(); }
        }

        /// <summary>
        /// A hosted serviceek leállítását végzi;
        /// Ezt a metódust meg kell hívni a IProcessHostPreloadClient működéséhez!
        /// </summary>
        public static void Stop() 
        {
            var le = new DCLogEntry(LogLevel.Information, $"{typeof(WebServerHostedServiceStarter).FullName}.{System.Reflection.MethodBase.GetCurrentMethod().Name}");
            try { IISHostedServiceStarterInstance._StopExternalInitializers(le); }
            catch (Exception ex) { le.AddExceptionResult(ex); le.SetLogLevel(LogLevel.Error); }
            finally { le.Write(); }
        }

        /// <summary>
        /// IProcessHostPreloadClient-et megvalósító metódus, amelyet a w3w worker process újratöltésekor hív meg az IIS
        /// </summary>
        /// <param name="parameters"></param>
        public void Preload(string[] parameters)
        {
            var le = new DCLogEntry(LogLevel.Information, $"{typeof(WebServerHostedServiceStarter).FullName}.{System.Reflection.MethodBase.GetCurrentMethod().Name}");
            try
            {
                _SetupAutoStart<WebServerHostedServiceStarter>(disablemode: false, le: le);
                _StartExternalInitializers(le);
                le.AddSuccessResult("SUCCESS");
            }
            catch (Exception ex) { le.AddExceptionResult(ex);le.SetLogLevel(LogLevel.Error); }
            finally { le.Write(); }
        }

        /// <summary>
        /// Kell legyen egy publikus paraméter nélküli konstruktor a webapp preload funkcióhoz!!!
        /// </summary>
        public WebServerHostedServiceStarter() { }


        #region private members
        private class InterfaceErrorApplicationException : ApplicationException
        {
            public InterfaceErrorApplicationException(string typename, string methodname) : base($"Class '{typename}' does not implement 'public static void {methodname}()' action!") { }
            public InterfaceErrorApplicationException(string typename) : base($"Class '{typename}' does not implement '{nameof(IWebServerHostedServiceStarter)}' interface!") { }
        }
        void IRegisteredObject.Stop(bool immediate) { Stop(); }
        private void _RegisterExternalInitializer(Action externalinitializerstartmethod, Action externalinitializerstopmethod, DCLogEntry le)
        {
            if (externalinitializerstartmethod != null)
            {
                externalinitializerList.Add(Tuple.Create(externalinitializerstartmethod, externalinitializerstopmethod));
                le?.AddDataField("ADD external initializer START", $"{externalinitializerstartmethod.Method.DeclaringType.FullName}.{externalinitializerstartmethod.Method.Name}()");
                le?.AddDataField("ADD external initializer STOP", $"{externalinitializerstopmethod.Method.DeclaringType.FullName}.{externalinitializerstopmethod.Method.Name}()");
            }
        }
        private void _StartExternalInitializers(DCLogEntry le)
        {
            lock (ExternalInitializerStarStopLocker)
            {
                int numofall = externalinitializerList.Count();
                int counter = externalinitializerList.Count();
                if (numofall == 0) { le.AddDebugField(nameof(_StartExternalInitializers), NOEXTERNALINITIALIZERSAREREGISTERED); return; }

                HostingEnvironment.RegisterObject(IISHostedServiceStarterInstance);
                foreach (var ei in externalinitializerList)
                {
                    var externalinitializername = $"External initializer: {ei.Item1.Method.DeclaringType.FullName}.{ei.Item1.Method.Name}()";
                    try { ei.Item1.Invoke(); le.AddDataField($"{externalinitializername} START SUCCESS", $"{counter}of{numofall}"); }
                    catch (Exception ex) { le.AddDataField($"{externalinitializername} START EXCEPTION", ex.Message); }
                    counter++;
                }
            }
        }
        private void _StopExternalInitializers(DCLogEntry le)
        {
            lock (ExternalInitializerStarStopLocker)
            {
                int numofall = externalinitializerList.Count();
                int counter = externalinitializerList.Count();
                if (numofall == 0) { le.AddDebugField(nameof(_StopExternalInitializers), NOEXTERNALINITIALIZERSAREREGISTERED); return; }

                foreach (var ei in externalinitializerList)
                {
                    var externalinitializername = $"External initializer: {ei.Item2.Method.DeclaringType.FullName}.{ei.Item2.Method.Name}()";
                    try { ei.Item2.Invoke(); le.AddDataField($"{externalinitializername} STOP SUCCESS", $"{counter}of{numofall}"); }
                    catch (Exception ex) { le.AddDataField($"{externalinitializername} STOP EXCEPTION", ex.Message); }
                    counter++;
                }
                HostingEnvironment.UnregisterObject(IISHostedServiceStarterInstance);
            }
        }

        private object ExternalInitializerStarStopLocker = new object();
        private static readonly WebServerHostedServiceStarter IISHostedServiceStarterInstance = new WebServerHostedServiceStarter();
        private static List<Tuple<Action, Action>> externalinitializerList = new List<Tuple<Action, Action>>();
        #endregion private members

        #region private members - appconfig elements
        private static bool Config_DisableAutoStart
        {
            get
            {
                if (!config_disableautostart.HasValue)
                {
                    string disableautostartstring = ConfigurationManager.AppSettings[DISABLEAUTOSTART];
                    if (string.IsNullOrWhiteSpace(disableautostartstring)) config_disableautostart = DISABLEAUTOSTARTDEFAULT;
                    else if (disableautostartstring.ToLower() == bool.TrueString.ToLower()) config_disableautostart = true;
                    else if (disableautostartstring.ToLower() != bool.FalseString.ToLower()) config_disableautostart = false;
                    else config_disableautostart = DISABLEAUTOSTARTDEFAULT;
                }
                return config_disableautostart.Value;
            }
        }
        private static bool? config_disableautostart = null;
        private const string IISHOSTEDSERVICESTARTER = "WebServerHostedServiceStarter:";
        private const string DISABLEAUTOSTART = IISHOSTEDSERVICESTARTER + "disableautostart";
        private const bool DISABLEAUTOSTARTDEFAULT = false;
        private const string NOEXTERNALINITIALIZERSAREREGISTERED = "No external initializers are registered!";
        #endregion private members - appconfig elements

        #region private method - _SetupAutoStart
        /// <summary>
        /// Beállítja az autostart és alwaysrunning és a preload paramétereket a megadott websitra,
        /// web alkalmazásra és application pool-ra vonatkozóan.
        /// Enable webApp pool autostart, preload and alwaysrun
        ///     For web applications running on servers under your control, either physical or virtual, you can use the auto-start feature of IIS ≥ 7.5 shipped with 
        ///     Windows Server ≥ 2008 R2. Full setup requires the following steps to be done. Enable automatic start and AlwaysRunning mode for Application pool 
        ///     and configure Auto - start feature as written below.
        ///         Edit the following file: C:\Windows\System32\inetsrv\config\applicationHost.config.
        ///         All edits should be made below the <configuration><system.applicationHost> section.
        ///         In the <applicationPools> section find the <add name="YourApplicationPoolName"> tag of the application pool to be autostarted.
        ///             Add autoStart="true" and startMode="AlwaysRunning" attributes to this <add> tag.
        ///         In the <sites><site name="YourWebsiteName"> section find the <application path="/YourWebApplicationName" applicationPool="YourApplicationPoolName"> tag of the web application 
        ///             running on the application pool with name "YourApplicationPoolName".
        ///             Add preloadEnabled="true", serviceAutoStartEnabled="true" and serviceAutoStartProvider="AnyNameForYourAutoStartProvider" attributes to this <application> tag.
        ///             Here YourWebsiteName is the name of the website where the web application, using the application pool belongs to.
        ///         In the <serviceAutoStartProviders> section create the following element:
        ///             <add name="AnyNameForYourAutoStartProvider" type="FullTypeNameOfTheAutostartProviderClass, NameOfTheAssemblyContainingTheAutostartProviderClass" />
        ///             Here AnyNameForYourAutoStartProvider can be any name, that is suitable for the xml standard.
        ///             Here FullTypeNameOfTheAutostartProviderClass is the full type name (with namespace and class name) of the class that matches the IProcessHostPreloadClient interface;
        ///                 this class should not be within the root class of the mvc application, but for example another class on the same level! For what is in this code the tag is the following:
        ///                 <add name="ApplicationPreload" type="Vrh.Web.Reporting.ApplicationPreload, Vrh.Web.Reporting" />
        ///             Here NameOfTheAssemblyContainingTheAutostartProviderClass is the name of (for example the dll), but without the extension.
        //////////////////////////////////////////////////////////////////////////////////////
        /// </summary>
        /// <param name="serviceAutoStartProviderType">
        /// annak a class-nak a típusa, amely megvalósítja a IProcessHostPreloadClient interfészt a Preload metódus-sal,
        /// amely metódus meghívásra kerül mindenkor, amikor a hozzájuk tartozó w3w worker processz ujraindításra kerül.
        /// </param>
        /// <param name="websitename">a website neve; alapértelmezés:a tartalmazó website</param>
        /// <param name="webapplicationname">a web app neve; alapértelmezés:a tartalmazó web app</param>
        /// <param name="applicationpoolname">az app pool neve; alapértelmezés:a tartalmazó pool</param>
        /// <returns></returns>
        private static void _SetupAutoStart<T>(string websitename = null, string webapplicationname = null, string applicationpoolname = null, bool? disablemode = null, DCLogEntry le = null)
            where T: IProcessHostPreloadClient
        {
            Type serviceAutoStartProviderType = typeof(T);
            const string CONFIGFILEDIRECTORY = @"inetsrv\config";
            const string CONFIGFILENAME = @"applicationHost.config";
            //const string FILEPATH = @"C:\temp\applicationHost.config";
            bool writele = le == null;
            if (le == null) { le = new DCLogEntry(LogLevel.Information, $"{typeof(WebServerHostedServiceStarter).FullName}.{System.Reflection.MethodBase.GetCurrentMethod().Name}"); };
            try
            {
                websitename = websitename ?? HostingEnvironment.SiteName; //websitename = System.Web.Hosting.HostingEnvironment.ApplicationHost.GetSiteName();
                webapplicationname = webapplicationname ?? HostingEnvironment.ApplicationVirtualPath; //webapplicationname = System.Web.HttpRuntime.AppDomainAppVirtualPath;
                applicationpoolname = applicationpoolname ?? GetCurrentApplicationPoolName(websitename);
                const string PARTSEP = "____";
                string autostartprovidername = string.Concat(websitename,PARTSEP,applicationpoolname,PARTSEP,webapplicationname, PARTSEP, typeof(WebServerHostedServiceStarter).FullName)
                    .Replace(" ", "_").Replace("-", "_").Replace("/", "_").Replace(".", "_");
                string serviceAutoStartProviderTypeFullName = serviceAutoStartProviderType.FullName;
                string serviceAutoStartProviderAssassemblyName = serviceAutoStartProviderType.Assembly.GetName().Name;
                string configfilepath = Path.Combine(NativeSystemPath, CONFIGFILEDIRECTORY, CONFIGFILENAME);

                le.AddDataField("SetupAutoStart configfilepath", configfilepath);
                le.AddDataField("SetupAutoStart applicationpoolname", applicationpoolname);
                le.AddDataField("SetupAutoStart websitename (System.Web.Hosting.HostingEnvironment.SiteName)", websitename);
                le.AddDataField("SetupAutoStart webapplicationname (System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath)", webapplicationname);
                //le.AddDataField("System.Web.HttpRuntime.AppDomainAppVirtualPath", HttpRuntime.AppDomainAppVirtualPath);
                //le.AddDataField("System.Web.Hosting.HostingEnvironment.ApplicationHost.GetSiteName()", System.Web.Hosting.HostingEnvironment.ApplicationHost.GetSiteName());
                le.AddDataField("SetupAutoStart serviceAutoStartProviderTypeFullName", serviceAutoStartProviderTypeFullName);
                le.AddDataField("SetupAutoStart serviceAutoStartProviderAssassemblyName", serviceAutoStartProviderAssassemblyName);

                if (!disablemode.HasValue) disablemode = Config_DisableAutoStart;
                le.AddDataField("DisableautoStart", disablemode.Value);

                string filecontent;
                bool configchanged;
                lock (lockerFile)
                {
                    bool usefilestream = false;
                    if (usefilestream)
                    {
                        FileStream file=null; StreamReader reader = null; StreamWriter writer = null;
                        try
                        {
                            int opentriescounter = 0;
                            while (true)
                            {
                                try { file = new FileStream(configfilepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None); break;  }
                                catch 
                                {
                                    if (opentriescounter++ > 5) throw new Exception("ERROR! in accessing config file. Maybe it is open in other application!");
                                    Thread.Sleep(20); 
                                }
                            }

                            reader = new StreamReader(file, Encoding.ASCII);
                            filecontent = ProcessFileContent(reader.ReadToEnd(), applicationpoolname, websitename, webapplicationname, autostartprovidername, serviceAutoStartProviderAssassemblyName, serviceAutoStartProviderTypeFullName, disablemode.Value);
                            configchanged = filecontent != null;
                            if (configchanged)
                            {
                                writer = new StreamWriter(file, Encoding.ASCII);
                                writer.Write(filecontent);
                            }
                        }
                        finally { try { writer?.Dispose(); } catch { } try { reader?.Dispose();} catch { } try { file?.Dispose(); } catch { } }
                    }
                    else 
                    { 
                        filecontent = ProcessFileContent(System.IO.File.ReadAllText(configfilepath), applicationpoolname, websitename, webapplicationname, autostartprovidername, serviceAutoStartProviderAssassemblyName, serviceAutoStartProviderTypeFullName, disablemode.Value);
                        configchanged = filecontent != null;
                        if (configchanged) System.IO.File.WriteAllText(configfilepath, filecontent);
                    }
                }
                string modetxt = disablemode.Value ? "DISABLED" : "ENABLED";
                le.AddDataField("SetupAutoStart SUCCESS RESULT",configchanged ? modetxt : $"NO CHANGE, ALREADY {modetxt}");
            }
            catch (Exception ex) { le.AddDataField("SetupAutoStart EXCEPTION",ex.Message); le.SetLogLevel(LogLevel.Error); return; }
            finally { if (writele) le.Write(); }
        }
        private static string ProcessFileContent(string filecontent,string applicationpoolname,string websitename,string webapplicationname,string autostartprovidername,string serviceAutoStartProviderAssassemblyName, string serviceAutoStartProviderTypeFullName,bool disablemode)
        {
            const string SYSTEMAPPLICATIONHOST_ELEMENT = "system.applicationHost";
            const string APPLICATIONPOOLS_ELEMENT = "applicationPools";
            const string SITES_ELEMENT = "sites";
            const string SERVICEAUTOSTARTPROVIDERS_ELEMENT = "serviceAutoStartProviders";
            const string SITE_ELEMENT = "site";
            const string APPLICATION_ELEMENT = "application";
            const string ADD_ELEMENT = "add";
            const string NAME_ATTRIBUTE = "name";
            const string TYPE_ATTRIBUTE = "type";
            const string SERVICEAUTOSTARTENABLED_ATTRIBUTE = "serviceAutoStartEnabled";
            const string SERVICEAUTOSTARTPROVIDER_ATTRIBUTE = "serviceAutoStartProvider";
            const string APPLICATIONPOOL_ATTRIBUTE = "applicationPool";
            const string PATH_ATTRIBUTE = "path";
            const string AUTOSTART_ATTRIBUTE = "autoStart";
            const string STARTMODE_ATTRIBUTE = "startMode";
            const string PRELOADENABLED_ATTRIBUTE = "preloadEnabled";

            XElement configxml = XElement.Parse(filecontent, LoadOptions.PreserveWhitespace);
            XElement myconfigrootxml = configxml.Element(XName.Get(SYSTEMAPPLICATIONHOST_ELEMENT));

            XElement myapplicationpooladdelement = myconfigrootxml?.Element(XName.Get(APPLICATIONPOOLS_ELEMENT))?
                .Elements(XName.Get(ADD_ELEMENT))?.FirstOrDefault(e => e.Attribute(XName.Get(NAME_ATTRIBUTE)).Value == applicationpoolname);
            XElement mysiteapplicationelement = myconfigrootxml?.Element(XName.Get(SITES_ELEMENT))?
                .Elements(XName.Get(SITE_ELEMENT))?.FirstOrDefault(e => e.Attribute(XName.Get(NAME_ATTRIBUTE)).Value == websitename)
                .Elements(XName.Get(APPLICATION_ELEMENT))?.FirstOrDefault(e => e.Attribute(XName.Get(PATH_ATTRIBUTE)).Value == webapplicationname && e.Attribute(XName.Get(APPLICATIONPOOL_ATTRIBUTE)).Value == applicationpoolname);
            if (myapplicationpooladdelement == null || mysiteapplicationelement == null) return null;

            bool configchanged = false;
            if (disablemode)
            {
                configchanged = SetOrAddAttribute(AUTOSTART_ATTRIBUTE, myapplicationpooladdelement, bool.FalseString.ToLower()) || configchanged;
                configchanged = SetOrAddAttribute(STARTMODE_ATTRIBUTE, myapplicationpooladdelement, StartMode.OnDemand.ToString()) || configchanged;
                configchanged = SetOrAddAttribute(SERVICEAUTOSTARTENABLED_ATTRIBUTE, mysiteapplicationelement, bool.FalseString.ToLower()) || configchanged;
                configchanged = SetOrAddAttribute(PRELOADENABLED_ATTRIBUTE, mysiteapplicationelement, bool.FalseString.ToLower()) || configchanged;
                configchanged = RemoveAttribute(SERVICEAUTOSTARTPROVIDER_ATTRIBUTE, mysiteapplicationelement) || configchanged;

                XElement myautostartproviderselement = myconfigrootxml.Element(XName.Get(SERVICEAUTOSTARTPROVIDERS_ELEMENT));
                configchanged = RemoveElementWithSelectorAttribute(ADD_ELEMENT, myautostartproviderselement, NAME_ATTRIBUTE, autostartprovidername, removecontainerifnochild: true) || configchanged;
            }
            else
            {
                configchanged = SetOrAddAttribute(AUTOSTART_ATTRIBUTE, myapplicationpooladdelement, bool.TrueString.ToLower()) || configchanged;
                configchanged = SetOrAddAttribute(STARTMODE_ATTRIBUTE, myapplicationpooladdelement, StartMode.AlwaysRunning.ToString()) || configchanged;
                configchanged = SetOrAddAttribute(SERVICEAUTOSTARTENABLED_ATTRIBUTE, mysiteapplicationelement, bool.TrueString.ToLower()) || configchanged;
                configchanged = SetOrAddAttribute(PRELOADENABLED_ATTRIBUTE, mysiteapplicationelement, bool.TrueString.ToLower()) || configchanged;
                configchanged = SetOrAddAttribute(SERVICEAUTOSTARTPROVIDER_ATTRIBUTE, mysiteapplicationelement, autostartprovidername) || configchanged;

                configchanged = SetOrAddElement(SERVICEAUTOSTARTPROVIDERS_ELEMENT, myconfigrootxml, "", out XElement myautostartproviderselement) || configchanged;
                configchanged = SetOrAddElementWithSelectorAttribute(ADD_ELEMENT, myautostartproviderselement, NAME_ATTRIBUTE, autostartprovidername, "", out XElement myautostartprovideraddelement) || configchanged;
                configchanged = SetOrAddAttribute(TYPE_ATTRIBUTE, myautostartprovideraddelement, $"{serviceAutoStartProviderTypeFullName},{serviceAutoStartProviderAssassemblyName }") || configchanged;
            }
            return configxml.ToString();
        }
        private static object lockerFile = new Object();
        private static bool RemoveAttribute(string attributetoremovename, XElement attrcontainer)
        {
            XAttribute attr = attrcontainer.Attribute(XName.Get(attributetoremovename));
            if (attr == null) return false;
            attr.Remove();
            return true;
        }
        private static bool RemoveElement(string elementtoremovename, XElement elementcontainer,bool removecontainerifnochild=false)
        {
            bool configchanged = false;
            XElement elementtoremove = elementcontainer?.Element(XName.Get(elementtoremovename));
            if (elementtoremove == null) return configchanged;
            elementtoremove.Remove();
            if (removecontainerifnochild)
            {
                var childelements = elementcontainer.Elements();
                if (childelements == null || !childelements.Any()) elementcontainer.Remove();
            }
            return (configchanged=true);
        }
        private static bool RemoveElementWithSelectorAttribute(string elementtoremovename, XElement elementcontainer,string selectorattributename, string selectorattributevalue,bool removecontainerifnochild=false)
        {
            bool configchanged = false;
            XElement elementtoremove = elementcontainer?.Elements(XName.Get(elementtoremovename))?.FirstOrDefault(e => e.Attribute(XName.Get(selectorattributename)).Value == selectorattributevalue);
            if (elementtoremove == null) return configchanged;
            elementtoremove.Remove();
            if (removecontainerifnochild)
            {
                var childelements = elementcontainer.Elements();
                if (childelements == null || !childelements.Any()) elementcontainer.Remove();
            }
            return (configchanged = true);
        }
        private static bool SetOrAddElement(string elementtosetname, XElement elementcontainer, string elementvalue, out XElement myelement)
        {
            bool configchanged = false;
            myelement = elementcontainer?.Element(XName.Get(elementtosetname));
            if (myelement == null) { myelement = new XElement(XName.Get(elementtosetname), ""); elementcontainer.Add(myelement); configchanged = true; }
            if (myelement.Value != elementvalue) { myelement.Value = elementvalue; configchanged = true; }
            return configchanged;
        }
        private static bool SetOrAddElementWithSelectorAttribute(string elementtosetname, XElement elementcontainer, string selectorattributename, string selectorattributevalue, string elementvalue, out XElement myelement)
        {
            bool configchanged = false;
            myelement = elementcontainer?.Elements(XName.Get(elementtosetname))?.FirstOrDefault(e => e.Attribute(XName.Get(selectorattributename)).Value == selectorattributevalue);
            if (myelement == null)
            {
                myelement = new XElement(XName.Get(elementtosetname), "");
                elementcontainer.Add(myelement);
                SetOrAddAttribute(selectorattributename, myelement, selectorattributevalue);
                configchanged = true;
            }
            if (myelement.Value != elementvalue) { myelement.Value = elementvalue; configchanged = true; }
            return configchanged;
        }
        private static bool SetOrAddAttribute(string attributetosetname, XElement attrcontainer, string attrvalue)
        {
            bool configchanged = false;
            XAttribute attr = attrcontainer.Attribute(XName.Get(attributetosetname));
            if (attr == null) { attr = new XAttribute(XName.Get(attributetosetname), ""); attrcontainer.Add(attr); configchanged = true; }
            if (attr.Value != attrvalue) { attr.Value = attrvalue; configchanged = true; }
            return configchanged;
        }
        private static string NativeSystemPath
        {
            get { return System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), Environment.Is64BitOperatingSystem?"Sysnative":""); }
        }
        private static string GetCurrentApplicationPoolName(string websitename=null)
        {
            return (new ServerManager()).Sites[websitename ?? System.Web.Hosting.HostingEnvironment.SiteName].Applications?
                .FirstOrDefault(app => app.Path == HttpRuntime.AppDomainAppVirtualPath)?
                .ApplicationPoolName;
        }
        #endregion private method - _SetupAutoStart
    }
}
