using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pluto.Api;
using System.Net;
using PlutoServer.MSL.Connectors;

namespace PlutoServer.MSL
{
    public static class RegistrationWrapper
    {
        // Allow the Registration wrapper to let the Service enter into a different 'disconnected' state
        internal static int HasConnectivityErrorCountInARow = 0;
        internal static int HasConnectivityErrorCountMaxBeforeStateChange = 10;
        internal static string RegistrationHOST = "";
        internal static uint RegistrationPORT = 0;

        #region Connectivity

        /// <summary>
        /// Check to make sure we can communicate with the Registration Service.
        /// Check the Server's ip & port for connectivity as well as that this
        /// computer has connectivity. 
        /// This way we won't throw any connectivity errors, which is good.
        /// </summary>        
        /// <returns>true, if successfully can connect, false otherwise</returns>
        private static bool HasConnectivity()
        {
            try
            {
                bool bCanConnect = false;
                bool bUseConfiguration = true;

                // Try the overwritten Registration Host and Port first, if exists
                if (RegistrationHOST != "" && RegistrationPORT > 0)
                {
                    bCanConnect = Yaulw.Net.IPHostHelper.HasConnectivity(RegistrationHOST, RegistrationPORT, 0);
                    if (bCanConnect)
                        bUseConfiguration = false;
                }

                // Else, fall-back on the configuration, if we couldn't connect
                if (!bCanConnect)
                {
                    bCanConnect = Yaulw.Net.IPHostHelper.HasConnectivity(Configuration.REGISTRATION_HOST_URL, (uint)Configuration.REGISTRATION_CHANNEL_PORT, 0);
                    if (bCanConnect)
                        bUseConfiguration = true;
                }

                if (bCanConnect)
                {
                    MSLSpecific.Logger.Info("Network Communication successful with the McKesson's Mobile Gateway bUseConfiguration={0}", bUseConfiguration.ToString());
                    if (bUseConfiguration)
                    {
                        IPAddress ip = Yaulw.Net.IPHostHelper.GetIpForHost(Configuration.REGISTRATION_HOST_URL);
                        RegistrationAPI.API.SetNetworkSettings(ip.ToString(), (uint)Configuration.REGISTRATION_CHANNEL_PORT);
                    }
                    else
                    {
                        IPAddress ip = Yaulw.Net.IPHostHelper.GetIpForHost(RegistrationHOST);
                        RegistrationAPI.API.SetNetworkSettings(ip.ToString(), RegistrationPORT);
                    }
                    HasConnectivityErrorCountInARow = 0;
                }
                else
                {
                    // Try to retrieve the latest Host and Port Setting online
                    try
                    {
                        string HostNPortSetOnline = Yaulw.Net.WCHelper.ScreenScrapeFromURL(Configuration.REGISTRATION_HOST_N_PORT_URL);
                        if (!String.IsNullOrEmpty(HostNPortSetOnline))
                        {
                            RegistrationHOST = HostNPortSetOnline.Split(';')[0];
                            RegistrationPORT = uint.Parse(HostNPortSetOnline.Split(';')[1]);
                        }
                    }
                    catch (Exception e) 
                    { 
                        MSLSpecific.Logger.Error("Retrieving Host and Port Setting from REGISTRATION_HOST_N_PORT_URL failed", e); 
                    }

                    // Try to connection one more time
                    if (RegistrationHOST != "" && RegistrationPORT > 0)
                        bCanConnect = Yaulw.Net.IPHostHelper.HasConnectivity(RegistrationHOST, RegistrationPORT, 0);

                    // if we can connect, we are Golden
                    if (bCanConnect)
                    {
                        MSLSpecific.Logger.Info("Network Communication successful with the McKesson's Mobile Gateway bUseConfiguration=False");                        
                        IPAddress ip = Yaulw.Net.IPHostHelper.GetIpForHost(RegistrationHOST);
                        RegistrationAPI.API.SetNetworkSettings(ip.ToString(), RegistrationPORT);
                        HasConnectivityErrorCountInARow = 0;
                    }
                    else
                    {
                        // Connectivity Errors too plentiful - Change the state of the service to reflect that
                        if (HasConnectivityErrorCountInARow >= HasConnectivityErrorCountMaxBeforeStateChange)
                        {
                            MSLSpecific.Logger.Info("Entering no_Connectivity to McKesson's Mobile Gateway state");
                            PlutoService.state = HostGUIDstate.no_connectivity;
                        }
                        else
                        {
                            MSLSpecific.Logger.Error("Network Communication unsuccessful with McKesson's Mobile Gateway");
                            HasConnectivityErrorCountInARow++;
                        }
                    }
                }
                return bCanConnect;                
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("HasConnectivity() error thrown", e);
            }
            return false;
        }

        #endregion

