using System;
using System.Collections.Generic;
using System.Text;
using IndianHealthService.BMXNet.WinForm.Configuration;
using IndianHealthService.BMXNet.Net;
using System.Security.Principal;
using IndianHealthService.BMXNet.Forms;
using IndianHealthService.BMXNet.WinForm.Forms;
using System.Windows.Forms;
using IndianHealthService.BMXNet.WinForm.Model;

namespace IndianHealthService.BMXNet.WinForm
{
    /// <summary>
    /// This class models the workflow of the LoginProcess.  It correographs UI and non-UI
    /// login methods, login cancelling, management dialogs, and provides hooks (events) to 
    /// customize the login workflow.
    /// </summary>
    /// <example>
    /// See the SDK for other examples.
    /// <code>
    /// this.Framework = WinFramework.CreateWithNetworkBroker(true);
    /// this.Framework.LoadSettings(LocalPersistentStore.CreateIn(Environment.SpecialFolder.LocalApplicationData, EntryAssemblyInfo.AssemblyCompany + "/" + EntryAssemblyInfo.AssemblyProduct, false), "settings");
    /// this.Framework.LoadConnectionSpecs(LocalPersistentStore.CreateIn(Environment.SpecialFolder.LocalApplicationData, EntryAssemblyInfo.AssemblyCompany + "/" + EntryAssemblyInfo.AssemblyProduct, false), "connectiosn");
    /// LoginProcess login = this.Framework.CreateLoginProcess();
    /// 
    /// //Attempt a non-UI WindowsAuth if and only if there is a default connection with WindowsAuth
    /// //Of course, an application can set its own policy of when to AttemptWindowsAuthLogin()
    /// 
    /// if (login.HasDefaultConnectionWithUseWindowsAuth)
    /// {
    ///     login.AttemptWindowsAuthLogin();
    /// }
    /// 
    /// //If not attempted yet, i.e. skipped the AttemptWindowsAuthLogin(), or was unsuccessul, try and UI login
    /// if (!login.WasLoginAttempted || !login.WasSuccessful)
    /// {
    ///     login.AttemptUserInputLogin(IndianHealthService.BMXNet.Util.EntryAssemblyInfo.AssemblyTitle+" Login", 3,!this.Framework.BootStrapSettings.Get("lockedit",false), this);
    /// }
    ///        
    /// //If the login process was unable to login after the max tries (or fow other configuration reasons), exit the application
    /// if (!login.WasSuccessful)
    /// {
    ///     this.Close();
    ///     return;
    /// }
    ///  
    /// // Making sure that the user's division is set.  Can use AttemptUserInputSetDivision() or the application can devise another solution
    /// if ((this.Framework.User.Division == null) &amp;&amp; !this.Framework.AttemptUserInputSetDivision("Set Initial Division", this))
    /// {
    ///     this.Close();
    ///     return;
    /// }
    ///
    /// // Logged in with valid user and division 
    ///  this.RemoteSession = this.Framework.PrimaryRemoteSession;
    /// </code></example>
    public class LoginProcess
    {
        /// <summary>
        /// Triggered before every login attempt. See <see cref="AttemptingLoginEventArgs"/> for details .
        /// </summary>
        public event EventHandler<AttemptingLoginEventArgs> AttemptingLogin;
        
        /// <summary>
        /// Triggered after every login attempt. See <see cref="LoginAttemptedEventArgs"/> for details .
        /// </summary>
        public event EventHandler<LoginAttemptedEventArgs> LoginAttempted;
        
        private bool _cancel = false;

        /// <summary>
        /// During a LoginAttempted the LoginProcess can be Cancelled by setting Cancel to True.
        /// </summary>
        public bool Cancel
        {
            get { return _cancel; }
            set { _cancel = value; }
        }

        private bool _IsSwitchServerModeEnabled = false;

        /// <summary>
        /// If set to True, the Connection combo box on the Login screen with be dropped down when the 
        /// dialog is displayed.  This is useful for applications that have an option to change Connections. 
        /// The default is False.
        /// </summary>
        public bool IsSwitchServerModeEnabled
        {
            get { return _IsSwitchServerModeEnabled; }
            set { _IsSwitchServerModeEnabled = value; }
        }

        private bool _autoSetDivisionToLastLookup = true;

        /// <summary>
        /// If set to True, RPMS is checked and if there was a previously set division for the user it will be used, otherwise 
        /// MustResolveDivision will be set to True and the division will need to be set for the user.
        /// The default is True
        /// </summary>
        public bool AutoSetDivisionToLastLookup
        {
            get { return _autoSetDivisionToLastLookup; }
            set { _autoSetDivisionToLastLookup = value; }
        }

