/*
 Copyright 2015 OpenMarket Ltd
 Copyright 2017 Vector Creations Ltd
 Copyright 2019 The Matrix.org Foundation C.I.C

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

#import "MXKAuthenticationViewController.h"

#import "MXKAuthInputsEmailCodeBasedView.h"
#import "MXKAuthInputsPasswordBasedView.h"

#import "MXKAccountManager.h"

#import "NSBundle+MatrixKit.h"

#import <AFNetworking/AFNetworking.h>
#import "MXKAppSettings.h"

#import "MXKSwiftHeader.h"

#import "GeneratedInterface-Swift.h"

@interface MXKAuthenticationViewController ()
{
    /**
     The matrix REST client used to make matrix API requests.
     */
    MXRestClient *mxRestClient;
    
    /**
     Current request in progress.
     */
    MXHTTPOperation *mxCurrentOperation;
    
    /**
     The MXKAuthInputsView class or a sub-class used when logging in.
     */
    Class loginAuthInputsViewClass;
    
    /**
     The MXKAuthInputsView class or a sub-class used when registering.
     */
    Class registerAuthInputsViewClass;
    
    /**
     The MXKAuthInputsView class or a sub-class used to handle forgot password case.
     */
    Class forgotPasswordAuthInputsViewClass;
    
    /**
     Customized block used to handle unrecognized certificate (nil by default).
     */
    MXHTTPClientOnUnrecognizedCertificate onUnrecognizedCertificateCustomBlock;
    
    /**
     The current authentication fallback URL (if any).
     */
    NSString *authenticationFallback;
    
    /**
     The cancel button added in navigation bar when fallback page is opened.
     */
    UIBarButtonItem *cancelFallbackBarButton;
    
    /**
     The timer used to postpone the registration when the authentication is pending (for example waiting for email validation)
     */
    NSTimer* registrationTimer;

    /**
     Identity server discovery.
     */
    MXAutoDiscovery *autoDiscovery;

    MXHTTPOperation *checkIdentityServerOperation;
}

/**
 The identity service used to make identity server API requests.
 */
@property (nonatomic) MXIdentityService *identityService;

@property (nonatomic) AnalyticsScreenTracker *screenTracker;

@property (nonatomic) BOOL isViewVisible;

@end

@implementation MXKAuthenticationViewController

#pragma mark - Class methods

+ (UINib *)nib
{
    return [UINib nibWithNibName:NSStringFromClass([MXKAuthenticationViewController class])
                          bundle:[NSBundle bundleForClass:[MXKAuthenticationViewController class]]];
}

+ (instancetype)authenticationViewController
{
    return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAuthenticationViewController class])
                                          bundle:[NSBundle bundleForClass:[MXKAuthenticationViewController class]]];
}

#pragma mark -

- (void)finalizeInit
{
    [super finalizeInit];
    
    // Set initial auth type
    _authType = MXKAuthenticationTypeLogin;
    
    _deviceDisplayName = nil;
    
    // Initialize authInputs view classes
    loginAuthInputsViewClass = MXKAuthInputsPasswordBasedView.class;
    registerAuthInputsViewClass = nil; // No registration flow is supported yet
    forgotPasswordAuthInputsViewClass = nil;
}

#pragma mark -

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // Check whether the view controller has been pushed via storyboard
    if (!_authenticationScrollView)
    {
        // Instantiate view controller objects
        [[[self class] nib] instantiateWithOwner:self options:nil];
    }
    
    self.authFallbackWebView = [[MXKAuthenticationFallbackWebView alloc] initWithFrame:self.authFallbackWebViewContainer.bounds];
    [self.authFallbackWebViewContainer addSubview:self.authFallbackWebView];
    [self.authFallbackWebView.leadingAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.leadingAnchor constant:0].active = YES;
    [self.authFallbackWebView.trailingAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.trailingAnchor constant:0].active = YES;
    [self.authFallbackWebView.topAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.topAnchor constant:0].active = YES;
    [self.authFallbackWebView.bottomAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.bottomAnchor constant:0].active = YES;
    
    // Load welcome image from MatrixKit asset bundle
    self.welcomeImageView.image = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"logoHighRes"];

    _authenticationScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    
    _subTitleLabel.numberOfLines = 0;
    
    _submitButton.enabled = NO;
    _authSwitchButton.enabled = YES;
    
    _homeServerTextField.text = _defaultHomeServerUrl;
    _identityServerTextField.text = _defaultIdentityServerUrl;

    // Hide the identity server by default
    [self setIdentityServerHidden:YES];

    // Create here REST client (if homeserver is defined)
    [self updateRESTClient];
    
    // Localize labels
    _homeServerLabel.text = [VectorL10n loginHomeServerTitle];
    _homeServerTextField.placeholder = [VectorL10n loginServerUrlPlaceholder];
    _homeServerInfoLabel.text = [VectorL10n loginHomeServerInfo];
    _identityServerLabel.text = [VectorL10n loginIdentityServerTitle];
    _identityServerTextField.placeholder = [VectorL10n loginServerUrlPlaceholder];
    _identityServerInfoLabel.text = [VectorL10n loginIdentityServerInfo];
    [_cancelAuthFallbackButton setTitle:[VectorL10n cancel] forState:UIControlStateNormal];
    [_cancelAuthFallbackButton setTitle:[VectorL10n cancel] forState:UIControlStateHighlighted];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onTextFieldChange:) name:UITextFieldTextDidChangeNotification object:nil];
    
    self.isViewVisible = YES;
    [self.screenTracker trackScreen];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    
    [self dismissKeyboard];
    
    // close any opened alert
    if (alert)
    {
        [alert dismissViewControllerAnimated:NO completion:nil];
        alert = nil;
    }
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    
    self.isViewVisible = NO;
}

#pragma mark - Override MXKViewController