        #region Registration Specifics that don't require network

        /// <summary>
        /// Get a Practice Name with (MCK) in it (for internal usuage)
        /// </summary>
        /// <param name="PracticeName"></param>
        /// <returns></returns>
        public static string GetMCKPractice(string PracticeName)
        {
            return RegistrationAPI.API.GetMcKInternalizedPracticeName(PracticeName);
        }

        #endregion

        #region Update Server

        /// <summary>
        /// Update Server Internal / External
        /// </summary>        
        /// <param name="internalIP"></param>
        /// <param name="externalIP"></param>
        /// <param name="Port"></param>
        public static void UpdateServerIntExt(IPAddress internalIP, IPAddress externalIP, uint Port)
        {            
            string LocalIP = "";
            if (Yaulw.Net.IPHostHelper.IsValidIPv4Address(internalIP, false))
                LocalIP = internalIP.ToString();

            string ExternalIP = "";
            if (Yaulw.Net.IPHostHelper.IsValidIPv4Address(externalIP, false))
                ExternalIP = externalIP.ToString();

            if ((!String.IsNullOrEmpty(LocalIP) || !String.IsNullOrEmpty(ExternalIP)) || Port > 0)
                UpdateServerIntExt(LocalIP, ExternalIP, Port);
            else
                MSLSpecific.Logger.Info("UpdateServerIntExt was called with invalid parameters. Registration not updated.");            
        }