        private bool _mustResolveDivision = false;

        /// <summary>
        /// If the division for the user has not been determine after the LoginProcess, MustResolveDivision will be set to True.
        /// The default if False.  
        /// </summary>
        public bool MustResolveDivision
        {
            get { return _mustResolveDivision; }
            set { _mustResolveDivision = value; }
        }

        private int _loginAttempts = 0;

        /// <summary>
        /// The number of login attempts so far.  This value can be modified during the AttemptingLogin and LoginAttempted events.
        /// </summary>
        public int LoginAttempts
        {
            get { return _loginAttempts; }
            set { _loginAttempts = value; }
        }

        private int _maxAttempts = 3;

        /// <summary>
        /// The number of login attempts before cancelling the LoginProcess.  The default value is 3.  This value can be modified during the AttemptingLogin and LoginAttempted events.
        /// </summary>
        public int MaxAttempts
        {
            get { return _maxAttempts; }
            set { _maxAttempts = value; }
        }

        private bool _wasLoginAttempted = false;

        /// <summary>
        /// True if a login was attempted.  False if the user is presented with a LoginDialog and "Cancel" is selected.
        /// The value is changed during every login attempt cycle. 
        /// </summary>
        public bool WasLoginAttempted
        {
            get { return _wasLoginAttempted; }
            set { _wasLoginAttempted = value; }
        }

        private bool _wasSuccessful = false;

        /// <summary>
        /// True if the most recent login attempt was successful.
        /// </summary>
        public bool WasSuccessful
        {
            get { return _wasSuccessful; }
            set { _wasSuccessful = value; }
        }
        private String _failureMessage = null;

        /// <summary>
        /// A reasonable message to display to the user if the last login attempt failed.
        /// </summary>
        public String FailureMessage
        {
            get { return _failureMessage; }
            set { _failureMessage = value; }
        }

        private Exception _failureException = null;

        /// <summary>
        /// If an exception occured during the last login attempt, FailureException will be set to it.
        /// </summary>
        public Exception FailureException
        {
            get { return _failureException; }
            set { _failureException = value; }
        }

        private RpmsConnectionSpec _connectionSpec = null;

        /// <summary>
        /// The active ConnectionSpec being used to login.  With care, the property can be changed or the instance can be modified during the AttemptingLogin and LoginAttempted events
        /// with care.  
        /// </summary>
        public RpmsConnectionSpec ConnectionSpec
        {
            get { return _connectionSpec; }
            set { _connectionSpec = value; }
        }

        internal LoginProcess(WinFramework aFramework)
        {
            this.Framework = aFramework;
        }

        internal RpmsConnectionSettings Settings
        {
            get { return this.Framework.ConnectionSettings; }
        }

        private WinFramework _framework = null;

        internal protected WinFramework Framework
        {
            get { return _framework; }
            set { _framework = value; }
        }

        /// <summary>
        /// Answer True if there is a default managed connection that uses WindowsAuthenication
        /// </summary>
        public bool HasDefaultConnectionWithUseWindowsAuth
        {
            get
            {
                RpmsConnectionSpec spec = this.Settings.DefaultConnectionSpec;

                return spec == null ? false : spec.UseWindowsAuthentication;
            }


        }


        /// <summary>
        /// Attempt a WindowsAuthentication Login and answer true if it was successful.
        /// </summary>        
        /// <returns>True if login was successful</returns>
        public bool AttemptWindowsAuthLogin(RpmsConnectionSpec aConnectionSpec)
        {
            RpmsConnectionSpec spec = aConnectionSpec;
            if (spec == null)
            {
                return this.Failed("A default connection spec must be set for automatic Windows Authenication");
            }

            if (spec.UseWindowsAuthentication)
            {
                return this.PrimitiveAttemptWindowsAuthLogin(spec);
            }
            else
            {
                return this.Failed("Connection spec must be set for automatic Windows Authenication for auto-login");
            }
        }

        /// <summary>
        /// Using the current default connection spec, attempt a WindowsAuthentication Login and answer true if it was successful.
        /// </summary>        
        /// <remarks>
        /// Most common approach when using the DefaultConnection feature the RpmsConnection spec class.
        /// </remarks>
        /// <returns>True if login was successful</returns>
        public bool AttemptWindowsAuthLogin()
        {
            return this.AttemptWindowsAuthLogin(this.Settings.DefaultConnectionSpec);       
        }

