import PropTypes from 'prop-types';
import { Component } from 'react';

import { appNavigate } from '../../app';
import { isRoomValid } from '../../base/conference';

import { generateRoomWithoutSeparator } from '../functions';

/**
 * Base (abstract) class for container component rendering the welcome page.
 *
 * @abstract
 */
export class AbstractWelcomePage extends Component {
    /**
     * {@code AbstractWelcomePage}'s React {@code Component} prop types.
     *
     * @static
     */
    static propTypes = {
        _room: PropTypes.string,
        dispatch: PropTypes.func
    };

    /**
     * Initializes a new {@code AbstractWelcomePage} instance.
     *
     * @param {Object} props - The React {@code Component} props to initialize
     * the new {@code AbstractWelcomePage} instance with.
     */
    constructor(props) {
        super(props);

        /**
         * Save room name into component's local state.
         *
         * @type {Object}
         * @property {number|null} animateTimeoutId - Identifier of the letter
         * animation timeout.
         * @property {string} generatedRoomname - Automatically generated
         * room name.
         * @property {string} room - Room name.
         * @property {string} roomPlaceholder - Room placeholder
         * that's used as a placeholder for input.
         * @property {nubmer|null} updateTimeoutId - Identifier of the timeout
         * updating the generated room name.
         */
        this.state = {
            animateTimeoutId: null,
            generatedRoomname: '',
            room: '',
            roomPlaceholder: '',
            updateTimeoutId: null
        };

        // Bind event handlers so they are only bound once per instance.
        this._animateRoomnameChanging
            = this._animateRoomnameChanging.bind(this);
        this._onJoin = this._onJoin.bind(this);
        this._onRoomChange = this._onRoomChange.bind(this);
        this._updateRoomname = this._updateRoomname.bind(this);
    }

    /**
     * This method is executed when component receives new properties.
     *
     * @inheritdoc
     * @param {Object} nextProps - New props component will receive.
     */
    componentWillReceiveProps(nextProps) {
        this.setState({ room: nextProps._room });
    }

    /**
     * This method is executed when method will be unmounted from DOM.
     *
     * @inheritdoc
     */
    componentWillUnmount() {
        this._clearTimeouts();
    }

    /**
     * Animates the changing of the room name.
     *
     * @param {string} word - The part of room name that should be added to
     * placeholder.
     * @private
     * @returns {void}
     */
    _animateRoomnameChanging(word) {
        let animateTimeoutId = null;
        const roomPlaceholder = this.state.roomPlaceholder + word.substr(0, 1);

        if (word.length > 1) {
            animateTimeoutId
                = setTimeout(
                    () => {
                        this._animateRoomnameChanging(
                            word.substring(1, word.length));
                    },
                    70);
        }

        this.setState({
            animateTimeoutId,
            roomPlaceholder
        });
    }

    /**
     * Method that clears timeouts for animations and updates of room name.
     *
     * @private
     * @returns {void}
     */
    _clearTimeouts() {
        clearTimeout(this.state.animateTimeoutId);
        clearTimeout(this.state.updateTimeoutId);
    }

    /**
     * Determines whether the 'Join' button is (to be) disabled i.e. there's no
     * valid room name typed into the respective text input field.
     *
     * @protected
     * @returns {boolean} If the 'Join' button is (to be) disabled, true;
     * otherwise, false.
     */
    _isJoinDisabled() {
        return !isRoomValid(this.state.room);
    }

    /**
     * Handles joining. Either by clicking on 'Join' button
     * or by pressing 'Enter' in room name input field.
     *
     * @protected
     * @returns {void}
     */
    _onJoin() {
        const room = this.state.room || this.state.generatedRoomname;

        room && this.props.dispatch(appNavigate(room));
    }

    /**
     * Handles 'change' event for the room name text input field.
     *
     * @param {string} value - The text typed into the respective text input
     * field.
     * @protected
     * @returns {void}
     */
    _onRoomChange(value) {
        this.setState({ room: value });
    }

    /**
     * Triggers the generation of a new room name and initiates an animation of
     * its changing.
     *
     * @protected
     * @returns {void}
     */
    _updateRoomname() {
        const generatedRoomname = generateRoomWithoutSeparator();
        const roomPlaceholder = '';
        const updateTimeoutId = setTimeout(this._updateRoomname, 10000);

        this._clearTimeouts();
        this.setState(
            {
                generatedRoomname,
                roomPlaceholder,
                updateTimeoutId
            },
            () => this._animateRoomnameChanging(generatedRoomname));
    }
}

/**
 * Maps (parts of) the redux state to the React {@code Component} props of
 * {@code AbstractWelcomePage}.
 *
 * @param {Object} state - The redux state.
 * @protected
 * @returns {{
 *     _room: string
 * }}
 */
export function _mapStateToProps(state) {
    return {
        _room: state['features/base/conference'].room
    };
}
