import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import clsx from 'clsx';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';

import { isMobileBrowser } from '../../base/environment/utils';
import { translate } from '../../base/i18n/functions';
import ContextMenu from '../../base/ui/components/web/ContextMenu';

type DownloadUpload = {
    download: number;
    upload: number;
};

/**
 * The type of the React {@code Component} props of
 * {@link ConnectionStatsTable}.
 */
interface IProps extends WithTranslation {

    /**
     * The audio SSRC of this client.
     */
    audioSsrc: number;

    /**
     * Statistics related to bandwidth.
     * {{
     *     download: Number,
     *     upload: Number
     * }}.
     */
    bandwidth: DownloadUpload;

    /**
     * Statistics related to bitrate.
     * {{
     *     download: Number,
     *     upload: Number
     * }}.
     */
    bitrate: DownloadUpload;

    /**
     * The number of bridges (aka media servers) currently used in the
     * conference.
     */
    bridgeCount: number;

    /**
     * An object containing the CSS classes.
     */
    classes: any;

    /**
     * Audio/video codecs in use for the connection.
     */
    codec: {
        [key: string]: {
            audio: string | undefined;
            video: string | undefined;
        };
    };

    /**
     * A message describing the connection quality.
     */
    connectionSummary: string;

    /**
     * Whether or not should display the "Show More" link.
     */
    disableShowMoreStats: boolean;

    /**
     * Whether or not the participant was verified.
     */
    e2eeVerified: boolean;

    /**
     * Whether or not should display the "Save Logs" link.
     */
    enableSaveLogs: boolean;

    /**
     * Statistics related to frame rates for each ssrc.
     * {{
     *     [ ssrc ]: Number
     * }}.
     */
    framerate: {
        [ssrc: string]: number;
    };

    /**
     * Whether or not the statistics are for local video.
     */
    isLocalVideo: boolean;

    /**
     * Whether or not the statistics are for screen share.
     */
    isVirtualScreenshareParticipant: boolean;

    /**
     * The send-side max enabled resolution (aka the highest layer that is not
     * suspended on the send-side).
     */
    maxEnabledResolution: number;

    /**
     * Callback to invoke when the user clicks on the download logs link.
     */
    onSaveLogs: () => void;

    /**
     * Callback to invoke when the show additional stats link is clicked.
     */
    onShowMore: (e?: React.MouseEvent) => void;

    /**
     * Statistics related to packet loss.
     * {{
     *     download: Number,
     *     upload: Number
     * }}.
     */
    packetLoss: DownloadUpload;

    /**
     * The endpoint id of this client.
     */
    participantId: string;

    /**
     * The region that we think the client is in.
     */
    region: string;

    /**
     * Statistics related to display resolutions for each ssrc.
     * {{
     *     [ ssrc ]: {
     *         height: Number,
     *         width: Number
     *     }
     * }}.
     */
    resolution: {
        [ssrc: string]: {
            height: number;
            width: number;
        };
    };

    /**
     * The region of the media server that we are connected to.
     */
    serverRegion: string;

    /**
     * Whether or not additional stats about bandwidth and transport should be
     * displayed. Will not display even if true for remote participants.
     */
    shouldShowMore: boolean;

    /**
     * Statistics related to transports.
     */
    transport: Array<{
        ip: string;
        localCandidateType: string;
        localip: string;
        p2p: boolean;
        remoteCandidateType: string;
        transportType: string;
        type: string;
    }>;

    /**
     * The video SSRC of this client.
     */
    videoSsrc: number;
}

/**
 * Click handler.
 *
 * @param {SyntheticEvent} event - The click event.
 * @returns {void}
 */
function onClick(event: React.MouseEvent) {
    // If the event is propagated to the thumbnail container the participant will be pinned. That's why the propagation
    // needs to be stopped.
    event.stopPropagation();
}