- (void)onKeyboardShowAnimationComplete
{
    // Report the keyboard view in order to track keyboard frame changes
    // TODO define inputAccessoryView for each text input
    // and report the inputAccessoryView.superview of the firstResponder in self.keyboardView.
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
- (void)setKeyboardHeight:(CGFloat)keyboardHeight
{
    // Deduce the bottom inset for the scroll view (Don't forget the potential tabBar)
    CGFloat scrollViewInsetBottom = keyboardHeight - self.bottomLayoutGuide.length;
    // Check whether the keyboard is over the tabBar
    if (scrollViewInsetBottom < 0)
    {
        scrollViewInsetBottom = 0;
    }
    
    UIEdgeInsets insets = self.authenticationScrollView.contentInset;
    insets.bottom = scrollViewInsetBottom;
    self.authenticationScrollView.contentInset = insets;
}
#pragma clang diagnostic pop

- (void)destroy
{
    self.authInputsView = nil;
    
    if (registrationTimer)
    {
        [registrationTimer invalidate];
        registrationTimer = nil;
    }
    
    if (mxCurrentOperation)
    {
        [mxCurrentOperation cancel];
        mxCurrentOperation = nil;
    }

    [self cancelIdentityServerCheck];

    [mxRestClient close];
    mxRestClient = nil;

    authenticationFallback = nil;
    cancelFallbackBarButton = nil;
    
    [super destroy];
}

#pragma mark - Class methods

- (void)registerAuthInputsViewClass:(Class)authInputsViewClass forAuthType:(MXKAuthenticationType)authType
{
    // Sanity check: accept only MXKAuthInputsView classes or sub-classes
    NSParameterAssert([authInputsViewClass isSubclassOfClass:MXKAuthInputsView.class]);
    
    if (authType == MXKAuthenticationTypeLogin)
    {
        loginAuthInputsViewClass = authInputsViewClass;
    }
    else if (authType == MXKAuthenticationTypeRegister)
    {
        registerAuthInputsViewClass = authInputsViewClass;
    }
    else if (authType == MXKAuthenticationTypeForgotPassword)
    {
        forgotPasswordAuthInputsViewClass = authInputsViewClass;
    }
}

- (void)setAuthType:(MXKAuthenticationType)authType
{
    if (_authType != authType)
    {
        _authType = authType;
        
        // Cancel external registration parameters if any
        _externalRegistrationParameters = nil;
        
        // Remove the current inputs view
        self.authInputsView = nil;
        
        isPasswordReseted = NO;
        
        [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator];
        [_authenticationActivityIndicator startAnimating];
    }
    
    // Restore user interaction
    self.userInteractionEnabled = YES;
    
    if (authType == MXKAuthenticationTypeLogin)
    {
        _subTitleLabel.hidden = YES;
        [_submitButton setTitle:[VectorL10n login] forState:UIControlStateNormal];
        [_submitButton setTitle:[VectorL10n login] forState:UIControlStateHighlighted];
        [_authSwitchButton setTitle:[VectorL10n createAccount] forState:UIControlStateNormal];
        [_authSwitchButton setTitle:[VectorL10n createAccount] forState:UIControlStateHighlighted];
        
        // Update supported authentication flow and associated information (defined in authentication session)
        [self refreshAuthenticationSession];
        
        self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenLogin];
    }
    else if (authType == MXKAuthenticationTypeRegister)
    {
        _subTitleLabel.hidden = NO;
        _subTitleLabel.text = [VectorL10n loginCreateAccount];
        [_submitButton setTitle:[VectorL10n signUp] forState:UIControlStateNormal];
        [_submitButton setTitle:[VectorL10n signUp] forState:UIControlStateHighlighted];
        [_authSwitchButton setTitle:[VectorL10n back] forState:UIControlStateNormal];
        [_authSwitchButton setTitle:[VectorL10n back] forState:UIControlStateHighlighted];
        
        // Update supported authentication flow and associated information (defined in authentication session)
        [self refreshAuthenticationSession];
        
        self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenRegister];
    }
    else if (authType == MXKAuthenticationTypeForgotPassword)
    {
        _subTitleLabel.hidden = YES;
        
        if (isPasswordReseted)
        {
            [_submitButton setTitle:[VectorL10n back] forState:UIControlStateNormal];
            [_submitButton setTitle:[VectorL10n back] forState:UIControlStateHighlighted];
        }
        else
        {
            [_submitButton setTitle:[VectorL10n submit] forState:UIControlStateNormal];
            [_submitButton setTitle:[VectorL10n submit] forState:UIControlStateHighlighted];
            
            [self refreshForgotPasswordSession];
        }
        
        [_authSwitchButton setTitle:[VectorL10n back] forState:UIControlStateNormal];
        [_authSwitchButton setTitle:[VectorL10n back] forState:UIControlStateHighlighted];
        
        self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenForgotPassword];
    }

    if (self.isViewVisible)
    {
        [self.screenTracker trackScreen];
    }
    
    [self checkIdentityServer];
}

- (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView
{
    // Here a new view will be loaded, hide first subviews which depend on auth flow
    _submitButton.hidden = YES;
    _noFlowLabel.hidden = YES;
    _retryButton.hidden = YES;
    
    if (_authInputsView)
    {
        [_authInputsView removeObserver:self forKeyPath:@"viewHeightConstraint.constant"];

        [NSLayoutConstraint deactivateConstraints:_authInputsView.constraints];
        [_authInputsView removeFromSuperview];
        _authInputsView.delegate = nil;
        [_authInputsView destroy];
        _authInputsView = nil;
    }
    
    _authInputsView = authInputsView;
    
    CGFloat previousInputsContainerViewHeight = _authInputContainerViewHeightConstraint.constant;
    
    if (_authInputsView)
    {
        _authInputsView.translatesAutoresizingMaskIntoConstraints = NO;
        [_authInputsContainerView addSubview:_authInputsView];
        
        _authInputsView.delegate = self;
        
        _submitButton.hidden = NO;
        _authInputsView.hidden = NO;
        
        _authInputContainerViewHeightConstraint.constant = _authInputsView.viewHeightConstraint.constant;
        
        NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView
                                                                         attribute:NSLayoutAttributeTop
                                                                         relatedBy:NSLayoutRelationEqual
                                                                            toItem:_authInputsView
                                                                         attribute:NSLayoutAttributeTop
                                                                        multiplier:1.0f
                                                                          constant:0.0f];
        
        
        NSLayoutConstraint* leadingConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView
                                                                             attribute:NSLayoutAttributeLeading
                                                                             relatedBy:NSLayoutRelationEqual
                                                                                toItem:_authInputsView
                                                                             attribute:NSLayoutAttributeLeading
                                                                            multiplier:1.0f
                                                                              constant:0.0f];
        
        NSLayoutConstraint* trailingConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView
                                                                              attribute:NSLayoutAttributeTrailing
                                                                              relatedBy:NSLayoutRelationEqual
                                                                                 toItem:_authInputsView
                                                                              attribute:NSLayoutAttributeTrailing
                                                                             multiplier:1.0f
                                                                               constant:0.0f];
        
        
        [NSLayoutConstraint activateConstraints:@[topConstraint, leadingConstraint, trailingConstraint]];
        
        [_authInputsView addObserver:self forKeyPath:@"viewHeightConstraint.constant" options:0 context:nil];
    }
    else
    {
        // No input fields are displayed
        _authInputContainerViewHeightConstraint.constant = _authInputContainerViewMinHeightConstraint.constant;
    }
    
    [self.view layoutIfNeeded];
    
    // Refresh content view height by considering the updated height of inputs container
    _contentViewHeightConstraint.constant += (_authInputContainerViewHeightConstraint.constant - previousInputsContainerViewHeight);
}

- (void)setDefaultHomeServerUrl:(NSString *)defaultHomeServerUrl
{
    _defaultHomeServerUrl = defaultHomeServerUrl;
    
    if (!_homeServerTextField.text.length)
    {
        [self setHomeServerTextFieldText:defaultHomeServerUrl];
    }
}

- (void)setDefaultIdentityServerUrl:(NSString *)defaultIdentityServerUrl
{
    _defaultIdentityServerUrl = defaultIdentityServerUrl;
    
    if (!_identityServerTextField.text.length)
    {
        [self setIdentityServerTextFieldText:defaultIdentityServerUrl];
    }
}

- (void)setHomeServerTextFieldText:(NSString *)homeServerUrl
{
    if (!homeServerUrl.length)
    {
        // Force refresh with default value
        homeServerUrl = _defaultHomeServerUrl;
    }
    
    _homeServerTextField.text = homeServerUrl;
    
    if (!mxRestClient || ![mxRestClient.homeserver isEqualToString:homeServerUrl])
    {
        [self updateRESTClient];
        
        if (_authType == MXKAuthenticationTypeLogin || _authType == MXKAuthenticationTypeRegister)
        {
            // Restore default UI
            self.authType = _authType;
        }
        else
        {
            // Refresh the IS anyway
            [self checkIdentityServer];
        }
    }
}

- (void)setIdentityServerTextFieldText:(NSString *)identityServerUrl
{    
    _identityServerTextField.text = identityServerUrl;
    
    [self updateIdentityServerURL:identityServerUrl];
}

- (void)updateIdentityServerURL:(NSString*)url
{
    if (![self.identityService.identityServer isEqualToString:url])
    {
        if (url.length)
        {
            self.identityService = [[MXIdentityService alloc] initWithIdentityServer:url accessToken:nil andHomeserverRestClient:mxRestClient];
        }
        else
        {
            self.identityService = nil;
        }
    }
    
    [mxRestClient setIdentityServer:url.length ? url : nil];
}

- (void)setIdentityServerHidden:(BOOL)hidden
{
    _identityServerContainer.hidden = hidden;
}

