using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Yaulw.File;
using System.IO;
using Yaulw.Thread;
using System.Diagnostics;

namespace PlutoServer.MSL
{
    internal static class AutoUpdate
    {               
        #region Private Statics

        /// <summary>
        /// Check every 2.5 hours
        /// </summary>
        private const int AUTO_CHECK_INTERVAL_IN_MINUTES = 150;

        /// <summary>
        /// Keep track when last update occured
        /// </summary>
        private static ISReadWrite s_ISReadWrite = new ISReadWrite("update");
        
        /// <summary>
        /// Auto-Update Thread
        /// </summary>
        private static SingleThreadTimer timer = null;

        /// <summary>
        /// Online Version of the Service
        /// </summary>
        private static Version OnlineVersion = null;

        /// <summary>
        /// Retrieve the LocalFileNameNPath for SetupMini File (TEMP FOLDER)
        /// </summary>
        /// <returns></returns>
        private static string MiniSetupFileNameNPath
        {
            get
            {
                string LocalFileNameNPath = Yaulw.Tools.PathNaming.PathEndsWithSlash(Path.GetTempPath()) + "SetupMini.exe";
                return LocalFileNameNPath;
            }
        }

        #endregion

        #region Construction

        static AutoUpdate()
        {
        }

        #endregion

        #region internal Methods

        /// <summary>
        /// Try to update the Service, if needed
        /// </summary>
        internal static void TryToUpdateIfNeeded(int nDelay, bool bCheckDTStamp)
        {
            // Default Auto Update with 15 sec delay
            if (nDelay < 0)
                nDelay = 15;

            PlutoService.AppLogInfo(String.Format("TryToUpdateIfNeeded called at {0} with delay {1} and CheckDT {2}", DateTime.Now.ToLongTimeString(), nDelay.ToString(), bCheckDTStamp));
            if (bCheckDTStamp)
            {
                DateTime dtLastUpdated = GetLastUpdated();
                TimeSpan ts = DateTime.Now - dtLastUpdated;
                if (ts.TotalDays >= 1.0)
                {
                    PlutoService.AppLogInfo(String.Format("LaunchUpdateThread called with Delay {0}", nDelay));
                    LaunchUpdateThread(nDelay);
                }
            }
            else
            {
                PlutoService.AppLogInfo(String.Format("LaunchUpdateThread called with Delay {0}", nDelay));
                LaunchUpdateThread(nDelay);
            }
        }

        /// <summary>
        /// Main Entry Point - Service Calls into here upon Start-Up
        /// </summary>
        internal static void StartAutoTimer_ServiceStarted()
        {
            // Important Sanity Clean-up, this way we know that the service 
            // always downloads the latest setup and runs the latest setup
            if (File.Exists(MiniSetupFileNameNPath))
                File.Delete(MiniSetupFileNameNPath);

            // Right at Service Startup Check if we have tried to update
            // within the last day, if we have not, try to update.
            // NOTE: Don't do this if we just got installed for the very first time
            //       the service seems to hang when doing that during the install of
            //       lytec/medisoft. (not known what the root cause is), but let's 
            //       just avoid it alltogether. (We could potentially add a timeout here
            //       and just check way way later), not sure what the best course of action
            //       is at this point.
            DateTime dt = Yaulw.Registry.RegKey.GetKey<DateTime>(Yaulw.Registry.HKEYRoot.LocalMachine, PlutoService.LOCAL_MACHINE_SUBKEY, PlutoService.LOCAL_MACHINE_INITIALSETUPDT, DateTime.MinValue);
            TimeSpan ts = DateTime.Now - dt;

            // If the ts is < 10 Minutes we must have just gotten installed, hence, we shouldn't
            // try to update the service and just let it start (let the autoUpdate Handler below or Diagnose Mobile handle the initial updating).
            if (ts.TotalMinutes > 10)
                TryToUpdateIfNeeded(0, false);            

            // Additionally, we want to start a Timer Thread,
            // that Randomly checks for new updates, once every few hours, but only
            // in specific circumstances will it actually perform the update. ( check every 2.5 hours )
            timer = new SingleThreadTimer(AutoUpdateHandler, (uint)TimeSpan.FromMinutes(AUTO_CHECK_INTERVAL_IN_MINUTES).TotalMilliseconds, true);
            // * for debugging *:
            //SingleThreadTimer timer = new SingleThreadTimer(STElapsedEventHandler, (uint)TimeSpan.FromSeconds(30).TotalMilliseconds, true);
        }

