using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using Pluto.Registration;
using System.Reflection;
using Yaulw.Thread;

namespace RegistrationServer
{
    public partial class RegistrationService : ServiceBase
    {

        #region Internal Consts

        // Threshold used to determine if system just rebooted, and service just
        // started. We need to wait a little because we need to make sure the Database Server is up
        internal const int NUMBER_OF_MINUTES_SERVICE_UP_THRESHOLD = 5;
        internal const int NUMBER_OF_MINUTES_SYSTEM_UP_THRESHOLD = 10;

        #endregion

        #region Internal Statics 

        /// <summary>
        /// Service Start Time
        /// </summary>
        internal static DateTime ServiceStartDT = DateTime.MinValue;   

        #endregion        

        #region Private Members

        /// <summary>
        /// Check if this is the first time the network got enabled
        /// </summary>
        private bool FirstTimeNetworkConnected = true;

        /// <summary>
        /// Was the Channel Activated?
        /// </summary>
        private bool ROHasBeenActivated = false;        

        /// <summary>
        /// Did a System Reboot just occur?
        /// </summary>
        private bool SystemJustStarted_SoUseServiceThreshold = false;
        private bool ServiceUpTimeReached = false;
        private bool SystemThresholdNotReached = true;

        /// <summary>
        /// Is the Logger Setup?
        /// </summary>
        private bool LoggerIsSetup = false;

        /// <summary>
        /// Our Connectivity Timer Thread
        /// </summary>
        private SingleThreadTimer connectivityTimer = null;

        /// <summary>
        /// If for some reason the network goes down, we should re-initialize
        /// everything as if nothing happened
        /// </summary>
        private bool NetworkOutageDetected = false;

        #endregion

        #region Construction

        /// <summary>
        /// Singleton Instance of the Service
        /// </summary>
        internal static RegistrationService s_RegistrationService = null;

        /// <summary>
        /// Constructor
        /// </summary>
        public RegistrationService()
        {        
            InitializeComponent();

            // Log to the Application System Log
            this.EventLog.Log = "Application";
            s_RegistrationService = this;

            // Handle all unexpected errors
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        }

        /// <summary>
        /// Unhandled Domain Exception
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Exception ex = (Exception)e.ExceptionObject;
            String msg = "UnhandledException Occured: " + ex.Message + "\n\n" + ex.InnerException.Message;
            AppLogError(msg);
        }

        #endregion

        #region Windows Event Logging

        internal static void AppLogError(string errMsg)
        {
            s_RegistrationService.EventLog.WriteEntry(errMsg, EventLogEntryType.Error);
            if (Registration.Logger != null)
                Registration.Logger.Error(errMsg);
        }

        internal static void AppLogInfo(string infoMsg)
        {
            s_RegistrationService.EventLog.WriteEntry(infoMsg, EventLogEntryType.Information);
            if (Registration.Logger != null)
                Registration.Logger.Info(infoMsg);
        }

        internal static void AppLogWarning(string warnMsg)
        {
            s_RegistrationService.EventLog.WriteEntry(warnMsg, EventLogEntryType.Warning);
            if (Registration.Logger != null)
                Registration.Logger.Info(warnMsg);
        }

        #endregion 

        // usefull for debugging Service in visual studio
#if DEBUG
        internal void DebugStart(string[] args) 
        {             
            DEBUGGING_ONLY.DEBUGSTEP_INTO();
            OnStart(args); 
        }
#endif

        #region System / Service Up Time

        /// <summary>
        /// Get Service UpTime
        /// </summary>
        /// <returns></returns>
        private TimeSpan GetServiceUpTime()
        {
            return (DateTime.Now - ServiceStartDT);
        }

        /// <summary>
        /// For Service Handling, see if the system has been up long enough
        /// Once it becomes true, it will always return true
        /// </summary>
        private bool HasServiceBeenUpLongEnough()
        {
            if (!ServiceUpTimeReached)
                ServiceUpTimeReached = GetServiceUpTime().TotalMinutes >= NUMBER_OF_MINUTES_SERVICE_UP_THRESHOLD;
            return ServiceUpTimeReached;
        }

        /// <summary>
        /// For Error Handling, see if the system has been up till this threshold
        /// Once it becomes false, will always return false
        /// </summary>
        /// <returns></returns>
        private bool IsSystemBeenUpWithinMaxThreshold()
        {
            if (SystemThresholdNotReached)
                SystemThresholdNotReached = (Yaulw.Installer.Common.GetSystemUpTime().TotalMinutes <= NUMBER_OF_MINUTES_SYSTEM_UP_THRESHOLD);
            return SystemThresholdNotReached;
        }

        #endregion

        /// <summary>
        /// Service Start-Up
        /// </summary>
        /// <param name="args"></param>
        protected override void OnStart(string[] args)
        {
            try
            {
                // Service Started                                                    
                // System just started, so make sure that service is timing out a little to make
                // sure everything is up and running            
                SystemJustStarted_SoUseServiceThreshold = IsSystemBeenUpWithinMaxThreshold();
                AppLogInfo(String.Format("MSLMobile Gateway Service started up at: {0}. System Reboot detected: {1}", DateTime.Now.ToLongTimeString(), SystemJustStarted_SoUseServiceThreshold));
                ServiceStartDT = DateTime.Now;

                // Internet / Network Connectivity Timer
                // * Ran into issues when computer is rebooted, we need certain things working before the service can start, 
                // * therefore decided to put DataStore Read(), AutoUpdate, RO Connection into a Connection Timer,
                // that keeps checking every 10 seconds if we have network connectivity * Enables / Disables RO Channels as needed, just in case *
                AppLogInfo("MSLMobile Gateway Service starting connectivity timer.");
                connectivityTimer = new SingleThreadTimer(ConnectivityHandler, (uint)TimeSpan.FromSeconds(10).TotalMilliseconds, true);
            }
            catch (Exception e)
            {
                AppLogError(String.Format("Service OnStart() Error thrown. Fatal. Can't continue: {0}", e.Message));
                this.Stop();
            }
        }