- (void)checkIdentityServer
{
    [self cancelIdentityServerCheck];

    // Hide the field while checking data
    [self setIdentityServerHidden:YES];

    NSString *homeserver = mxRestClient.homeserver;

    // First, fetch the IS advertised by the HS
    if (homeserver)
    {
        MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServer for homeserver %@", homeserver);

        autoDiscovery = [[MXAutoDiscovery alloc] initWithUrl:homeserver];

        MXWeakify(self);
        checkIdentityServerOperation = [autoDiscovery findClientConfig:^(MXDiscoveredClientConfig * _Nonnull discoveredClientConfig) {
            MXStrongifyAndReturnIfNil(self);

            NSString *identityServer = discoveredClientConfig.wellKnown.identityServer.baseUrl;
            MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServer: Identity server: %@", identityServer);

            if (identityServer)
            {
                // Apply the provided IS
                [self setIdentityServerTextFieldText:identityServer];
            }

            // Then, check if the HS needs an IS for running
            MXWeakify(self);
            MXHTTPOperation *operation = [self checkIdentityServerRequirementWithCompletion:^(BOOL identityServerRequired) {
                
                MXStrongifyAndReturnIfNil(self);

                self->checkIdentityServerOperation = nil;

                // Show the field only if an IS is required so that the user can customise it
                [self setIdentityServerHidden:!identityServerRequired];
            }];

            if (operation)
            {
                [self->checkIdentityServerOperation mutateTo:operation];
            }
            else
            {
                self->checkIdentityServerOperation = nil;
            }

            self->autoDiscovery = nil;

        } failure:^(NSError *error) {
            MXStrongifyAndReturnIfNil(self);

            // No need to report this error to the end user
            // There will be already an error about failing to get the auth flow from the HS
            MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServer. Error: %@", error);

            self->autoDiscovery = nil;
        }];
    }
}

- (void)cancelIdentityServerCheck
{
    if (checkIdentityServerOperation)
    {
        [checkIdentityServerOperation cancel];
        checkIdentityServerOperation = nil;
    }
}

- (MXHTTPOperation*)checkIdentityServerRequirementWithCompletion:(void (^)(BOOL identityServerRequired))completion
{
    MXHTTPOperation *operation;

    if (_authType == MXKAuthenticationTypeLogin)
    {
        // The identity server is only required for registration and password reset
        // It is then stored in the user account data
        completion(NO);
    }
    else
    {
        operation = [mxRestClient supportedMatrixVersions:^(MXMatrixVersions *matrixVersions) {

            MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServerRequirement: %@", matrixVersions.doesServerRequireIdentityServerParam ? @"YES": @"NO");
            completion(matrixVersions.doesServerRequireIdentityServerParam);

        } failure:^(NSError *error) {
            // No need to report this error to the end user
            // There will be already an error about failing to get the auth flow from the HS
            MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServerRequirement. Error: %@", error);
        }];
    }

    return operation;
}

- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
{
    _submitButton.enabled = (userInteractionEnabled && _authInputsView.areAllRequiredFieldsSet);
    _authSwitchButton.enabled = userInteractionEnabled;
    
    _homeServerTextField.enabled = userInteractionEnabled;
    _identityServerTextField.enabled = userInteractionEnabled;
    
    _userInteractionEnabled = userInteractionEnabled;
}

- (void)refreshAuthenticationSession
{
    // Remove reachability observer
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
    
    // Cancel potential request in progress
    [mxCurrentOperation cancel];
    mxCurrentOperation = nil;
    
    // Reset potential authentication fallback url
    authenticationFallback = nil;
    
    if (mxRestClient && (self.authType == MXKAuthenticationTypeLogin || self.authType == MXKAuthenticationTypeRegister))
    {
        MXWeakify(self);
        
        // Get the login session to determine available SSO flows.
        mxCurrentOperation = [mxRestClient getLoginSession:^(MXAuthenticationSession* loginAuthSession) {
            MXStrongifyAndReturnIfNil(self);
            
            if (self.authType == MXKAuthenticationTypeRegister)
            {
                MXWeakify(self);
                self->mxCurrentOperation = [self->mxRestClient getRegisterSession:^(MXAuthenticationSession* registerAuthSession) {
                    MXStrongifyAndReturnIfNil(self);
                    
                    // Handle the register session along with any SSO flows from the login session
                    MXLoginSSOFlow *loginSSOFlow = [self loginSSOFlowWithProvidersFromFlows:loginAuthSession.flows];
                    [self handleAuthenticationSession:registerAuthSession withFallbackSSOFlow:loginSSOFlow];
                    
                } failure:^(NSError *error) {
                    
                    [self onFailureDuringMXOperation:error];
                    
                }];
            }
            else
            {
                // Handle the login session by itself
                [self handleAuthenticationSession:loginAuthSession withFallbackSSOFlow:nil];
            }
            
        } failure:^(NSError *error) {
            
            MXStrongifyAndReturnIfNil(self);
            [self onFailureDuringMXOperation:error];
            
        }];
    }
    else
    {
        // Not supported for other types
        MXLogDebug(@"[MXKAuthenticationVC] refreshAuthenticationSession is ignored");
    }
}

- (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession withFallbackSSOFlow:(MXLoginSSOFlow *)fallbackSSOFlow
{
    mxCurrentOperation = nil;
    
    [_authenticationActivityIndicator stopAnimating];
    
    // Check whether fallback is defined, and retrieve the right input view class.
    Class authInputsViewClass;
    if (_authType == MXKAuthenticationTypeLogin)
    {
        authenticationFallback = [mxRestClient loginFallback];
        authInputsViewClass = loginAuthInputsViewClass;
        
    }
    else if (_authType == MXKAuthenticationTypeRegister)
    {
        authenticationFallback = [mxRestClient registerFallback];
        authInputsViewClass = registerAuthInputsViewClass;
    }
    else
    {
        // Not supported for other types
        MXLogDebug(@"[MXKAuthenticationVC] handleAuthenticationSession is ignored");
        return;
    }
    
    MXKAuthInputsView *authInputsView = nil;
    if (authInputsViewClass)
    {
        // Instantiate a new auth inputs view, except if the current one is already an instance of this class.
        if (self.authInputsView && self.authInputsView.class == authInputsViewClass)
        {
            // Use the current view
            authInputsView = self.authInputsView;
        }
        else
        {
            authInputsView = [authInputsViewClass authInputsView];
        }
    }
    
    if (authInputsView)
    {
        // Apply authentication session on inputs view
        if ([authInputsView setAuthSession:authSession withAuthType:_authType] == NO)
        {
            MXLogDebug(@"[MXKAuthenticationVC] Received authentication settings are not supported");
            authInputsView = nil;
        }
        else if (!_softLogoutCredentials)
        {
            // If all listed flows in this authentication session are not supported we suggest using the fallback page.
            if (authenticationFallback.length && authInputsView.authSession.flows.count == 0)
            {
                MXLogDebug(@"[MXKAuthenticationVC] No supported flow, suggest using fallback page");
                authInputsView = nil;
            }
            else if (authInputsView.authSession.flows.count != authSession.flows.count)
            {
                MXLogDebug(@"[MXKAuthenticationVC] The authentication session contains at least one unsupported flow");
            }
        }
    }
    
    if (authInputsView)
    {
        // Check whether the current view must be replaced
        if (self.authInputsView != authInputsView)
        {
            // Refresh layout
            self.authInputsView = authInputsView;
        }
        
        // Refresh user interaction
        self.userInteractionEnabled = _userInteractionEnabled;
        
        // Check whether an external set of parameters have been defined to pursue a registration
        if (self.externalRegistrationParameters)
        {
            if ([authInputsView setExternalRegistrationParameters:self.externalRegistrationParameters])
            {
                // Launch authentication now
                [self onButtonPressed:_submitButton];
            }
            else
            {
                [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n notSupportedYet]}]];
                
                _externalRegistrationParameters = nil;
                
                // Restore login screen on failure
                self.authType = MXKAuthenticationTypeLogin;
            }
        }

        if (_softLogoutCredentials)
        {
            [authInputsView setSoftLogoutCredentials:_softLogoutCredentials];
        }
    }
    else
    {
        // Remove the potential auth inputs view
        self.authInputsView = nil;
        
        // Cancel external registration parameters if any
        _externalRegistrationParameters = nil;
        
        // Notify user that no flow is supported
        if (_authType == MXKAuthenticationTypeLogin)
        {
            _noFlowLabel.text = [VectorL10n loginErrorDoNotSupportLoginFlows];
        }
        else
        {
            _noFlowLabel.text = [VectorL10n loginErrorRegistrationIsNotSupported];
        }
        MXLogDebug(@"[MXKAuthenticationVC] Warning: %@", _noFlowLabel.text);
        
        if (authenticationFallback.length)
        {
            [_retryButton setTitle:[VectorL10n loginUseFallback] forState:UIControlStateNormal];
            [_retryButton setTitle:[VectorL10n loginUseFallback] forState:UIControlStateNormal];
        }
        else
        {
            [_retryButton setTitle:[VectorL10n retry] forState:UIControlStateNormal];
            [_retryButton setTitle:[VectorL10n retry] forState:UIControlStateNormal];
        }
        
        _noFlowLabel.hidden = NO;
        _retryButton.hidden = NO;
    }
}