        /// <summary>
        /// Main Entry Point - Service Calls into here upon Stop
        /// </summary>
        internal static void StopAutoTimer_ServiceStopped()
        {
            if (timer != null)
            {
                timer.Stop();
                timer = null;
            }
        }

        /// <summary>
        /// Fetch the Latest Version that is Online
        /// </summary>
        /// <returns>valid version object or null if error occured</returns>
        internal static Version FetchOnlineVerion()
        {
            string fetchVersion = Yaulw.Net.WCHelper.ScreenScrapeFromURL(Configuration.CHECK_VERSION_URL);
            if (!String.IsNullOrEmpty(fetchVersion))
            {
                fetchVersion = fetchVersion.Replace("\n", "");
                fetchVersion = fetchVersion.Replace("\r", "");
                fetchVersion = fetchVersion.Trim();

                try
                {
                    Version version = new Version(fetchVersion);
                    return version;
                }
                catch (Exception e)
                {
                    PlutoService.AppLogError(String.Format("The url:{0} to check the Mobile version could not be parsed: {1}", Configuration.CHECK_VERSION_URL, e.Message));
                }
            }
            else
            {
                PlutoService.AppLogError(String.Format("The url:{0} to check the Mobile version could not be accessed", Configuration.CHECK_VERSION_URL));
            }
            return null;
        }

        /// <summary>
        /// Quick Check to see if the Local Service is out of date
        /// </summary>
        /// <returns></returns>
        internal static bool IsLocalServiceVersionOutOfDate()
        {
            OnlineVersion = FetchOnlineVerion();
            Version LocalVersion = Yaulw.Assembly.AssemblyW.GetAssemblyVersion(Yaulw.Assembly.AssemblyW.AssemblyST.Entry);
            if (OnlineVersion != null && LocalVersion != null)
            {
                if (OnlineVersion.Major == LocalVersion.Major && OnlineVersion > LocalVersion)
                {
                    PlutoService.AppLogInfo("Newer Mobile Api Service Found Online");
                    return true;
                }
                else
                {
                    PlutoService.AppLogInfo("Mobile Api Service is already up to date");
                    return false;
                }
            }
            return false;
        }

        #endregion

        #region Private Methods / Helpers

        /// <summary>
        /// Called Randomly every 2.5 Hours, will call Update only if called in the hours of 2 am - 6am AND
        /// the TimeSpan of the last update has been over a day
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void AutoUpdateHandler(object sender, Yaulw.Thread.SingleThreadTimer.STElapsedEventArgs e)
        {
            DateTime dt = e.SignalTime.ToLocalTime();
            TimeSpan ts = dt.TimeOfDay;
            bool bIsBetween2amAnd6am = (ts.TotalHours >= 2.0) && (ts.TotalHours <= 6.0);

            // Only Check between the hours of 2am to 6am
            // And if we haven't updated today yet
            if (bIsBetween2amAnd6am)
            {
                // Launch update without a thread, already inside a thread
                TryToUpdateIfNeeded(0, true);                
            }
        }

        /// <summary>
        /// Get the LastUpdated() DT or DateTime.Min if none
        /// </summary>
        /// <returns></returns>
        private static DateTime GetLastUpdated()
        {
            string s = s_ISReadWrite.ReadFromIS();
            DateTime dt = DateTime.MinValue;
            if (!String.IsNullOrEmpty(s) && DateTime.TryParse(s, out dt))
                return dt;
            else
                return DateTime.MinValue;
        }

