// @flow

import React from 'react';
import { TouchableOpacity, View } from 'react-native';

import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { connect } from '../../../base/redux';
import {
    isNarrowAspectRatio,
    makeAspectRatioAware
} from '../../../base/responsive-ui';
import {
    RecordingExpandedLabel
} from '../../../recording';
import { isToolboxVisible } from '../../../toolbox';
import { TranscribingExpandedLabel } from '../../../transcribing';
import { shouldDisplayTileView } from '../../../video-layout';
import { VideoQualityExpandedLabel } from '../../../video-quality';

import AbstractLabels, {
    _abstractMapStateToProps,
    type Props as AbstractLabelsProps
} from '../AbstractLabels';
import { shouldDisplayNotifications } from '../../functions';
import styles from './styles';

/**
 * The type of the React {@code Component} props of {@link Labels}.
 */
type Props = AbstractLabelsProps & {

    /**
     * Function to translate i18n labels.
     */
    t: Function,

    /**
     * The indicator which determines whether the UI is reduced (to accommodate
     * smaller display areas).
     *
     * @private
     */
    _reducedUI: boolean,

    /**
     * True if the labels should be visible, false otherwise.
     */
    _visible: boolean
};

type State = {

    /**
     * Layout object of the outermost container. For stucture please see:
     * https://facebook.github.io/react-native/docs/view#onlayout
     */
    containerLayout: ?Object,

    /**
     * Layout objects of the individual labels. This data type contains the same
     * structure as the layout is defined here:
     * https://facebook.github.io/react-native/docs/view#onlayout
     * but keyed with the ID of the label its layout it contains. E.g.
     *
     * {
     *   transcribing: {
     *     { layout: { x, y, width, height } }
     *   },
     *   ...
     * }
     */
    labelLayouts: Object,

    /**
     * Position of the label to render the {@code ExpandedLabel} to.
     */
    parentPosition: ?number,

    /**
     * String to show which {@code ExpandedLabel} to be shown. (Equals to the
     * label IDs below.)
     */
    visibleExpandedLabel: ?string
}

const LABEL_ID_QUALITY = 'quality';
const LABEL_ID_RECORDING = 'recording';
const LABEL_ID_STREAMING = 'streaming';
const LABEL_ID_TRANSCRIBING = 'transcribing';

/**
 * The {@code ExpandedLabel} components to be rendered for the individual
 * {@code Label}s.
 */
const EXPANDED_LABELS = {
    quality: VideoQualityExpandedLabel,
    recording: {
        component: RecordingExpandedLabel,
        props: {
            mode: JitsiRecordingConstants.mode.FILE
        }
    },
    streaming: {
        component: RecordingExpandedLabel,
        props: {
            mode: JitsiRecordingConstants.mode.STREAM
        }
    },
    transcribing: TranscribingExpandedLabel
};

/**
 * Timeout to hide the {@ExpandedLabel}.
 */
const EXPANDED_LABEL_TIMEOUT = 5000;

/**
 * A container that renders the conference indicators, if any.
 */
class Labels extends AbstractLabels<Props, State> {
    /**
     * Timeout for the expanded labels to disappear.
     */
    expandedLabelTimeout: TimeoutID;

    /**
     * Instantiates a new instance of {@code Labels}.
     *
     * @inheritdoc
     */
    constructor(props: Props) {
        super(props);

        this.state = {
            containerLayout: undefined,
            labelLayouts: {},
            parentPosition: undefined,
            visibleExpandedLabel: undefined
        };

        this._onTopViewLayout = this._onTopViewLayout.bind(this);
    }

    /**
     * Implements React {@code Component}'s componentWillUnmount.
     *
     * @inheritdoc
     */
    componentWillUnmount() {
        clearTimeout(this.expandedLabelTimeout);
    }