        private bool Failed(string aMessage)
        {
            this.WasLoginAttempted = true;
            this.WasSuccessful = false;
            this.FailureMessage = aMessage;

            return this.WasSuccessful;
        }

      /// <summary>
      /// Attempt an interactive UI login.  There are several useful arguments to control the process and appearance of the this process.
      /// </summary>
      /// <param name="aDialogTitle">The title of the login.  Customize to be something like "MyApplication Login"</param>
      /// <param name="maxAttempts">The number of attempts before cancelling</param>
      /// <param name="enableConnectionManagement">If false, the user will not be able to change the connections.  This is useful for lockdown situations</param>
      /// <param name="aUiOwnerForPositioningDialog">Provide your main application as the window owner so the LoginDialog box is correctly managed.</param>
      /// <returns>True if login was successful</returns>
        public bool AttemptUserInputLogin(string aDialogTitle, int maxAttempts, bool enableConnectionManagement,IWin32Window aUiOwnerForPositioningDialog)
        {
            this.MaxAttempts = maxAttempts;
            RpmsLoginView view = (RpmsLoginView)new RpmsLoginDialog();
            
            view.Title = aDialogTitle;

            RpmsLoginPresenter presenter = new RpmsLoginPresenter();
            presenter.EnableConnectionManagement = enableConnectionManagement;
            presenter.View = view;
            presenter.Model = this;
            presenter.UiOwner = aUiOwnerForPositioningDialog;

            presenter.Open();


            return this.WasSuccessful;
        }

        /// <summary>
        /// If the application is managing the actual login, send Succeeded() to indicate success.  
        /// </summary>
        /// <remarks>
        /// Do not set WasSuccessful to true.
        /// </remarks>
        /// <returns>True if successful</returns>
        public bool Succeeded()
        {
            this.WasLoginAttempted = true;
            this.WasSuccessful = true;
            this.FailureMessage = "";
            this.Framework.Login = this;

            return this.WasSuccessful;
        }



        internal bool PrimitiveAttemptWindowsAuthLogin(RpmsConnectionSpec aSpec)
        {
            BMXNetBroker broker = this.Framework.SocketBroker;
            this.ConnectionSpec = aSpec;

            if (this.AttemptingLogin != null)
            {
                AttemptingLoginEventArgs args = new AttemptingLoginEventArgs();
                args.Process = this;
                this.AttemptingLogin(this, args);
                if (args.Handled)
                {
                    return this.WasSuccessful;
                }
                
                if (args.Cancel)
                {
                    this.Cancel = true;
                    return false;
                }
            }

            try
            {     
                if (broker.Open(aSpec.Server, aSpec.Port, aSpec.NameSpace, WindowsIdentity.GetCurrent(),aSpec.SendTimeout,aSpec.ReceiveTimeout))
                {
                    this.Succeeded();             
                    this.ResolveDivision(broker);
                    return this.WasSuccessful;
                }
                else
                {          
                    return this.Failed("Unable to authenicate.  Use may need to login");
                }
            }
            catch (BMXNetException problem)
            {
                this.FailureException = problem;
                return this.Failed(problem.Message);
            }
            catch (Exception anException)
            {
                this.FailureException = anException;                
                return this.Failed("Critical issue: " + anException.Message);
            }
        }


        public VerifyCodeUpdateResult AttemptVerifyCodeChange(WinFramework bmxFramework, RpmsConnectionSpec bmxConnectionSpec, string strAccessCode)
        {

            // Open the verify code upate dialog
            VerifyCodeUpdateDialog vcd = new VerifyCodeUpdateDialog(bmxFramework, bmxConnectionSpec, strAccessCode);
            DialogResult dr = new DialogResult();
            VerifyCodeUpdateResult vr = new VerifyCodeUpdateResult();

            while (vr.DialogResult != DialogResult.Cancel && !(vr.WasVerifyCodeUpdatedSuccessfully ))
            {
               vr  = vcd.ShowVerifyChangeDialog(null);
            }

            return vr;

        }