const styles = (theme: Theme) => {
    return {
        actions: {
            margin: '10px auto',
            textAlign: 'center' as const
        },
        connectionStatsTable: {
            '&, & > table': {
                fontSize: '12px',
                fontWeight: '400',

                '& td': {
                    padding: '2px 0'
                }
            },
            '& > table': {
                whiteSpace: 'nowrap'
            },

            '& td:nth-child(n-1)': {
                paddingLeft: '5px'
            },

            '& $upload, & $download': {
                marginRight: '2px'
            }
        },
        contextMenu: {
            position: 'relative' as const,
            marginTop: 0,
            right: 'auto',
            padding: `${theme.spacing(2)} ${theme.spacing(1)}`,
            marginLeft: theme.spacing(1),
            marginRight: theme.spacing(1),
            marginBottom: theme.spacing(1)
        },
        download: {},
        mobile: {
            margin: theme.spacing(3)
        },
        status: {
            fontWeight: 'bold'
        },
        upload: {}
    };
};

/**
 * React {@code Component} for displaying connection statistics.
 *
 * @augments Component
 */
class ConnectionStatsTable extends Component<IProps> {
    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render() {
        const {
            classes,
            disableShowMoreStats,
            enableSaveLogs,
            isVirtualScreenshareParticipant,
            isLocalVideo
        } = this.props;
        const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });

        if (isVirtualScreenshareParticipant) {
            return this._renderScreenShareStatus();
        }

        return (
            <ContextMenu
                className = { classes.contextMenu }
                hidden = { false }
                inDrawer = { true }>
                <div
                    className = { className }
                    onClick = { onClick }>
                    { this._renderStatistics() }
                    <div className = { classes.actions }>
                        { isLocalVideo && enableSaveLogs ? this._renderSaveLogs() : null}
                        { !disableShowMoreStats && this._renderShowMoreLink() }
                    </div>
                    { this.props.shouldShowMore ? this._renderAdditionalStats() : null }
                </div>
            </ContextMenu>
        );
    }

    /**
     * Creates a ReactElement that will display connection statistics for a screen share thumbnail.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderScreenShareStatus() {
        const { classes } = this.props;
        const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });

        return (<ContextMenu
            className = { classes.contextMenu }
            hidden = { false }
            inDrawer = { true }>
            <div
                className = { className }
                onClick = { onClick }>
                <tbody>
                    { this._renderResolution() }
                    { this._renderFrameRate() }
                </tbody>
            </div>
        </ContextMenu>);
    }

    /**
     * Creates a table as ReactElement that will display additional statistics
     * related to bandwidth and transport for the local user.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderAdditionalStats() {
        const { isLocalVideo } = this.props;

        return (
            <table>
                <tbody>
                    { isLocalVideo ? this._renderBandwidth() : null }
                    { isLocalVideo ? this._renderTransport() : null }
                    { this._renderRegion() }
                    { isLocalVideo ? this._renderBridgeCount() : null }
                    { this._renderAudioSsrc() }
                    { this._renderVideoSsrc() }
                    { this._renderParticipantId() }
                </tbody>
            </table>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying bandwidth related
     * statistics.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderBandwidth() {
        const { classes } = this.props;
        const { download, upload } = this.props.bandwidth || {};

        return (
            <tr>
                <td>
                    { this.props.t('connectionindicator.bandwidth') }
                </td>
                <td>
                    <span className = { classes.download }>
                        &darr;
                    </span>
                    { download ? `${download} Kbps` : 'N/A' }
                    <span className = { classes.upload }>
                        &uarr;
                    </span>
                    { upload ? `${upload} Kbps` : 'N/A' }
                </td>
            </tr>
        );
    }

    /**
     * Creates a a table row as a ReactElement for displaying bitrate related
     * statistics.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderBitrate() {
        const { classes } = this.props;
        const { download, upload } = this.props.bitrate || {};

        return (
            <tr>
                <td>
                    <span>
                        { this.props.t('connectionindicator.bitrate') }
                    </span>
                </td>
                <td>
                    <span className = { classes.download }>
                        &darr;
                    </span>
                    { download ? `${download} Kbps` : 'N/A' }
                    <span className = { classes.upload }>
                        &uarr;
                    </span>
                    { upload ? `${upload} Kbps` : 'N/A' }
                </td>
            </tr>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying the audio ssrc.
     * This will typically be something like "Audio SSRC: 12345".
     *
     * @returns {JSX.Element}
     * @private
     */
    _renderAudioSsrc() {
        const { audioSsrc, t } = this.props;

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.audio_ssrc') }</span>
                </td>
                <td>{ audioSsrc || 'N/A' }</td>
            </tr>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying the video ssrc.
     * This will typically be something like "Video SSRC: 12345".
     *
     * @returns {JSX.Element}
     * @private
     */
    _renderVideoSsrc() {
        const { videoSsrc, t } = this.props;

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.video_ssrc') }</span>
                </td>
                <td>{ videoSsrc || 'N/A' }</td>
            </tr>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying the endpoint id.
     * This will typically be something like "Endpoint id: 1e8fbg".
     *
     * @returns {JSX.Element}
     * @private
     */
    _renderParticipantId() {
        const { participantId, t } = this.props;

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.participant_id') }</span>
                </td>
                <td>{ participantId || 'N/A' }</td>
            </tr>
        );
    }

    /**
     * Creates a a table row as a ReactElement for displaying codec, if present.
     * This will typically be something like "Codecs (A/V): Opus, vp8".
     *
     * @private
     * @returns {ReactElement}
     */
    _renderCodecs() {
        const { audioSsrc, codec, t, videoSsrc } = this.props;

        let codecString = 'N/A';

        if (codec) {
            const audioCodec = codec[audioSsrc]?.audio;
            const videoCodec = codec[videoSsrc]?.video;

            if (audioCodec || videoCodec) {
                codecString = [ audioCodec, videoCodec ].filter(Boolean).join(', ');
            }
        }

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.codecs') }</span>
                </td>
                <td>{ codecString }</td>
            </tr>
        );
    }

    /**
     * Creates a a table row as a ReactElement for displaying e2ee verication status, if present.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderE2EEVerified() {
        const { e2eeVerified, t } = this.props;

        if (e2eeVerified === undefined) {
            return;
        }

        const status = e2eeVerified ? '\u{2705}' : '\u{274C}';

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.e2eeVerified') }</span>
                </td>
                <td>{ status }</td>
            </tr>
        );
    }


    /**
     * Creates a table row as a ReactElement for displaying a summary message
     * about the current connection status.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderConnectionSummary() {
        const { classes } = this.props;

        return (
            <tr className = { classes.status }>
                <td>
                    <span>{ this.props.t('connectionindicator.status') }</span>
                </td>
                <td>{ this.props.connectionSummary }</td>
            </tr>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying the "connected to"
     * information.
     *
     * @returns {ReactElement}
     * @private
     */
    _renderRegion() {
        const { region, serverRegion, t } = this.props;
        let str = serverRegion;

        if (!serverRegion) {
            return;
        }


        if (region && serverRegion && region !== serverRegion) {
            str += ` from ${region}`;
        }

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.connectedTo') }</span>
                </td>
                <td>{ str }</td>
            </tr>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying the "bridge count"
     * information.
     *
     * @returns {*}
     * @private
     */
    _renderBridgeCount() {
        const { bridgeCount, t } = this.props;

        // 0 is valid, but undefined/null/NaN aren't.
        if (!bridgeCount && bridgeCount !== 0) {
            return;
        }

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.bridgeCount') }</span>
                </td>
                <td>{ bridgeCount }</td>
            </tr>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying frame rate related
     * statistics.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderFrameRate() {
        const { framerate, t, videoSsrc } = this.props;
        let frameRateString = 'N/A';

        if (framerate) {
            frameRateString = String(framerate[videoSsrc] ?? 'N/A');
        }

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.framerate') }</span>
                </td>
                <td>{ frameRateString }</td>
            </tr>
        );
    }

    /**
     * Creates a tables row as a ReactElement for displaying packet loss related
     * statistics.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderPacketLoss() {
        const { classes, packetLoss, t } = this.props;
        let packetLossTableData;

        if (packetLoss) {
            const { download, upload } = packetLoss;

            packetLossTableData = (
                <td>
                    <span className = { classes.download }>
                        &darr;
                    </span>
                    { download === null ? 'N/A' : `${download}%` }
                    <span className = { classes.upload }>
                        &uarr;
                    </span>
                    { upload === null ? 'N/A' : `${upload}%` }
                </td>
            );
        } else {
            packetLossTableData = <td>N/A</td>;
        }

        return (
            <tr>
                <td>
                    <span>
                        { t('connectionindicator.packetloss') }
                    </span>
                </td>
                { packetLossTableData }
            </tr>
        );
    }

    /**
     * Creates a table row as a ReactElement for displaying resolution related
     * statistics.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderResolution() {
        const { isVirtualScreenshareParticipant, maxEnabledResolution, resolution, t, videoSsrc } = this.props;
        let resolutionString = 'N/A';

        if (resolution && videoSsrc) {
            const { width, height } = resolution[videoSsrc] ?? { };

            if (width && height) {
                resolutionString = `${width}x${height}`;

                if (maxEnabledResolution && maxEnabledResolution < 720 && !isVirtualScreenshareParticipant) {
                    const maxEnabledResolutionTitle = t('connectionindicator.maxEnabledResolution');

                    resolutionString += ` (${maxEnabledResolutionTitle} ${maxEnabledResolution}p)`;
                }
            }
        }

        return (
            <tr>
                <td>
                    <span>{ t('connectionindicator.resolution') }</span>
                </td>
                <td>{ resolutionString }</td>
            </tr>
        );
    }

    /**
     * Creates a ReactElement for display a link to save the logs.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderSaveLogs() {
        return (
            <span>
                <a
                    className = 'savelogs link'
                    onClick = { this.props.onSaveLogs }
                    role = 'button'
                    tabIndex = { 0 }>
                    { this.props.t('connectionindicator.savelogs') }
                </a>
                <span> | </span>
            </span>
        );
    }


    /**
     * Creates a ReactElement for display a link to toggle showing additional
     * statistics.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderShowMoreLink() {
        const translationKey
            = this.props.shouldShowMore
                ? 'connectionindicator.less'
                : 'connectionindicator.more';

        return (
            <a
                className = 'showmore link'
                onClick = { this.props.onShowMore }
                role = 'button'
                tabIndex = { 0 }>
                { this.props.t(translationKey) }
            </a>
        );
    }

    /**
     * Creates a table as a ReactElement for displaying connection statistics.
     *
     * @private
     * @returns {ReactElement}
     */
    _renderStatistics() {
        return (
            <table>
                <tbody>
                    { this._renderConnectionSummary() }
                    { this._renderBitrate() }
                    { this._renderPacketLoss() }
                    { this._renderResolution() }
                    { this._renderFrameRate() }
                    { this._renderCodecs() }
                    { this._renderE2EEVerified() }
                </tbody>
            </table>
        );
    }

    /**
     * Creates table rows as ReactElements for displaying transport related
     * statistics.
     *
     * @private
     * @returns {ReactElement[]}
     */
    _renderTransport() {
        const { t, transport } = this.props;

        if (!transport || transport.length === 0) {
            const NA = (
                <tr key = 'address'>
                    <td>
                        <span>{ t('connectionindicator.address') }</span>
                    </td>
                    <td>
                        N/A
                    </td>
                </tr>
            );

            return [ NA ];
        }

        const data: {
            localIP: string[];
            localPort: string[];
            remoteIP: string[];
            remotePort: string[];
            transportType: string[];
        } = {
            localIP: [],
            localPort: [],
            remoteIP: [],
            remotePort: [],
            transportType: []
        };

        for (let i = 0; i < transport.length; i++) {
            const ip = getIP(transport[i].ip);
            const localIP = getIP(transport[i].localip);
            const localPort = getPort(transport[i].localip);
            const port = getPort(transport[i].ip);

            if (!data.remoteIP.includes(ip)) {
                data.remoteIP.push(ip);
            }

            if (!data.localIP.includes(localIP)) {
                data.localIP.push(localIP);
            }

            if (!data.localPort.includes(localPort)) {
                data.localPort.push(localPort);
            }

            if (!data.remotePort.includes(port)) {
                data.remotePort.push(port);
            }

            if (!data.transportType.includes(transport[i].type)) {
                data.transportType.push(transport[i].type);
            }
        }

        // All of the transports should be either P2P or JVB
        let isP2P = false, isTURN = false;

        if (transport.length) {
            isP2P = transport[0].p2p;
            isTURN = transport[0].localCandidateType === 'relay'
                || transport[0].remoteCandidateType === 'relay';
        }

        const additionalData = [];

        if (isP2P) {
            additionalData.push(
                <span> (p2p)</span>);
        }
        if (isTURN) {
            additionalData.push(<span> (turn)</span>);
        }

        // First show remote statistics, then local, and then transport type.
        const tableRowConfigurations = [
            {
                additionalData,
                data: data.remoteIP,
                key: 'remoteaddress',
                label: t('connectionindicator.remoteaddress',
                    { count: data.remoteIP.length })
            },
            {
                data: data.remotePort,
                key: 'remoteport',
                label: t('connectionindicator.remoteport',
                        { count: transport.length })
            },
            {
                data: data.localIP,
                key: 'localaddress',
                label: t('connectionindicator.localaddress',
                    { count: data.localIP.length })
            },
            {
                data: data.localPort,
                key: 'localport',
                label: t('connectionindicator.localport',
                    { count: transport.length })
            },
            {
                data: data.transportType,
                key: 'transport',
                label: t('connectionindicator.transport',
                    { count: data.transportType.length })
            }
        ];

        return tableRowConfigurations.map(this._renderTransportTableRow);
    }

    /**
     * Creates a table row as a ReactElement for displaying a transport related
     * statistic.
     *
     * @param {Object} config - Describes the contents of the row.
     * @param {ReactElement} config.additionalData - Extra data to display next
     * to the passed in config.data.
     * @param {Array} config.data - The transport statistics to display.
     * @param {string} config.key - The ReactElement's key. Must be unique for
     * iterating over multiple child rows.
     * @param {string} config.label - The text to display describing the data.
     * @private
     * @returns {ReactElement}
     */
    _renderTransportTableRow(config: any) {
        const { additionalData, data, key, label } = config;

        return (
            <tr key = { key }>
                <td>
                    <span>
                        { label }
                    </span>
                </td>
                <td>
                    { getStringFromArray(data) }
                    { additionalData || null }
                </td>
            </tr>
        );
    }
}

/**
 * Utility for getting the IP from a transport statistics object's
 * representation of an IP.
 *
 * @param {string} value - The transport's IP to parse.
 * @private
 * @returns {string}
 */
function getIP(value: string) {
    if (!value) {
        return '';
    }

    return value.substring(0, value.lastIndexOf(':'));
}

/**
 * Utility for getting the port from a transport statistics object's
 * representation of an IP.
 *
 * @param {string} value - The transport's IP to parse.
 * @private
 * @returns {string}
 */
function getPort(value: string) {
    if (!value) {
        return '';
    }

    return value.substring(value.lastIndexOf(':') + 1, value.length);
}

/**
 * Utility for concatenating values in an array into a comma separated string.
 *
 * @param {Array} array - Transport statistics to concatenate.
 * @private
 * @returns {string}
 */
function getStringFromArray(array: string[]) {
    let res = '';

    for (let i = 0; i < array.length; i++) {
        res += (i === 0 ? '' : ', ') + array[i];
    }

    return res;
}

export default translate(withStyles(styles)(ConnectionStatsTable));
