/*
 * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
 * the License. A copy of the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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 * as React from 'react';
import { Component } from 'react';
import Amplify, { I18n, ConsoleLogger as Logger, Hub } from '@aws-amplify/core';
import Auth from '@aws-amplify/auth';
import Greetings from './Greetings';
import SignIn from './SignIn';
import ConfirmSignIn from './ConfirmSignIn';
import RequireNewPassword from './RequireNewPassword';
import SignUp from './SignUp';
import Loading from './Loading';
import ConfirmSignUp from './ConfirmSignUp';
import VerifyContact from './VerifyContact';
import ForgotPassword from './ForgotPassword';
import TOTPSetup from './TOTPSetup';
import Constants from './common/constants';

import AmplifyTheme from '../Amplify-UI/Amplify-UI-Theme';
import AmplifyMessageMap from '../AmplifyMessageMap';

import { Container, Toast } from '../Amplify-UI/Amplify-UI-Components-React';

const logger = new Logger('Authenticator');
const AUTHENTICATOR_AUTHSTATE = 'amplify-authenticator-authState';

export default class Authenticator extends Component {
    constructor(props) {
        super(props);

        this.handleStateChange = this.handleStateChange.bind(this);
        this.handleAuthEvent = this.handleAuthEvent.bind(this);
        this.onHubCapsule = this.onHubCapsule.bind(this);

        this._initialAuthState = this.props.authState || 'signIn';
        this.state = { authState: 'loading' };
        Hub.listen('auth', this);
    }

    componentDidMount() {
        const config = this.props.amplifyConfig;
        if (config) {
            Amplify.configure(config);
        }
        this._isMounted = true;
        // the workaround for Cognito Hosted UI
        // don't check the user immediately if redirected back from Hosted UI
        // instead waiting for the hub event sent from Auth module
        // the item in the localStorage is a mark to indicate whether
        // the app is redirected back from Hosted UI or not
        const byHostedUI = localStorage.getItem(Constants.SIGN_IN_WITH_HOSTEDUI_KEY);
        localStorage.removeItem(Constants.SIGN_IN_WITH_HOSTEDUI_KEY);
        if (!byHostedUI) this.checkUser();
    }

    componentWillUnmount() {
        this._isMounted = false;
    }
    checkUser() {
        if (!Auth || typeof Auth.currentAuthenticatedUser !== 'function') {
            throw new Error('No Auth module found, please ensure @aws-amplify/auth is imported');
        }
        return Auth.currentAuthenticatedUser()
            .then(user => {
                if (!this._isMounted) { return; }
                this.handleStateChange('signedIn', user);
            })
            .catch(err => {
                if (!this._isMounted) { return; }
                let cachedAuthState = null;
                try {
                    cachedAuthState = localStorage.getItem(AUTHENTICATOR_AUTHSTATE);
                } catch (e) {
                    logger.debug('Failed to get the auth state from local storage', e);
                }
                const promise = cachedAuthState === 'signedIn'? Auth.signOut() : Promise.resolve();
                promise.then(() => this.handleStateChange(this._initialAuthState))
                    .catch((e) => {
                        logger.debug('Failed to sign out', e);
                    });
            });
    }

    onHubCapsule(capsule) {
        const { channel, payload, source } = capsule;
        if (channel === 'auth') {
            switch (payload.event) {
                case 'cognitoHostedUI':
                    this.handleStateChange('signedIn', payload.data);
                    break;
                case 'cognitoHostedUI_failure':
                    this.handleStateChange('signIn', null);
                    break;
                case 'parsingUrl_failure':
                    this.handleStateChange('signIn', null);
                    break;
                case 'signOut':
                    this.handleStateChange('signIn', null);
                    break;
                case 'customGreetingSignOut':
                    this.handleStateChange('signIn', null);
                    break;
                default:
                    break;
            }
        }
    }

    handleStateChange(state, data) {
        logger.debug('authenticator state change ' + state, data);
        if (state === this.state.authState) { return; }

        if (state === 'signedOut') { state = 'signIn'; }
        try {
            localStorage.setItem(AUTHENTICATOR_AUTHSTATE, state);
        } catch (e) {
            logger.debug('Failed to set the auth state into local storage', e);
        }

        if (this._isMounted) {
            this.setState({ authState: state, authData: data, error: null, showToast: false });            
        }
        if (this.props.onStateChange) { this.props.onStateChange(state, data); }
    }

    handleAuthEvent(state, event, showToast = true) {
        if (event.type === 'error') {
            const map = this.props.errorMessage || AmplifyMessageMap;
            const message = (typeof map === 'string')? map : map(event.data);
            this.setState({ error: message, showToast });
            
        }
    }

    render() {
        const { authState, authData } = this.state;
        const theme = this.props.theme || AmplifyTheme;
        const messageMap = this.props.errorMessage || AmplifyMessageMap;

        let { hideDefault, hide = [], federated, signUpConfig } = this.props;
        if (hideDefault) {
            hide = hide.concat([
                Greetings,
                SignIn,
                ConfirmSignIn,
                RequireNewPassword,
                SignUp,
                ConfirmSignUp,
                VerifyContact,
                ForgotPassword,
                TOTPSetup,
                Loading
            ]);
        }

        let props_children = [];
        if (typeof this.props.children === 'object') {
            if (Array.isArray(this.props.children)){
                props_children = this.props.children;
            } else {
                props_children.push(this.props.children);
            }
        } 

        const default_children = [
            <Greetings federated={federated}/>,
            <SignIn federated={federated}/>,
            <ConfirmSignIn/>,
            <RequireNewPassword/>,
            <SignUp signUpConfig={signUpConfig}/>,
            <ConfirmSignUp/>,
            <VerifyContact/>,
            <ForgotPassword/>,
            <TOTPSetup/>,
            <Loading/>
        ];

        const props_children_override =  React.Children.map(props_children, child => child.props.override);
        hide = hide.filter((component) => !props_children.find(child => child.type === component));
        
        const render_props_children = React.Children.map(props_children, (child, index) => {
            return React.cloneElement(child, {
                    key: 'aws-amplify-authenticator-props-children-' + index,
                    theme,
                    messageMap,
                    authState,
                    authData,
                    onStateChange: this.handleStateChange,
                    onAuthEvent: this.handleAuthEvent,
                    hide,
                    override: props_children_override
                });
        });
       
        const render_default_children = hideDefault ? [] : React.Children.map(default_children, (child, index) => {
                return React.cloneElement(child, {
                    key: 'aws-amplify-authenticator-default-children-' + index,
                    theme,
                    messageMap,
                    authState,
                    authData,
                    onStateChange: this.handleStateChange,
                    onAuthEvent: this.handleAuthEvent,
                    hide,
                    override: props_children_override
                });
            });

        const render_children = render_default_children.concat(render_props_children);
        const error = this.state.error;        

        return (
            <Container theme={theme}>
                {this.state.showToast && 
                    <Toast theme={theme} onClose={() => this.setState({showToast: false})}>
                        { I18n.get(error) }
                    </Toast>
                }
                {render_children}
            </Container>
        );
    }
}