    /**
     * Implements React {@code Component}'s render.
     *
     * @inheritdoc
     */
    render() {
        if (!this.props._visible) {
            return null;
        }

        const wide = !isNarrowAspectRatio(this);
        const { _filmstripVisible, _reducedUI } = this.props;

        return (
            <View
                pointerEvents = 'box-none'
                style = { styles.labelWrapper }>
                <View
                    onLayout = { this._onTopViewLayout }
                    pointerEvents = 'box-none'
                    style = { [
                        styles.indicatorContainer,
                        wide && _filmstripVisible
                            && styles.indicatorContainerWide
                    ] }>
                    <TouchableOpacity
                        onLayout = { this._createOnLayout(LABEL_ID_RECORDING) }
                        onPress = { this._createOnPress(LABEL_ID_RECORDING) } >
                        {
                            this._renderRecordingLabel(
                                JitsiRecordingConstants.mode.FILE)
                        }
                    </TouchableOpacity>
                    <TouchableOpacity
                        onLayout = { this._createOnLayout(LABEL_ID_STREAMING) }
                        onPress = { this._createOnPress(LABEL_ID_STREAMING) } >
                        {
                            this._renderRecordingLabel(
                                JitsiRecordingConstants.mode.STREAM)
                        }
                    </TouchableOpacity>
                    <TouchableOpacity
                        onLayout = {
                            this._createOnLayout(LABEL_ID_TRANSCRIBING)
                        }
                        onPress = {
                            this._createOnPress(LABEL_ID_TRANSCRIBING)
                        } >
                        {
                            this._renderTranscribingLabel()
                        }
                    </TouchableOpacity>
                    {/*
                      * Emil, Lyubomir, Nichole, and Zoli said that the Labels
                      * should not be rendered in Picture-in-Picture. Saul
                      * argued that the recording Labels should be rendered. As
                      * a temporary compromise, don't render the
                      * VideoQualityLabel at least because it's not that
                      * important.
                      */
                        _reducedUI || (
                            <TouchableOpacity
                                onLayout = {
                                    this._createOnLayout(LABEL_ID_QUALITY) }
                                onPress = {
                                    this._createOnPress(LABEL_ID_QUALITY) } >
                                { this._renderVideoQualityLabel() }
                            </TouchableOpacity>
                        )
                    }
                </View>
                <View
                    style = { [
                        styles.indicatorContainer,
                        wide && _filmstripVisible
                            && styles.indicatorContainerWide
                    ] }>
                    {
                        this._renderExpandedLabel()
                    }
                </View>
            </View>
        );
    }

    /**
     * Creates a function to be invoked when the onLayout of the touchables are
     * triggered.
     *
     * @param {string} label - The identifier of the label that's onLayout is
     * triggered.
     * @returns {Function}
     */
    _createOnLayout(label) {
        return ({ nativeEvent: { layout } }) => {
            const { labelLayouts } = this.state;
            const updatedLayout = {};

            updatedLayout[label] = layout;

            this.setState({
                labelLayouts: {
                    ...labelLayouts,
                    ...updatedLayout
                }
            });
        };
    }

    /**
     * Creates a function to be invoked when the onPress of the touchables are
     * triggered.
     *
     * @param {string} label - The identifier of the label that's onLayout is
     * triggered.
     * @returns {Function}
     */
    _createOnPress(label) {
        return () => {
            const {
                containerLayout,
                labelLayouts
            } = this.state;
            let { visibleExpandedLabel } = this.state;

            if (containerLayout) {
                const labelLayout = labelLayouts[label];

                // This calculation has to be changed if the labels are not
                // positioned right anymore.
                const right = containerLayout.width - labelLayout.x;

                visibleExpandedLabel
                    = visibleExpandedLabel === label ? undefined : label;

                clearTimeout(this.expandedLabelTimeout);
                this.setState({
                    parentPosition: right,
                    visibleExpandedLabel
                });

                if (visibleExpandedLabel) {
                    this.expandedLabelTimeout = setTimeout(() => {
                        this.setState({
                            visibleExpandedLabel: undefined
                        });
                    }, EXPANDED_LABEL_TIMEOUT);
                }
            }
        };
    }

    _onTopViewLayout: Object => void

    /**
     * Invoked when the View containing the {@code Label}s is laid out.
     *
     * @param {Object} layout - The native layout object.
     * @returns {void}
     */
    _onTopViewLayout({ nativeEvent: { layout } }) {
        this.setState({
            containerLayout: layout
        });
    }

    /**
     * Rendes the expanded (explaining) label for the label that was touched.
     *
     * @returns {React$Element}
     */
    _renderExpandedLabel() {
        const { parentPosition, visibleExpandedLabel } = this.state;

        if (visibleExpandedLabel) {
            const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel];

            if (expandedLabel) {
                const component = expandedLabel.component || expandedLabel;
                const expandedLabelProps = expandedLabel.props || {};

                return React.createElement(component, {
                    ...expandedLabelProps,
                    parentPosition
                });
            }
        }

        return null;
    }

    _renderRecordingLabel: string => React$Element<*>;

    _renderTranscribingLabel: () => React$Element<*>

    _renderVideoQualityLabel: () => React$Element<*>;
}

/**
 * Maps (parts of) the redux state to the associated
 * {@code Labels}'s props.
 *
 * @param {Object} state - The redux state.
 * @private
 * @returns {{
 *     _filmstripVisible: boolean,
 *     _reducedUI: boolean,
 *     _visible: boolean
 * }}
 */
function _mapStateToProps(state) {
    return {
        ..._abstractMapStateToProps(state),
        _reducedUI: state['features/base/responsive-ui'].reducedUI,
        _visible: !isToolboxVisible(state)
            && !shouldDisplayTileView(state)
            && !shouldDisplayNotifications(state)
    };
}

export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));