        /// <summary>
        /// Set the LastUpdated() DT
        /// </summary>
        /// <param name="dt"></param>
        private static void SetLastUpdated(DateTime dt)
        {
            if (dt != null)
            {
                s_ISReadWrite.WriteToIS(dt.ToString());
            }
        }

        /// <summary>
        /// Add A Delay variable to the Update Thread
        /// </summary>
        private class UpdateThreadParams
        {
            public int nDelay = 0;
        }

        /// <summary>
        /// Check for Updates and update the service if needed
        /// </summary>
        private static void CheckForUpdatesAndUpdate(object o)
        {
            int nSleepInMiliseconds = 0;
            if (o != null)
            {
                UpdateThreadParams p = (UpdateThreadParams)o;            
                nSleepInMiliseconds = p.nDelay;
            }

            PlutoService.AppLogInfo(string.Format("CheckForUpdatesAndUpdate called with nDelay {0}", nSleepInMiliseconds));
            if(nSleepInMiliseconds > 0)
               System.Threading.Thread.Sleep(nSleepInMiliseconds);
                        
            // Update this service, if Online Version is bigger than Local Version
            if (IsLocalServiceVersionOutOfDate())
            {
                if (Yaulw.Net.WCHelper.DownloadFileFromURL(Configuration.DOWNLOAD_MINISETUP_URL, MiniSetupFileNameNPath, true))
                {
                    // * Important * sanity check. Kumar/Jeff found this. State can happen that we can reach the
                    // version url but don't actually download the file which then causes the service to crash when
                    // calling PSetupSpwan (might as well also make sure that the new file is equal to the file we expect)                    
                    if (File.Exists(MiniSetupFileNameNPath))
                    {
                        // The Setup will also try to auto-close this service, so first update the dt then spawn the setup.exe
                        SetLastUpdated(DateTime.Now);
                        PlutoService.AppLogInfo(String.Format("Updating to newer Mobile Api Service '{0}' via '{1}'", OnlineVersion.ToString(), MiniSetupFileNameNPath));

                        //No longer needed - ServiceStartVersionSent (handled in PlutoService.MSL.cs) will take care of this for us
                        //RegistrationWrapper.ServerHasBeenUpdated(OnlineVersion.ToString());

                        // The auto-update is also forcing a close (however we will also try to committ suicide) 
                        Yaulw.Installer.Common.PSetupSpwan(MiniSetupFileNameNPath, "", false);

                        // * Double Knock-out * Force the Service to close here a kill itself here as well, don't know if this even get's called
                        // but might as well make sure and double punch
                        //PlutoService.StopNow();
                        //PlutoService.KillCurrentProccess();
                    }
                    else
                    {                        
                        PlutoService.AppLogInfo(String.Format("Failed to download the file to update the service, please download and install manually: '{0}'", Configuration.DOWNLOAD_MINISETUP_URL));
                    }
                }
            }            
        }

        /// <summary>
        /// Launch the Update within a Thread() with specified Delay,
        /// Thread only launched if Delay > 0, otherwise just calls CheckForUpdatesAndUpdate() directly
        /// </summary>
        /// <param name="nDelay"></param>
        internal static void LaunchUpdateThread(int nDelayInSeconds)
        {
            if (nDelayInSeconds > 0)
            {
                UpdateThreadParams p = new UpdateThreadParams() { nDelay = (int)TimeSpan.FromSeconds(nDelayInSeconds).TotalMilliseconds };
                Yaulw.Thread.TStarter.StartParameterizedThread(CheckForUpdatesAndUpdate, p, "CheckForUpdate_Thread", System.Threading.ApartmentState.MTA, true, System.Threading.ThreadPriority.Normal);
            }
            else
            {
                CheckForUpdatesAndUpdate(null);
            }
        }

        #endregion
    }
}