- (void)setExternalRegistrationParameters:(NSDictionary*)parameters
{
    if (parameters.count)
    {
        MXLogDebug(@"[MXKAuthenticationVC] setExternalRegistrationParameters");
        
        // Cancel the current operation if any.
        [self cancel];
        
        // Load the view controller’s view if it has not yet been loaded.
        // This is required before updating view's textfields (homeserver url...)
        [self loadViewIfNeeded];
        
        // Force register mode
        self.authType = MXKAuthenticationTypeRegister;
        
        // Apply provided homeserver if any
        id hs_url = parameters[@"hs_url"];
        NSString *homeserverURL = nil;
        if (hs_url && [hs_url isKindOfClass:NSString.class])
        {
            homeserverURL = hs_url;
        }
        [self setHomeServerTextFieldText:homeserverURL];
        
        // Apply provided identity server if any
        id is_url = parameters[@"is_url"];
        NSString *identityURL = nil;
        if (is_url && [is_url isKindOfClass:NSString.class])
        {
            identityURL = is_url;
        }
        [self setIdentityServerTextFieldText:identityURL];
        
        // Disable user interaction
        self.userInteractionEnabled = NO;
        
        // Cancel potential request in progress
        [mxCurrentOperation cancel];
        mxCurrentOperation = nil;
        
        // Remove the current auth inputs view
        self.authInputsView = nil;
        
        // Set external parameters and trigger a refresh (the parameters will be taken into account during [handleAuthenticationSession:])
        _externalRegistrationParameters = parameters;
        [self refreshAuthenticationSession];
    }
    else
    {
        MXLogDebug(@"[MXKAuthenticationVC] reset externalRegistrationParameters");
        _externalRegistrationParameters = nil;
        
        // Restore default UI
        self.authType = _authType;
    }
}

- (void)setSoftLogoutCredentials:(MXCredentials *)softLogoutCredentials
{
    MXLogDebug(@"[MXKAuthenticationVC] setSoftLogoutCredentials");

    // Cancel the current operation if any.
    [self cancel];

    // Load the view controller’s view if it has not yet been loaded.
    // This is required before updating view's textfields (homeserver url...)
    [self loadViewIfNeeded];

    if (softLogoutCredentials)
    {
        // Force register mode
        self.authType = MXKAuthenticationTypeLogin;

        [self setHomeServerTextFieldText:softLogoutCredentials.homeServer];
        [self setIdentityServerTextFieldText:softLogoutCredentials.identityServer];

        // Cancel potential request in progress
        [mxCurrentOperation cancel];
        mxCurrentOperation = nil;

        // Remove the current auth inputs view
        self.authInputsView = nil;
    }

    // Set parameters and trigger a refresh (the parameters will be taken into account during [handleAuthenticationSession:])
    _softLogoutCredentials = softLogoutCredentials;
    [self refreshAuthenticationSession];
}

- (void)setOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertificateBlock
{
    onUnrecognizedCertificateCustomBlock = onUnrecognizedCertificateBlock;
}

- (void)isUserNameInUse:(void (^)(BOOL isUserNameInUse))callback
{
    mxCurrentOperation = [mxRestClient isUserNameInUse:self.authInputsView.userId callback:^(BOOL isUserNameInUse) {
        
        self->mxCurrentOperation = nil;
        
        if (callback)
        {
            callback (isUserNameInUse);
        }
        
    }];
}

- (void)testUserRegistration:(void (^)(MXError *mxError))callback
{
    mxCurrentOperation = [mxRestClient testUserRegistration:self.authInputsView.userId callback:callback];
}

- (MXLoginSSOFlow*)loginSSOFlowWithProvidersFromFlows:(NSArray<MXLoginFlow*>*)loginFlows
{
    MXLoginSSOFlow *ssoFlowWithProviders;
    
    for (MXLoginFlow *loginFlow in loginFlows)
    {
        if ([loginFlow isKindOfClass:MXLoginSSOFlow.class])
        {
            MXLoginSSOFlow *ssoFlow = (MXLoginSSOFlow *)loginFlow;
            
            if (ssoFlow.identityProviders.count)
            {
                ssoFlowWithProviders = ssoFlow;
                break;
            }
        }
    }
    
    return ssoFlowWithProviders;
}