        /// <summary>
        /// Attempt a headless non-interactive UI login.  This would be useful for an ASP.NET or NT-service type application
        /// </summary>
        /// <param name="aSpec">The RpmsConnectionSpec to use during the login process</param>
        /// <param name="anAccessCode">The clear text access code</param>
        /// <param name="aVerifyCode">The clear text verify code</param>
        /// <returns>True if the login was successful</returns>
        public bool AttemptAccessVerifyLogin(RpmsConnectionSpec aSpec, String anAccessCode, String aVerifyCode)
        {
            BMXNetBroker broker = this.Framework.SocketBroker;
   
            this.ConnectionSpec = aSpec;


            if (this.AttemptingLogin != null)
            {
                AttemptingLoginEventArgs args = new AttemptingLoginEventArgs();
                args.Process = this;
                this.AttemptingLogin(this, args);
                if (args.Handled)
                {
                    return this.WasSuccessful;
                }
                
                if (args.Cancel)
                {
                    this.Cancel = true;
                    return false;
                }
            }


            try
            {
                this.LoginAttempts++;

                if (broker.Open(aSpec.Server, aSpec.Port, aSpec.NameSpace, anAccessCode, aVerifyCode,aSpec.SendTimeout,aSpec.ReceiveTimeout))
                {
                     this.Succeeded();
                    if (aSpec.UseWindowsAuthentication)
                    {
                        String result = broker.RegisterWindowsIdentityForWindowsAuthenication(WindowsIdentity.GetCurrent());
                    }
                    this.ResolveDivision(broker);

                    return this.WasSuccessful;
                }
                else
                {
                    return this.Failed("Unable to authenicate.  Use may need to login");
                }
            }
            
            catch (BMXNetException problem)
            {
                this.FailureException = problem;  
              
                if ( problem.Message.Contains("VERIFY CODE MUST be changed before continued use."))
                {
                    string message = "Your Verify Code has expired. Do you want to submit a new Verify Code now? Click Yes to continue, Click No to exit the sign in process.";
                    string caption = "Verify Code Expired";
			        MessageBoxButtons buttons = MessageBoxButtons.YesNo;
                    
			        DialogResult result;

			        // Displays the MessageBox.

			        result = MessageBox.Show(message, caption, buttons);

			        if (result == System.Windows.Forms.DialogResult.Yes)
			        {
                         // Open the verify code upate dialog
                        VerifyCodeUpdateDialog vcd =new VerifyCodeUpdateDialog(this.Framework,this.ConnectionSpec,anAccessCode);
                        DialogResult dr = new DialogResult();

                        while ( (dr != DialogResult.Cancel) && (!vcd.IsUpdateSuccessful))
                        {
                            dr = vcd.ShowDialog();
                        }


                        if (dr == DialogResult.OK)
                        {                       
                                 bool bLogin = AttemptAccessVerifyLogin(this.ConnectionSpec,vcd.AccessCode, vcd.NewVerifyCode);
                                 return bLogin;
                        }
                        else
                        {
                            this.FailureMessage = "VERIFY CODE MUST be changed before continued use.";
                            return this.WasSuccessful;
                        }
			        }
                    else
                    {
                        return this.Failed(problem.Message);
                    }
                  }
                else
                {
                    return this.Failed(problem.Message);
                }
                
            }
            catch (Exception anException)
            {
                this.FailureException = anException;                
                return this.Failed("Critical issue: " + anException.Message);
            }
        }

        private void TriggerAttemptingLogin()
        {
            throw new NotImplementedException();
        }

        private void ResolveDivision(BMXNetBroker aBroker)
        {
            List<SelectableDivision> divisions = this.Framework.Divisions(aBroker.Duz);
            if (divisions.Count == 1)
            {
                this.Framework.SetDivision(divisions[0]);
            }
            else
            {
                if (this.AutoSetDivisionToLastLookup)
                {
                    WinDivision last = null;
                    foreach (WinDivision each in divisions)
                    {
                        if (each.MostRecentLookup)
                        {
                            last = each;
                            break;
                        }
                    }
                    if (last == null)
                    {
                        this.MustResolveDivision = true;
                    }
                    else
                    {
                        this.Framework.SetDivision(last);
                    }
                }
                else
                {
                    this.MustResolveDivision = true;
                }
            }
        }

        /// <summary>
        /// Will trigger the LoginAttempted event 
        /// </summary>
        /// <returns>Answer true if the LoginProcess has been Canceled or handled</returns>
        public bool HandleLoginAttemptedFailed()
        {
            if (this.LoginAttempted != null)
            {
                LoginAttemptedEventArgs args = new LoginAttemptedEventArgs();
                args.Process = this;
                args.Handled = false;
                args.WasSuccessful = this.WasLoginAttempted;
                this.LoginAttempted(this, args);
                if (args.Cancel)
                {
                    this.Cancel = true;
                }
                return args.Handled || this.Cancel;

            }
            else
            {
                return false;
            }
        }
    }
}