using System;
using System.Net;
using System.Windows;
using System.ComponentModel;
using System.ServiceModel.DomainServices.Client;
using System.ServiceModel.DomainServices.Client.ApplicationServices;
using MyAuth.Web;
using InvExplorer;

namespace MyAuth
{
    /// <summary>
    /// Delegate invoked when authentication operations are completed.
    /// </summary>
    /// <param name="error">Error that occurred during the authentication operation (if any).</param>
    public delegate void AuthenticationOperationCompleted(Exception error);

    /// <summary>
    /// Provides a context and object model for user authentication.
    /// </summary>
    public class AuthenticationModel : INotifyPropertyChanged
    {
        // ** fields
        Exception _error;
        WebContext _wc;

        // ** ctor
        public AuthenticationModel()
        {
            if (!DesignerProperties.IsInDesignTool)
            {
                // create web context
                _wc = new WebContext();
                _wc.Authentication = new FormsAuthentication();

                // hook up event handlers
                Service.LoggedIn += (s, e) => { OnLoggedIn(); };
                Service.LoggedOut += (s, e) => { OnPropertyChanged(); };

                // load user info from cookie saved in the last session
                Service.LoadUser(completeUserLoaded, null);
            }
        }
        void completeUserLoaded(LoadUserOperation operation)
        {
            OnPropertyChanged();
        }

        // ** object model

        /// <summary>
        /// Gets the current user.
        /// </summary>
        public Web.User User
        {
            get { return WebContext.Current != null ? WebContext.Current.User : null; }
        }
        /// <summary>
        /// Gets a reference to the authentication service.
        /// </summary>
        public AuthenticationService Service
        {
            get { return WebContext.Current != null ? WebContext.Current.Authentication : null; }
        }
        /// <summary>
        /// Gets the last error detected.
        /// </summary>
        public Exception Error
        {
            get { return _error; }
            private set { _error = value; }
        }
        /// <summary>
        /// Log in.
        /// </summary>
        public void Login(LoginParameters parms, AuthenticationOperationCompleted aoc)
        {
            Service.Login(parms, completeLogin, aoc);
        }
        void completeLogin(LoginOperation op)
        {
            // check result
            var x = op.Error;
            if (x == null && !op.LoginSuccess)
            {
                x = new Exception("Login failed. Please check e-mail and password.");
            }

            // invoke delegate
            var aoc = op.UserState as AuthenticationOperationCompleted;
            if (aoc != null)
            {
                aoc(x);
            }

            // inform listeners
            Error = x;
            OnPropertyChanged();
        }
        /// <summary>
        /// Log out.
        /// </summary>
        public void Logout(AuthenticationOperationCompleted aoc)
        {
            try
            {
                Service.Logout(completeLogout, aoc);
            }
            catch (Exception x)
            {
                if (aoc != null)
                {
                    aoc(x);
                    Error = x;
                }
            }
        }
        void completeLogout(LogoutOperation op)
        {
            // invoke delegate
            var aoc = op.UserState as AuthenticationOperationCompleted;
            if (aoc != null)
            {
                aoc(op.Error);
            }

            // inform listeners
            Error = op.Error;
            OnPropertyChanged();
        }
        /// <summary>
        /// Create a new user.
        /// </summary>
        public void CreateUser(string email, string password, 
            string friendlyName, bool logIn, bool isPersistent,
            AuthenticationOperationCompleted aoc)
        {
            // create new user
            var userData = new RegistrationData();
            userData.Email = email;
            userData.FriendlyName = friendlyName;
            var ctx = new UserRegistrationContext();
            ctx.CreateUser(userData, password, (op) =>
                {
                    // check result
                    var x = op.Error;
                    if (x == null && !string.IsNullOrEmpty(op.Value))
                    {
                        var msg = "Error creating account:\r\n" + op.Value;
                        x = new Exception(msg);
                    }

                    // invoke delegate
                    if (aoc != null)
                    {
                        aoc(x);
                    }

                    // log in if no errors
                    if (x == null && logIn) 
                    {
                        // no error, log new user in
                        var parms = new LoginParameters(email, password, isPersistent, null);
                        Login(parms, aoc);
                    }
                    else 
                    {
                        // notify users
                        Error = x;
                        OnPropertyChanged();
                    }
                },
            null);
        }
        /// <summary>
        /// Saves data associated with the current user.
        /// </summary>
        public void SaveUserData(AuthenticationOperationCompleted aoc)
        {
            try
            {
                Service.SaveUser(completeSaveData, aoc);
            }
            catch (Exception x)
            {
                if (aoc != null)
                {
                    aoc(x);
                    Error = x;
                }
            }
        }
        void completeSaveData(SaveUserOperation op)
        {
            // invoke delegate
            var aoc = op.UserState as AuthenticationOperationCompleted;
            if (aoc != null)
            {
                aoc(op.Error);
            }

            // inform listeners
            Error = op.Error;
            OnPropertyChanged();
        }
        /// <summary>
        /// Resets the current user's password.
        /// </summary>
        public void ResetPassword(string userName, AuthenticationOperationCompleted aoc)
        {
            try
            {
                var ctx = new UserRegistrationContext();
                ctx.ResetPassword(userName, userName, completeReset, aoc);
            }
            catch (Exception x)
            {
                if (aoc != null)
                {
                    aoc(x);
                    Error = x;
                }
            }
        }
        void completeReset(InvokeOperation<string> op)
        {
            // check result
            var x = op.Error;
            if (x == null && !string.IsNullOrEmpty(op.Value))
            {
                x = new Exception(op.Value);
            }

            // invoke delegate
            var aoc = op.UserState as AuthenticationOperationCompleted;
            if (aoc != null)
            {
                aoc(x);
            }

            // inform listeners
            Error = x;
            OnPropertyChanged();
        }
        /// <summary>
        /// Change the current user's password.
        /// </summary>
        public void ChangePassword(string oldPassword, string newPassword, AuthenticationOperationCompleted aoc)
        {
            try
            {
                var ctx = new UserRegistrationContext();
                ctx.ChangePassword(User.Name, oldPassword, newPassword, completeChange, aoc);
            }
            catch (Exception x)
            {
                if (aoc != null)
                {
                    aoc(x);
                    Error = x;
                }
            }
        }
        void completeChange(InvokeOperation<string> op)
        {
            // check result
            var x = op.Error != null 
                ? op.Error 
                : new Exception(op.Value);

            // invoke delegate
            var aoc = op.UserState as AuthenticationOperationCompleted;
            if (aoc != null)
            {
                aoc(x);
            }

            // inform listeners
            Error = x;
            OnPropertyChanged();
        }
        /// <summary>
        /// Occurs when a user logs in.
        /// </summary>
        public event EventHandler LoggedIn;
        /// <summary>
        /// Raises the <see cref="LoggedIn"/> event.
        /// </summary>
        protected virtual void OnLoggedIn()
        {
            OnPropertyChanged();
            if (LoggedIn != null)
                LoggedIn(this, EventArgs.Empty);
        }

        // ** INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }
        void OnPropertyChanged(string propName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propName));
        }
        void OnPropertyChanged()
        {
            OnPropertyChanged(new PropertyChangedEventArgs(null));
        }
    }
}