- (IBAction)onButtonPressed:(id)sender
{
    [self dismissKeyboard];
    
    if (sender == _submitButton)
    {
        // Disable user interaction to prevent multiple requests
        self.userInteractionEnabled = NO;
        
        // Check parameters validity
        NSString *errorMsg = [self.authInputsView validateParameters];
        if (errorMsg)
        {
            [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:errorMsg}]];
        }
        else
        {
            [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator];
            
            // Launch the authentication according to its type
            if (_authType == MXKAuthenticationTypeLogin)
            {
                // Prepare the parameters dict
                [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
                    
                    if (parameters && self->mxRestClient)
                    {
                        [self->_authenticationActivityIndicator startAnimating];
                        [self loginWithParameters:parameters];
                    }
                    else
                    {
                        MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
                        [self onFailureDuringAuthRequest:error];
                    }
                    
                }];
            }
            else if (_authType == MXKAuthenticationTypeRegister)
            {
                // Check here the availability of the userId
                if (self.authInputsView.userId.length)
                {
                    [_authenticationActivityIndicator startAnimating];
                    
                    if (self.authInputsView.password.length)
                    {
                        // Trigger here a register request in order to associate the filled userId and password to the current session id
                        // This will check the availability of the userId at the same time
                        NSDictionary *parameters = @{@"auth": @{
                                                        @"session": self.authInputsView.authSession.session,
                                                        @"type": kMXLoginFlowTypeDummy
                                                     },
                                                     @"username": self.authInputsView.userId,
                                                     @"password": self.authInputsView.password,
                                                     @"bind_email": @(NO),
                                                     @"initial_device_display_name":self.deviceDisplayName
                                                     };
                        
                        mxCurrentOperation = [mxRestClient registerWithParameters:parameters success:^(NSDictionary *JSONResponse) {
                            
                            // Unexpected case where the registration succeeds without any other stages
                            MXLoginResponse *loginResponse;
                            MXJSONModelSetMXJSONModel(loginResponse, MXLoginResponse, JSONResponse);

                            MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse
                                                                                andDefaultCredentials:self->mxRestClient.credentials];
                            
                            // Sanity check
                            if (!credentials.userId || !credentials.accessToken)
                            {
                                [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n notSupportedYet]}]];
                            }
                            else
                            {
                                MXLogDebug(@"[MXKAuthenticationVC] Registration succeeded");

                                // Report the certificate trusted by user (if any)
                                credentials.allowedCertificate = self->mxRestClient.allowedCertificate;
                                
                                [self onSuccessfulLogin:credentials];
                            }
                            
                        } failure:^(NSError *error) {
                            
                            self->mxCurrentOperation = nil;
                            
                            // An updated authentication session should be available in response data in case of unauthorized request.
                            NSDictionary *JSONResponse = nil;
                            if (error.userInfo[MXHTTPClientErrorResponseDataKey])
                            {
                                JSONResponse = error.userInfo[MXHTTPClientErrorResponseDataKey];
                            }
                            
                            if (JSONResponse)
                            {
                                MXAuthenticationSession *authSession = [MXAuthenticationSession modelFromJSON:JSONResponse];
                                
                                [self->_authenticationActivityIndicator stopAnimating];
                                
                                // Update session identifier
                                self.authInputsView.authSession.session = authSession.session;
                                
                                // Launch registration by preparing parameters dict
                                [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
                                    
                                    if (parameters && self->mxRestClient)
                                    {
                                        [self->_authenticationActivityIndicator startAnimating];
                                        [self registerWithParameters:parameters];
                                    }
                                    else
                                    {
                                        MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
                                        [self onFailureDuringAuthRequest:error];
                                    }
                                    
                                }];
                            }
                            else
                            {
                                [self onFailureDuringAuthRequest:error];
                            }
                        }];
                    }
                    else
                    {
                        [self isUserNameInUse:^(BOOL isUserNameInUse) {
                            
                            if (isUserNameInUse)
                            {
                                MXLogDebug(@"[MXKAuthenticationVC] User name is already use");
                                [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n authUsernameInUse]}]];
                            }
                            else
                            {
                                [self->_authenticationActivityIndicator stopAnimating];
                               
                                // Launch registration by preparing parameters dict
                                [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
                                    
                                    if (parameters && self->mxRestClient)
                                    {
                                        [self->_authenticationActivityIndicator startAnimating];
                                        [self registerWithParameters:parameters];
                                    }
                                    else
                                    {
                                        MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
                                        [self onFailureDuringAuthRequest:error];
                                    }
                                    
                                }];
                            }
                            
                        }];
                    }
                }
                else if (self.externalRegistrationParameters)
                {
                    // Launch registration by preparing parameters dict
                    [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
                        
                        if (parameters && self->mxRestClient)
                        {
                            [self->_authenticationActivityIndicator startAnimating];
                            [self registerWithParameters:parameters];
                        }
                        else
                        {
                            MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
                            [self onFailureDuringAuthRequest:error];
                        }
                        
                    }];
                }
                else
                {
                    MXLogDebug(@"[MXKAuthenticationVC] User name is missing");
                    [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n authInvalidUserName]}]];
                }
            }
            else if (_authType == MXKAuthenticationTypeForgotPassword)
            {
                // Check whether the password has been reseted
                if (isPasswordReseted)
                {
                    // Return to login screen
                    self.authType = MXKAuthenticationTypeLogin;
                }
                else
                {
                    // Prepare the parameters dict
                    [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
                        
                        if (parameters && self->mxRestClient)
                        {
                            [self->_authenticationActivityIndicator startAnimating];
                            [self resetPasswordWithParameters:parameters];
                        }
                        else
                        {
                            MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
                            [self onFailureDuringAuthRequest:error];
                        }
                        
                    }];
                }
            }
        }
    }
    else if (sender == _authSwitchButton)
    {
        if (_authType == MXKAuthenticationTypeLogin)
        {
            self.authType = MXKAuthenticationTypeRegister;
        }
        else
        {
            self.authType = MXKAuthenticationTypeLogin;
        }
    }
    else if (sender == _retryButton)
    {
        if (authenticationFallback)
        {
            [self showAuthenticationFallBackView:authenticationFallback];
        }
        else
        {
            [self refreshAuthenticationSession];
        }
    }
    else if (sender == _cancelAuthFallbackButton)
    {
        // Hide fallback webview
        [self hideRegistrationFallbackView];
    }
}

- (void)cancel
{
    MXLogDebug(@"[MXKAuthenticationVC] cancel");
    
    // Cancel external registration parameters if any
    _externalRegistrationParameters = nil;
    
    if (registrationTimer)
    {
        [registrationTimer invalidate];
        registrationTimer = nil;
    }
    
    // Cancel request in progress
    if (mxCurrentOperation)
    {
        [mxCurrentOperation cancel];
        mxCurrentOperation = nil;
    }
    
    [_authenticationActivityIndicator stopAnimating];
    self.userInteractionEnabled = YES;
    
    // Reset potential completed stages
    self.authInputsView.authSession.completed = nil;
    
    // Update authentication inputs view to return in initial step
    [self.authInputsView setAuthSession:self.authInputsView.authSession withAuthType:_authType];
}

- (void)onFailureDuringAuthRequest:(NSError *)error
{
    mxCurrentOperation = nil;
    [_authenticationActivityIndicator stopAnimating];
    self.userInteractionEnabled = YES;
    
    // Ignore connection cancellation error
    if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled))
    {
        MXLogDebug(@"[MXKAuthenticationVC] Auth request cancelled");
        return;
    }
    
    MXLogDebug(@"[MXKAuthenticationVC] Auth request failed: %@", error);
    
    // Cancel external registration parameters if any
    _externalRegistrationParameters = nil;
    
    // Translate the error code to a human message
    NSString *title = error.localizedFailureReason;
    if (!title)
    {
        if (self.authType == MXKAuthenticationTypeLogin)
        {
            title = [VectorL10n loginErrorTitle];
        }
        else if (self.authType == MXKAuthenticationTypeRegister)
        {
            title = [VectorL10n registerErrorTitle];
        }
        else
        {
            title = [VectorL10n error];
        }
    }
    NSString* message = error.localizedDescription;
    NSDictionary* dict = error.userInfo;
    
    // detect if it is a Matrix SDK issue
    if (dict)
    {
        NSString* localizedError = [dict valueForKey:@"error"];
        NSString* errCode = [dict valueForKey:@"errcode"];
        
        if (localizedError.length > 0)
        {
            message = localizedError;
        }
        
        if (errCode)
        {
            if ([errCode isEqualToString:kMXErrCodeStringForbidden])
            {
                message = [VectorL10n loginErrorForbidden];
            }
            else if ([errCode isEqualToString:kMXErrCodeStringUnknownToken])
            {
                message = [VectorL10n loginErrorUnknownToken];
            }
            else if ([errCode isEqualToString:kMXErrCodeStringBadJSON])
            {
                message = [VectorL10n loginErrorBadJson];
            }
            else if ([errCode isEqualToString:kMXErrCodeStringNotJSON])
            {
                message = [VectorL10n loginErrorNotJson];
            }
            else if ([errCode isEqualToString:kMXErrCodeStringLimitExceeded])
            {
                message = [VectorL10n loginErrorLimitExceeded];
            }
            else if ([errCode isEqualToString:kMXErrCodeStringUserInUse])
            {
                message = [VectorL10n loginErrorUserInUse];
            }
            else if ([errCode isEqualToString:kMXErrCodeStringLoginEmailURLNotYet])
            {
                message = [VectorL10n loginErrorLoginEmailNotYet];
            }
            else if ([errCode isEqualToString:kMXErrCodeStringResourceLimitExceeded])
            {
                [self showResourceLimitExceededError:dict onAdminContactTapped:nil];
                return;
            }
            else if (!message.length)
            {
                message = errCode;
            }
        }
    }
    
    // Alert user
    if (alert)
    {
        [alert dismissViewControllerAnimated:NO completion:nil];
    }
    
    alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    
    [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
                                              style:UIAlertActionStyleDefault
                                            handler:^(UIAlertAction * action) {
                                                
                                                self->alert = nil;
                                                
                                            }]];
    
    
    [self presentViewController:alert animated:YES completion:nil];
    
    // Update authentication inputs view to return in initial step
    [self.authInputsView setAuthSession:self.authInputsView.authSession withAuthType:_authType];
    if (self.softLogoutCredentials)
    {
        self.authInputsView.softLogoutCredentials = self.softLogoutCredentials;
    }
}