        #region Private Connectivity Timed Thread (runs every 10 Seconds)

        /// <summary>
        /// Single Threaded Connectivity Handler (Handles Auto-Update, ReadInDataStore(), and RO Channels) 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ConnectivityHandler(object sender, Yaulw.Thread.SingleThreadTimer.STElapsedEventArgs e)
        {
            // System just started, we won't do anyting in here until our
            // Service Startup 'NUMBER_OF_MINUTES_SERVICE_UP_THRESHOLD' timeout of is reached
            if (SystemJustStarted_SoUseServiceThreshold && !HasServiceBeenUpLongEnough())
                return;

            // Check Connectivity,
            // ~withouth basic networking being up there is nothing really to do here
            bool bHasConnection = Yaulw.Net.IPHostHelper.HasConnection();
            if (!bHasConnection)
            {
                DeactivateChannelIfActivated(true);
                return;
            }

            // Always set up the Logger First (Needed for all subsequent DB Calls)
            if (!LoggerIsSetup)
            {
                AppLogInfo("Setting up Logger");
                Registration.Setup_Logger();
                LoggerIsSetup = true;
            }       

            // * Check to Make sure the System has been up long enough *
            // Calling VerifyConnectivity() requires SQL Server to be up
            // and running and hence, we can't do it right at service start when the
            // server just rebooted, we wait 5 of service uptime to make sure after a server reboot, 
            // that the services needed are up
            if (FirstTimeNetworkConnected)
            {
                AppLogInfo("System has been up long enough. Starting Initialization.");

                // Make sure we can connect to the Database
                try
                {
                    if (!DataAccessLayer.VerifyConnectivity())
                    {
                        AppLogError("Cannot verify SQL Server Credentials upon initialization. Service must be stopped.");
                        this.Stop();
                    }
                    else
                    {
                        AppLogInfo("Success. SQL Server Credentials could connect upon initialization.");
                    }
                }
                catch (Exception ex)
                {
                    AppLogError(String.Format("Cannot verify SQL Server Credentials upon initialization '{0}'. Service must be stopped.", ex.Message));
                    this.Stop();
                }
                FirstTimeNetworkConnected = false;                
                return;
            }

            // We have a valid SQL Server Connection, hence we can continue
            if (!FirstTimeNetworkConnected)
            {         
                // If Network disconnection occured, we should try to re-read in everything,
                // to make sure we are doing the best we can
                if (NetworkOutageDetected)
                {
                    AppLogInfo("Network Outage Detected - Trying to retry connecting to SQL Server");
                    // Make sure we can connect to the Database
                    try
                    {
                        if (!DataAccessLayer.VerifyConnectivity())
                            AppLogError("Cannot verify SQL Server Credentials after Network Outage.");
                        else
                            AppLogInfo("Success. SQL Server Credentials could connect after Networ Outage.");
                    }
                    catch (Exception ex)
                    {
                        AppLogError(String.Format("Cannot verify SQL Server Credentials after Network Outage '{0}'.", ex.Message));
                    }
                }

                // Activate the RO Channel
                ActivateChannelIfNotActivated();                
            }
        }

        #endregion

        #region Private RO Channel Helpers

        /// <summary>
        /// Activate all Channel
        /// </summary>
        private void ActivateChannelIfNotActivated()
        {
            if (!ROHasBeenActivated)
            {
                SetPort(Configuration.Port);
                ServerChannel.Activate();
                ROHasBeenActivated = true;
                AppLogInfo(String.Format("MSLMobile Gateway Service Channel Activated on Port:{0}.", Configuration.Port));
                NetworkOutageDetected = false;
            }
        }

        /// <summary>
        /// Deactivate RO Channel
        /// </summary>
        private void DeactivateChannelIfActivated(bool bNetworkOutage)
        {
            if (ROHasBeenActivated)
            {                
                ServerChannel.Deactivate();
                ROHasBeenActivated = false;
                AppLogInfo("MSLMobile Gateway Service Channel Deactivated.");
                NetworkOutageDetected = bNetworkOutage;
            }
        }

        #endregion

// usefull for debugging Service in visual studio
#if DEBUG
        internal void DebugStop() { OnStop(); }
#endif
        /// <summary>
        /// SCM OnStop() call-in
        /// </summary>
        protected override void OnStop()
        {
            try
            {
                // First stop the internal connectivity timer
                if (connectivityTimer != null)
                {
                    AppLogInfo("MSLMobile Gateway Service connectivity timer stopped.");
                    connectivityTimer.Stop();
                }

                DeactivateChannelIfActivated(false);
                AppLogInfo("End DeactivateChannelIfActivated.");
            }
            catch (Exception) { /* ignore */ }
        }
    }
}