        /// <summary>
        /// Update Server Internal / External (actually does the work)
        /// </summary>
        /// <param name="internalIP"></param>
        /// <param name="externalIP"></param>
        /// <param name="Port"></param>
        private static void UpdateServerIntExt(string internalIP, string externalIP, uint Port)
        {
            try
            {
                if(HasConnectivity())
                {
                    bool bSuccess = RegistrationAPI.API.UpdateServer(MSLSpecific.GetHostGUID(), internalIP, externalIP, Port);
                    if (bSuccess)
                        MSLSpecific.Logger.Info("Updating IP Internal/External information occured successfully for Host:{0} LocalIP:{1}, ExternalIP:{2}, Port:{3}", MSLSpecific.GetHostGUID(), internalIP, externalIP, Port);
                    else 
                        MSLSpecific.Logger.Error("Updating IP Internal/External information failed for Host:{0} LocalIP:{1}, ExternalIP:{2}, Port:{3}", MSLSpecific.GetHostGUID(), internalIP, externalIP, Port);
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("UdateServerIntExt Error Thrown", e);
            }
        }

        /// <summary>
        /// Update Server Internal 
        /// </summary>
        /// <param name="internalIP"></param>
        /// <param name="Port"></param>
        public static void UpdateServerInternal(IPAddress internalIP)
        {
            string LocalIP = "";
            if (Yaulw.Net.IPHostHelper.IsValidIPv4Address(internalIP, false))
                LocalIP = internalIP.ToString();

            if (!String.IsNullOrEmpty(LocalIP))
                UpdateServerInternal(LocalIP);
            else
                MSLSpecific.Logger.Info("UpdateServerInternal was called with invalid parameters. Registration not updated.");            
        }

        /// <summary>
        /// Update Server Internal (actually does the work)
        /// </summary>
        /// <param name="bLog"></param>
        /// <param name="internalIP"></param>        
        /// <param name="Port"></param>
        private static void UpdateServerInternal(string internalIP)
        {
            try
            {
                if (HasConnectivity())
                {
                    bool bSuccess = RegistrationAPI.API.UpdateServer(MSLSpecific.GetHostGUID(), internalIP, String.Empty, PlutoService.INTERNAL_CHANNEL_PORT);
                    if (bSuccess)
                        MSLSpecific.Logger.Info("Updating IP Internal information occured successfully for Host:{0} LocalIP:{1}, Port:{2}", MSLSpecific.GetHostGUID(), internalIP, PlutoService.INTERNAL_CHANNEL_PORT);
                    else
                        MSLSpecific.Logger.Error("Updating IP Internal information failed for Host:{0} LocalIP:{1}, Port:{2}", MSLSpecific.GetHostGUID(), internalIP, PlutoService.INTERNAL_CHANNEL_PORT);
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("UpdateServerInternal Error Thrown", e);
            }
        }

        #endregion

        #region Register Update/New Practice

        /// <summary>
        /// Register New Practice
        /// </summary>
        /// <param name="PracticeName">Name of Practice</param>
        /// <param name="bIsMedisoft">Is Medisoft</param>
        /// <param name="strApiKey">out ApiKey</param>
        /// <param name="strPin">out Pin</param>
        /// <returns>true, if successful, false otherwise</returns>
        public static bool RegisterNewPractice(string PracticeName, bool bIsMedisoft, out string strApiKey, out string strPin)
        {
            strApiKey = "";
            strPin = "";
            try
            {
                if (HasConnectivity())
                {
                    // Set up local IP            
                    string LocalIP = "";
                    if (Yaulw.Net.IPHostHelper.IsValidIPv4Address(PlutoService.InternalIP, false))
                        LocalIP = PlutoService.InternalIP.ToString();

                    // Set up external IP and Port
                    string ExternalIP = "";
                    uint Port = 0;
                    if (PlutoService.IsExtIPandExtPortSet)
                    {
                        if (Yaulw.Net.IPHostHelper.IsValidIPv4Address(PlutoService.ExternalIP, false))
                            ExternalIP = PlutoService.ExternalIP.ToString();
                        Port = PlutoService.ExternalPort;
                    }
                    else
                    {
                        Port = PlutoService.INTERNAL_CHANNEL_PORT;
                    }

                    // Set up Practice Name
                    if (PlutoService.IsOnMcKessonNetwork)
                        PracticeName = GetMCKPractice(PracticeName);

                    // Set up Product Name
                    string ProductName = ProductType.GetProductName(bIsMedisoft);

                    // Make the Registration Call                    
                    MSLSpecific.Logger.Info("Registering PracticeName:{0}, LocalIP:{1}, ExternalIP:{2}, Port:{3}, ProductName:{4} for Host:{5}", PracticeName, LocalIP, ExternalIP, Port, ProductName, MSLSpecific.GetHostGUID());
                    bool bSuccess = RegistrationAPI.API.RegisterNewServerPractice(MSLSpecific.GetHostGUID(), PracticeName, LocalIP, ExternalIP, Port, ProductName, out strApiKey, out strPin);
                    if (bSuccess)
                        MSLSpecific.Logger.Info("Registering Practice successfully");
                    else
                        MSLSpecific.Logger.Error("Registering Practice failed");
                    return bSuccess;
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("RegisterNewPractice Error Thrown", e);
            }
            return false;
        }

        /// <summary>
        /// Update Practice Name
        /// </summary>
        /// <param name="UserApiKey"></param>
        /// <param name="Pin"></param>
        /// <param name="NewPracticeName"></param>
        /// <returns></returns>
        public static bool UpdatePracticeName(string UserApiKey, string Pin, string NewPracticeName)
        {
            try
            {
                if (HasConnectivity())
                {
                    // Set up Practice Name
                    if (PlutoService.IsOnMcKessonNetwork)
                        NewPracticeName = GetMCKPractice(NewPracticeName);

                    bool bSuccess = RegistrationAPI.API.UpdatePracticeName(UserApiKey, Pin, NewPracticeName);
                    if (bSuccess)
                        MSLSpecific.Logger.Info("Updating Practice Name successfully");
                    else
                        MSLSpecific.Logger.Error("Updating Practice Name failed");
                    return bSuccess;
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("UpdatePracticeName Error Thrown", e);
            }
            return false;
        }

        #endregion

        #region Other Register Methods

        /// <summary>
        /// Retrieve Pin for UserApiKey
        /// </summary>
        /// <param name="UserApiKey"></param>
        /// <returns></returns>
        public static string RetrievePinForUserApiKey(string UserApiKey)
        {
            try
            {
                if (HasConnectivity())
                {
                    if (!String.IsNullOrEmpty(UserApiKey))
                    {
                        string Pin = RegistrationAPI.API.RetrieveUserApiKeyPin(UserApiKey);
                        return Pin;
                    }
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("RetrievePinForUserApiKey Error Thrown", e);
            }
            return String.Empty;
        }

        /// <summary>
        /// Is this Server reachable online
        /// </summary>
        /// <returns></returns>
        public static bool IsServerReachable()
        {
            try
            {
                if (PlutoService.IsOnMcKessonNetwork)
                {
                    MSLSpecific.Logger.Info("This host/server is in WIFI only mode, due to being on the McKesson Network");
                    PlutoService.AppLogWarning("This is a host/server is in WIFI only mode, due to being on the McKesson Network");
                    return false;
                }

                if (!PlutoService.IsExtIPandExtPortSet)
                {
                    MSLSpecific.Logger.Info("This host/server is in WIFI only mode, external connectivity not configured");
                    PlutoService.AppLogWarning("This is a host/server is in WIFI only mode, external connectivity not configured");
                    return false;
                }

                if (HasConnectivity())
                {
                    bool bSuccess = RegistrationAPI.API.IsServerReachable(PlutoService.ExternalIP.ToString(), PlutoService.ExternalPort);
                    if (bSuccess)
                        MSLSpecific.Logger.Info("The server/host was reachable (success) on External IP:{0} and Port:{1}", PlutoService.ExternalIP.ToString(), PlutoService.ExternalPort);
                    else
                        MSLSpecific.Logger.Error("The server/host was unreachable (failure) on External IP:{0} and Port:{1}", PlutoService.ExternalIP.ToString(), PlutoService.ExternalPort);
                    return bSuccess;
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("IsServerReachable Error Thrown", e);
            }
            return false;
        }

        /// <summary>
        /// Is a specific Server reachable online
        /// </summary>
        /// <param name="bLog"></param>
        /// <param name="externalIP"></param>
        /// <param name="Port"></param>
        /// <returns></returns>
        public static bool IsServerReachable(IPAddress externalIP, uint Port)
        {
            try
            {
                // Set up external IP            
                string ExtIP = "";
                if (Yaulw.Net.IPHostHelper.IsValidIPv4Address(externalIP, false))
                    ExtIP = externalIP.ToString();                              
                if (String.IsNullOrEmpty(ExtIP))
                {
                    MSLSpecific.Logger.Info("Invalid External IP passed in");
                    return false;
                }

                // Check Port
                if (Port <= 0)
                {
                    MSLSpecific.Logger.Info("Invalid Port passed in");
                    return false;
                }

                // Check Connectivity
                if (HasConnectivity())
                {
                    bool bSuccess = RegistrationAPI.API.IsServerReachable(ExtIP, Port);
                    if (bSuccess)
                        MSLSpecific.Logger.Info("The server/host was reachable (success) on External IP:{0} and Port:{1}", ExtIP, Port);
                    else
                        MSLSpecific.Logger.Error("The server/host was unreachable (failure) on External IP:{0} and Port:{1}", ExtIP, Port);
                    return bSuccess;
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error(String.Format("IsServerReachable Error Thrown with ExtIP={0} and Port={1}", externalIP.ToString(), Port.ToString()), e);
            }
            return false;
        }

        /// <summary>
        /// Retrieve the HostGUID from a Server/Port if that Server Port is running Pluto
        /// </summary>
        /// <param name="externalIP"></param>
        /// <param name="Port"></param>
        /// <returns>HostGUID or "" if none found</returns>
        public static string RetrieveHostGUIDForServer(IPAddress externalIP, uint Port)
        {
            try
            {
                // Set up external IP            
                string ExtIP = "";
                if (Yaulw.Net.IPHostHelper.IsValidIPv4Address(externalIP, false))
                    ExtIP = externalIP.ToString();
                if (String.IsNullOrEmpty(ExtIP))
                {
                    MSLSpecific.Logger.Info("Invalid External IP passed in");
                    return String.Empty;
                }

                // Check Port
                if (Port <= 0)
                {
                    MSLSpecific.Logger.Info("Invalid Port passed in");
                    return String.Empty;
                }

                if(HasConnectivity())
                {
                    string retVal = RegistrationAPI.API.RetrievePlutoHostGUID(externalIP.ToString(), Port);
                    return retVal;
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("RetrieveHostGUIDForServer Error Thrown", e);
            }
            return String.Empty;
        }

        /// <summary>
        /// Connectivity / HOST Guid State * Important for Integrity Checking *
        /// </summary>
        public enum HostGUIDstate
        {
            exists,
            not_exists,
            no_connectivity
        }

        /// <summary>
        /// Quick Check to see if our host guid even exists on the server, if it doesn't
        /// then no need to call UpdateServer() in any form
        /// </summary>
        /// <returns></returns>
        public static HostGUIDstate DoesServerHostGuidExist()
        {
            try
            {
                if (HasConnectivity())
                {                    
                    bool bExists = RegistrationAPI.API.DoesServerHostGUIDExist(MSLSpecific.GetHostGUID());
                    if (bExists)
                    {
                        MSLSpecific.Logger.Info("HostGuid found on Mobile Gateway Server. Some Practice(s) are registered online");
                        return HostGUIDstate.exists;
                    }
                    else
                    {
                        MSLSpecific.Logger.Info("HostGuid not found on Mobile Gateway Server. No Practice is registered online");
                        return HostGUIDstate.not_exists;
                    }
                }
            }
            catch (Exception e)
            {
                MSLSpecific.Logger.Error("DoesServerHostGuidExist Error Thrown", e);
            }
            return HostGUIDstate.no_connectivity;
        }

        /// <summary>
        /// Let the Registration Service know what we have been updated
        /// </summary>
        /// <param name="version"></param>
        public static void ServerHasBeenUpdated(string version)
        {
            try
            {
                if (HasConnectivity())
                {
                    RegistrationAPI.API.ServerHasBeenUpdated(MSLSpecific.GetHostGUID(), version);
                }
            }
            catch (Exception e)
            {                
                MSLSpecific.Logger.Error("ServerHasBeenUpdated Error Thrown", e);
            }
        }

        #endregion
    }
}