- (void)showResourceLimitExceededError:(NSDictionary *)errorDict onAdminContactTapped:(void (^)(NSURL *adminContact))onAdminContactTapped
{
    mxCurrentOperation = nil;
    [_authenticationActivityIndicator stopAnimating];
    self.userInteractionEnabled = YES;

    // Alert user
    if (alert)
    {
        [alert dismissViewControllerAnimated:NO completion:nil];
    }

    // Parse error data
    NSString *limitType, *adminContactString;
    NSURL *adminContact;

    MXJSONModelSetString(limitType, errorDict[kMXErrorResourceLimitExceededLimitTypeKey]);
    MXJSONModelSetString(adminContactString, errorDict[kMXErrorResourceLimitExceededAdminContactKey]);

    if (adminContactString)
    {
        adminContact = [NSURL URLWithString:adminContactString];
    }

    NSString *title = [VectorL10n loginErrorResourceLimitExceededTitle];

    // Build the message content
    NSMutableString *message = [NSMutableString new];
    if ([limitType isEqualToString:kMXErrorResourceLimitExceededLimitTypeMonthlyActiveUserValue])
    {
        [message appendString:[VectorL10n loginErrorResourceLimitExceededMessageMonthlyActiveUser]];
    }
    else
    {
        [message appendString:[VectorL10n loginErrorResourceLimitExceededMessageDefault]];
    }

    [message appendString:[VectorL10n loginErrorResourceLimitExceededMessageContact]];

    // Build the alert
    alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];

    MXWeakify(self);
    if (adminContact && onAdminContactTapped)
    {
        [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n loginErrorResourceLimitExceededContactButton]
                                                  style:UIAlertActionStyleDefault
                                                handler:^(UIAlertAction * action)
                          {
                              MXStrongifyAndReturnIfNil(self);
                              self->alert = nil;

                              // Let the system handle the URI
                              // It could be something like "mailto: server.admin@example.com"
                              onAdminContactTapped(adminContact);
                          }]];
    }

    [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                              style:UIAlertActionStyleDefault
                                            handler:^(UIAlertAction * action)
                      {
                          MXStrongifyAndReturnIfNil(self);
                          self->alert = nil;
                      }]];

    [self presentViewController:alert animated:YES completion:nil];

    // Update authentication inputs view to return in initial step
    [self.authInputsView setAuthSession:self.authInputsView.authSession withAuthType:_authType];
}

- (void)onSuccessfulLogin:(MXCredentials*)credentials
{
    mxCurrentOperation = nil;
    [_authenticationActivityIndicator stopAnimating];
    self.userInteractionEnabled = YES;

    if (self.softLogoutCredentials)
    {
        // Hydrate the account with the new access token
        MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:self.softLogoutCredentials.userId];
        [[MXKAccountManager sharedManager] hydrateAccount:account withCredentials:credentials];

        if (_delegate)
        {
            [_delegate authenticationViewController:self didLogWithUserId:credentials.userId];
        }
    }
    // Sanity check: check whether the user is not already logged in with this id
    else if ([[MXKAccountManager sharedManager] accountForUserId:credentials.userId])
    {
        //Alert user
        __weak typeof(self) weakSelf = self;
        
        if (alert)
        {
            [alert dismissViewControllerAnimated:NO completion:nil];
        }
        
        alert = [UIAlertController alertControllerWithTitle:[VectorL10n loginErrorAlreadyLoggedIn] message:nil preferredStyle:UIAlertControllerStyleAlert];
        
        [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
                                                  style:UIAlertActionStyleDefault
                                                handler:^(UIAlertAction * action) {
                                                    
                                                    // We remove the authentication view controller.
                                                    typeof(self) self = weakSelf;
                                                    self->alert = nil;
                                                    [self withdrawViewControllerAnimated:YES completion:nil];
                                                    
                                                }]];
        
        
        [self presentViewController:alert animated:YES completion:nil];
    }
    else
    {
        // Report the new account in account manager
        if (!credentials.identityServer)
        {
            credentials.identityServer = _identityServerTextField.text;
        }
        
        [self createAccountWithCredentials:credentials];
    }
}

- (MXHTTPOperation *)currentHttpOperation
{
    return mxCurrentOperation;
}

#pragma mark - Privates

// Hook point for triggering device rehydration in subclasses
// Avoid cycles by using a separate private method do to the actual work
- (void)createAccountWithCredentials:(MXCredentials *)credentials
{
    [self _createAccountWithCredentials:credentials];
}

- (void)_createAccountWithCredentials:(MXCredentials *)credentials
{
    MXKAccount *account = [[MXKAccount alloc] initWithCredentials:credentials];
    account.identityServerURL = credentials.identityServer;
    
    [[MXKAccountManager sharedManager] addAccount:account andOpenSession:YES];
    
    if (_delegate)
    {
        [_delegate authenticationViewController:self didLogWithUserId:credentials.userId];
    }
}

- (NSString *)deviceDisplayName
{
    if (_deviceDisplayName)
    {
        return _deviceDisplayName;
    }
    
#if TARGET_OS_IPHONE
    NSString *deviceName = [[UIDevice currentDevice].model isEqualToString:@"iPad"] ? [VectorL10n loginTabletDevice] : [VectorL10n loginMobileDevice];
#elif TARGET_OS_OSX
    NSString *deviceName = [VectorL10n loginDesktopDevice];
#endif
    
    return deviceName;
}

- (void)refreshForgotPasswordSession
{
    [_authenticationActivityIndicator stopAnimating];
    
    MXKAuthInputsView *authInputsView = nil;
    if (forgotPasswordAuthInputsViewClass)
    {
        // Instantiate a new auth inputs view, except if the current one is already an instance of this class.
        if (self.authInputsView && self.authInputsView.class == forgotPasswordAuthInputsViewClass)
        {
            // Use the current view
            authInputsView = self.authInputsView;
        }
        else
        {
            authInputsView = [forgotPasswordAuthInputsViewClass authInputsView];
        }
    }
    
    if (authInputsView)
    {
        // Update authentication inputs view to return in initial step
        [authInputsView setAuthSession:nil withAuthType:MXKAuthenticationTypeForgotPassword];
        
        // Check whether the current view must be replaced
        if (self.authInputsView != authInputsView)
        {
            // Refresh layout
            self.authInputsView = authInputsView;
        }
        
        // Refresh user interaction
        self.userInteractionEnabled = _userInteractionEnabled;
    }
    else
    {
        // Remove the potential auth inputs view
        self.authInputsView = nil;
        
        _noFlowLabel.text = [VectorL10n loginErrorForgotPasswordIsNotSupported];
        
        MXLogDebug(@"[MXKAuthenticationVC] Warning: %@", _noFlowLabel.text);
        
        _noFlowLabel.hidden = NO;
    }
}

- (void)updateRESTClient
{
    NSString *homeserverURL = [HomeserverAddress sanitized:_homeServerTextField.text];
    
    if (homeserverURL.length)
    {
        // Check change
        if ([homeserverURL isEqualToString:mxRestClient.homeserver] == NO)
        {
            mxRestClient = [[MXRestClient alloc] initWithHomeServer:homeserverURL andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) {
                
                // Check first if the app developer provided its own certificate handler.
                if (self->onUnrecognizedCertificateCustomBlock)
                {
                    return self->onUnrecognizedCertificateCustomBlock (certificate);
                }
                
                // Else prompt the user by displaying a fingerprint (SHA256) of the certificate.
                __block BOOL isTrusted;
                dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
                
                NSString *title = [VectorL10n sslCouldNotVerify];
                NSString *homeserverURLStr = [VectorL10n sslHomeserverUrl:homeserverURL];
                NSString *fingerprint = [VectorL10n sslFingerprintHash:@"SHA256"];
                NSString *certFingerprint = [certificate mx_SHA256AsHexString];
                
                NSString *msg = [NSString stringWithFormat:@"%@\n\n%@\n\n%@\n\n%@\n\n%@\n\n%@", [VectorL10n sslCertNotTrust], [VectorL10n sslCertNewAccountExpl], homeserverURLStr, fingerprint, certFingerprint, [VectorL10n sslOnlyAccept]];
                
                if (self->alert)
                {
                    [self->alert dismissViewControllerAnimated:NO completion:nil];
                }
                
                self->alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
                
                [self->alert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel]
                                                          style:UIAlertActionStyleDefault
                                                        handler:^(UIAlertAction * action) {
                                                            
                                                            self->alert = nil;
                                                            isTrusted = NO;
                                                            dispatch_semaphore_signal(semaphore);
                                                            
                                                        }]];
                
                [self->alert addAction:[UIAlertAction actionWithTitle:[VectorL10n sslTrust]
                                                          style:UIAlertActionStyleDefault
                                                        handler:^(UIAlertAction * action) {
                                                            
                                                            self->alert = nil;
                                                            isTrusted = YES;
                                                            dispatch_semaphore_signal(semaphore);
                                                            
                                                        }]];
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self presentViewController:self->alert animated:YES completion:nil];
                });
                
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                
                if (!isTrusted)
                {
                    // Cancel request in progress
                    [self->mxCurrentOperation cancel];
                    self->mxCurrentOperation = nil;
                    [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];

                    [self->_authenticationActivityIndicator stopAnimating];
                }
                
                return isTrusted;
            }];
            
            if (_identityServerTextField.text.length)
            {
                [self updateIdentityServerURL:self.identityServerTextField.text];
            }
        }
    }
    else
    {
        [mxRestClient close];
        mxRestClient = nil;
    }
}

- (void)loginWithParameters:(NSDictionary*)parameters
{
    // Add the device name
    NSMutableDictionary *theParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
    theParameters[@"initial_device_display_name"] = self.deviceDisplayName;
    
    mxCurrentOperation = [mxRestClient login:theParameters success:^(NSDictionary *JSONResponse) {

        MXLoginResponse *loginResponse;
        MXJSONModelSetMXJSONModel(loginResponse, MXLoginResponse, JSONResponse);

        MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse
                                                            andDefaultCredentials:self->mxRestClient.credentials];
        
        // Sanity check
        if (!credentials.userId || !credentials.accessToken)
        {
            [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n notSupportedYet]}]];
        }
        else
        {
            MXLogDebug(@"[MXKAuthenticationVC] Login process succeeded");

            // Report the certificate trusted by user (if any)
            credentials.allowedCertificate = self->mxRestClient.allowedCertificate;
            
            [self onSuccessfulLogin:credentials];
        }
        
    } failure:^(NSError *error) {
        
        [self onFailureDuringAuthRequest:error];
        
    }];
}

- (void)registerWithParameters:(NSDictionary*)parameters
{
    if (registrationTimer)
    {
        [registrationTimer invalidate];
        registrationTimer = nil;
    }
    
    // Add the device name
    NSMutableDictionary *theParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
    theParameters[@"initial_device_display_name"] = self.deviceDisplayName;
    
    mxCurrentOperation = [mxRestClient registerWithParameters:theParameters success:^(NSDictionary *JSONResponse) {
        
        MXLoginResponse *loginResponse;
        MXJSONModelSetMXJSONModel(loginResponse, MXLoginResponse, JSONResponse);

        MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse
                                                            andDefaultCredentials:self->mxRestClient.credentials];
        
        // Sanity check
        if (!credentials.userId || !credentials.accessToken)
        {
            [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n notSupportedYet]}]];
        }
        else
        {
            MXLogDebug(@"[MXKAuthenticationVC] Registration succeeded");

            // Report the certificate trusted by user (if any)
            credentials.allowedCertificate = self->mxRestClient.allowedCertificate;
            
            [self onSuccessfulLogin:credentials];
        }
        
    } failure:^(NSError *error) {
        
        self->mxCurrentOperation = nil;
        
        // Check whether the authentication is pending (for example waiting for email validation)
        MXError *mxError = [[MXError alloc] initWithNSError:error];
        if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnauthorized])
        {
            MXLogDebug(@"[MXKAuthenticationVC] Wait for email validation");
            
            // Postpone a new attempt in 10 sec
            self->registrationTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(registrationTimerFireMethod:) userInfo:parameters repeats:NO];
        }
        else
        {
            // The completed stages should be available in response data in case of unauthorized request.
            NSDictionary *JSONResponse = nil;
            if (error.userInfo[MXHTTPClientErrorResponseDataKey])
            {
                JSONResponse = error.userInfo[MXHTTPClientErrorResponseDataKey];
            }
            
            if (JSONResponse)
            {
                MXAuthenticationSession *authSession = [MXAuthenticationSession modelFromJSON:JSONResponse];
                
                if (authSession.completed)
                {
                    [self->_authenticationActivityIndicator stopAnimating];
                    
                    // Update session identifier in case of change
                    self.authInputsView.authSession.session = authSession.session;
                    
                    [self.authInputsView updateAuthSessionWithCompletedStages:authSession.completed didUpdateParameters:^(NSDictionary *parameters, NSError *error) {
                        
                        if (parameters)
                        {
                            MXLogDebug(@"[MXKAuthenticationVC] Pursue registration");
                            
                            [self->_authenticationActivityIndicator startAnimating];
                            [self registerWithParameters:parameters];
                        }
                        else
                        {
                            MXLogDebug(@"[MXKAuthenticationVC] Failed to update parameters");
                            
                            [self onFailureDuringAuthRequest:error];
                        }
                        
                    }];
                    
                    return;
                }
                
                [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n notSupportedYet]}]];
            }
            else
            {
                [self onFailureDuringAuthRequest:error];
            }
        }
    }];
}

- (void)registrationTimerFireMethod:(NSTimer *)timer
{
    if (timer == registrationTimer && timer.isValid)
    {
        MXLogDebug(@"[MXKAuthenticationVC] Retry registration");
        [self registerWithParameters:registrationTimer.userInfo];
    }
}

- (void)resetPasswordWithParameters:(NSDictionary*)parameters
{
    mxCurrentOperation = [mxRestClient resetPasswordWithParameters:parameters success:^() {
        
        MXLogDebug(@"[MXKAuthenticationVC] Reset password succeeded");
        
        self->mxCurrentOperation = nil;
        [self->_authenticationActivityIndicator stopAnimating];
        
        self->isPasswordReseted = YES;
        
        // Force UI update to refresh submit button title.
        self.authType = self->_authType;
        
        // Refresh the authentication inputs view on success.
        [self.authInputsView nextStep];
        
    } failure:^(NSError *error) {
        
        MXError *mxError = [[MXError alloc] initWithNSError:error];
        if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnauthorized])
        {
            MXLogDebug(@"[MXKAuthenticationVC] Forgot Password: wait for email validation");
            
            self->mxCurrentOperation = nil;
            [self->_authenticationActivityIndicator stopAnimating];
            
            if (self->alert)
            {
                [self->alert dismissViewControllerAnimated:NO completion:nil];
            }
            
            self->alert = [UIAlertController alertControllerWithTitle:[VectorL10n error] message:[VectorL10n authResetPasswordErrorUnauthorized] preferredStyle:UIAlertControllerStyleAlert];
            
            [self->alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
                                                      style:UIAlertActionStyleDefault
                                                    handler:^(UIAlertAction * action) {
                                                        
                                                        self->alert = nil;
                                                        
                                                    }]];
            
            
            [self presentViewController:self->alert animated:YES completion:nil];
        }
        else if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringNotFound])
        {
            MXLogDebug(@"[MXKAuthenticationVC] Forgot Password: not found");
            
            NSMutableDictionary *userInfo;
            if (error.userInfo)
            {
                userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
            }
            else
            {
                userInfo = [NSMutableDictionary dictionary];
            }
            userInfo[NSLocalizedDescriptionKey] = [VectorL10n authResetPasswordErrorNotFound];
            
            [self onFailureDuringAuthRequest:[NSError errorWithDomain:kMXNSErrorDomain code:0 userInfo:userInfo]];
        }
        else
        {
            [self onFailureDuringAuthRequest:error];
        }
        
    }];
}

- (void)onFailureDuringMXOperation:(NSError*)error
{
    mxCurrentOperation = nil;
    
    [_authenticationActivityIndicator stopAnimating];
    
    if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)
    {
        // Ignore this error
        MXLogDebug(@"[MXKAuthenticationVC] flows request cancelled");
        return;
    }
    
    MXLogDebug(@"[MXKAuthenticationVC] Failed to get %@ flows: %@", (_authType == MXKAuthenticationTypeLogin ? @"Login" : @"Register"), error);
    
    // Cancel external registration parameters if any
    _externalRegistrationParameters = nil;
    
    // Alert user
    NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
    if (!title)
    {
        title = [VectorL10n error];
    }
    NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
    
    if (alert)
    {
        [alert dismissViewControllerAnimated:NO completion:nil];
    }
    
    alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
    
    [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n dismiss]
                                              style:UIAlertActionStyleDefault
                                            handler:^(UIAlertAction * action) {
                                                
                                                self->alert = nil;
                                                
                                            }]];
    
    
    [self presentViewController:alert animated:YES completion:nil];
    
    // Handle specific error code here
    if ([error.domain isEqualToString:NSURLErrorDomain])
    {
        // Check network reachability
        if (error.code == NSURLErrorNotConnectedToInternet)
        {
            // Add reachability observer in order to launch a new request when network will be available
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReachabilityStatusChange:) name:AFNetworkingReachabilityDidChangeNotification object:nil];
        }
        else if (error.code == kCFURLErrorTimedOut)
        {
            // Send a new request in 2 sec
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self refreshAuthenticationSession];
            });
        }
        else
        {
            // Remove the potential auth inputs view
            self.authInputsView = nil;
        }
    }
    else
    {
        // Remove the potential auth inputs view
        self.authInputsView = nil;
    }
    
    if (!_authInputsView)
    {
        // Display failure reason
        _noFlowLabel.hidden = NO;
        _noFlowLabel.text = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
        if (!_noFlowLabel.text.length)
        {
            _noFlowLabel.text = [VectorL10n loginErrorNoLoginFlow];
        }
        [_retryButton setTitle:[VectorL10n retry] forState:UIControlStateNormal];
        [_retryButton setTitle:[VectorL10n retry] forState:UIControlStateNormal];
        _retryButton.hidden = NO;
    }
}

- (void)onReachabilityStatusChange:(NSNotification *)notif
{
    AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];
    AFNetworkReachabilityStatus status = reachabilityManager.networkReachabilityStatus;
    
    if (status == AFNetworkReachabilityStatusReachableViaWiFi || status == AFNetworkReachabilityStatusReachableViaWWAN)
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self refreshAuthenticationSession];
        });
    }
    else if (status == AFNetworkReachabilityStatusNotReachable)
    {
        _noFlowLabel.text = [VectorL10n networkErrorNotReachable];
    }
}

#pragma mark - Keyboard handling

- (void)dismissKeyboard
{
    // Hide the keyboard
    [_authInputsView dismissKeyboard];
    [_homeServerTextField resignFirstResponder];
    [_identityServerTextField resignFirstResponder];
}

#pragma mark - UITextField delegate

- (void)onTextFieldChange:(NSNotification *)notif
{
    _submitButton.enabled = _authInputsView.areAllRequiredFieldsSet;

    if (notif.object == _homeServerTextField)
    {
        // If any, the current request is obsolete
        [self cancelIdentityServerCheck];

        [self setIdentityServerHidden:YES];
    }
}

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
    if (textField == _homeServerTextField)
    {
        // Cancel supported AuthFlow refresh if a request is in progress
        [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
        
        if (mxCurrentOperation)
        {
            // Cancel potential request in progress
            [mxCurrentOperation cancel];
            mxCurrentOperation = nil;
        }
    }

    return YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    if (textField == _homeServerTextField)
    {
        [self setHomeServerTextFieldText:textField.text];
    }
    else if (textField == _identityServerTextField)
    {
        [self setIdentityServerTextFieldText:textField.text];
    }
}

- (BOOL)textFieldShouldReturn:(UITextField*)textField
{
    if (textField.returnKeyType == UIReturnKeyDone)
    {
        // "Done" key has been pressed
        [textField resignFirstResponder];
    }
    return YES;
}

#pragma mark - AuthInputsViewDelegate delegate

- (void)authInputsView:(MXKAuthInputsView*)authInputsView presentAlertController:(UIAlertController*)inputsAlert
{
    [self dismissKeyboard];
    [self presentViewController:inputsAlert animated:YES completion:nil];
}

- (void)authInputsViewDidPressDoneKey:(MXKAuthInputsView *)authInputsView
{
    if (_submitButton.isEnabled)
    {
        // Launch authentication now
        [self onButtonPressed:_submitButton];
    }
}

- (MXRestClient *)authInputsViewThirdPartyIdValidationRestClient:(MXKAuthInputsView *)authInputsView
{
    return mxRestClient;
}

- (MXIdentityService *)authInputsViewThirdPartyIdValidationIdentityService:(MXIdentityService *)authInputsView
{
    return self.identityService;
}

#pragma mark - Authentication Fallback

- (void)showAuthenticationFallBackView
{
    [self showAuthenticationFallBackView:authenticationFallback];
}

- (void)showAuthenticationFallBackView:(NSString*)fallbackPage
{
    _authenticationScrollView.hidden = YES;
    _authFallbackContentView.hidden = NO;
    
    // Add a cancel button in case of navigation controller use.
    if (self.navigationController)
    {
        if (!cancelFallbackBarButton)
        {
            cancelFallbackBarButton = [[UIBarButtonItem alloc] initWithTitle:[VectorL10n loginLeaveFallback] style:UIBarButtonItemStylePlain target:self action:@selector(hideRegistrationFallbackView)];
        }
        
        // Add cancel button in right bar items
        NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems;
        self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:cancelFallbackBarButton] : @[cancelFallbackBarButton];
    }

    if (self.softLogoutCredentials)
    {
        // Add device_id as query param of the fallback
        NSURLComponents *components = [[NSURLComponents alloc] initWithString:fallbackPage];

        NSMutableArray<NSURLQueryItem*> *queryItems = [components.queryItems mutableCopy];
        if (!queryItems)
        {
            queryItems = [NSMutableArray array];
        }

        [queryItems addObject:[NSURLQueryItem queryItemWithName:@"device_id"
                                                          value:self.softLogoutCredentials.deviceId]];

        components.queryItems = queryItems;

        fallbackPage = components.URL.absoluteString;
    }

    [_authFallbackWebView openFallbackPage:fallbackPage success:^(MXLoginResponse *loginResponse) {
        
        MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse andDefaultCredentials:self->mxRestClient.credentials];
        
        // TODO handle unrecognized certificate (if any) during registration through fallback webview.
        
        [self onSuccessfulLogin:credentials];
    }];
}

- (void)hideRegistrationFallbackView
{
    if (cancelFallbackBarButton)
    {
        NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems];
        [rightBarButtonItems removeObject:cancelFallbackBarButton];
        self.navigationItem.rightBarButtonItems = rightBarButtonItems;
    }
    
    [_authFallbackWebView stopLoading];
    _authenticationScrollView.hidden = NO;
    _authFallbackContentView.hidden = YES;
}

#pragma mark - KVO

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([@"viewHeightConstraint.constant" isEqualToString:keyPath])
    {
        // Refresh the height of the auth inputs view container.
        CGFloat previousInputsContainerViewHeight = _authInputContainerViewHeightConstraint.constant;
        _authInputContainerViewHeightConstraint.constant = _authInputsView.viewHeightConstraint.constant;
        
        // Force to render the view
        [self.view layoutIfNeeded];
        
        // Refresh content view height by considering the updated height of inputs container
        _contentViewHeightConstraint.constant += (_authInputContainerViewHeightConstraint.constant - previousInputsContainerViewHeight);
    }
    else
    {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

@end
