'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import _ from 'lodash';
import {Icon} from '../Icon';
import {showAlert} from '../utils';
import EventBus from '../../utils/EventBus';

import {formatSeconds, getProcessedPercentage, KEY_CODE} from './utils';
import {FocusHelper} from '../../utils/FocusHelper';
import {VolumeControl} from './VolumeControl';
import {Button} from '../Button';
import lang from '../../../lang/common/index-en_US';

const MODES = {
    FULL: 0,
    INVISIBLE: 1
};
const PLAYING_EVENT = 'AUDIO_PLAYING';

const PROGRESS_BAR = 'audio-progress-bar';
const PROGRESS_INDICATOR = 'audio-progress-current-pos';

const STEP = 1; //1 second

export class AudioPlayer extends React.Component<any, any> {
    static MODES = MODES;
    static propTypes = {
        id: PropTypes.string,
        mode: PropTypes.number,
        audioUrl: PropTypes.string,
        autoplay: PropTypes.bool,
        canPause: PropTypes.bool,
        onPlay: PropTypes.func,
        onRecord: PropTypes.func,
        downloadUrl: PropTypes.string,
        loadingError: PropTypes.string,
        onLoadError: PropTypes.func,
        pauseLabel: PropTypes.string,
        playLabel: PropTypes.string,
        timeSlider: PropTypes.string,
        recordLabel: PropTypes.string,
        downloadLabel: PropTypes.string,
        autoFocus: PropTypes.bool,
        onBlur: PropTypes.func
    };

    static defaultProps = {
        audioUrl: '',
        downloadUrl: '',
        onPlay: null,
        onRecord: null,
        onLoadError: null,
        id: '',
        mode: MODES.FULL,
        autoplay: false,
        canPause: true,
        get loadingError() {
            return lang.CANNOT_OPEN_AUDIO;
        },
        get playLabel() {
            return lang.PLAY;
        },
        get pauseLabel() {
            return lang.PAUSE;
        },
        get timeSlider() {
            return lang.TIMELINE_SLIDER;
        },
        get recordLabel() {
            return lang.RECORD;
        },
        get downloadLabel() {
            return lang.DOWNLOAD;
        },
        autoFocus: false,
        onBlur: null
    };

    constructor(props, params) {
        super(props, params);

        this.state = {
            playing: false,
            currentTime: 0,
            progressIndicatorPos: 0,
            duration: 0,
            volume: 1,
            isMuted: false
        };

        this.focusHelper = new FocusHelper();
    }

    initialized: any;
    focusHelper: any;
    progressIndicatorWidth: any;
    player: any;
    lastFocusElement: any;
    draggingProgress: any;
    audioPlayerWrapper: any;

    componentWillMount() {
        EventBus.on(PLAYING_EVENT, this.stopPlayer);
        if (this.props.autoplay) {
            this.play();
        }
    }

    componentDidMount() {
        if (!this.initialized && !this.isInVisible()) {
            let progressBar = this.refs[PROGRESS_BAR] as any;
            let progressIndicator = this.refs[PROGRESS_INDICATOR] as any;
            this.progressIndicatorWidth = progressIndicator.offsetWidth;

            // Drag start
            progressBar.addEventListener('mousedown', this.progressDragStart);
            // Drag end
            window.addEventListener('mouseup', this.windowDragEnd);

            // Drag end
            progressIndicator.addEventListener('keydown', this.progressKeyDown);
            //Key up
            progressIndicator.addEventListener('keyup', this.progressKeyUp);
        }
        this.initialized = true;

        if (this.props.autoFocus) {
            this.lastFocusElement = document.activeElement;
            setTimeout(() => this.focusHelper.focus());
        }
    }

    componentWillReceiveProps(nextProps) {
        if (
            this.player &&
            (this.props.audioUrl !== nextProps.audioUrl || this.props.id !== nextProps.id)
        ) {
            this.player.pause();
            this.stopMonitorAudioProgress();
            this.player.src = nextProps.audioUrl.replace('.wav', '.mp3');
            this.player.volume = 1;
            this.setState({
                playing: false,
                currentTime: 0,
                progressIndicatorPos: 0,
                duration: 0,
                volume: 1,
                isMuted: false
            });

            if (nextProps.autoplay) {
                this.play(true);
            }
        }
    }

    componentWillUnmount() {
        if (this.player) {
            this.player.pause();
            this.player.onerror = null;
            this.stopMonitorAudioProgress();
            delete this.player;
        }

        if (this.initialized && !this.isInVisible()) {
            let progressIndicator = this.refs[PROGRESS_INDICATOR] as any;
            progressIndicator.removeEventListener('keydown', this.progressKeyDown);
            progressIndicator.removeEventListener('keyup', this.progressKeyUp);
            window.removeEventListener('mouseup', this.windowDragEnd);
            window.removeEventListener('mousedown', this.progressDragStart);
            window.removeEventListener('mousemove', this.progressDrag);
        }

        EventBus.off(PLAYING_EVENT, this.stopPlayer);

        if (this.lastFocusElement) {
            this.lastFocusElement.focus();
        }
    }

    stopPlayer = instance => {
        if (instance !== this && this.player) {
            //other players are playing
            this.player.pause();
            this.stopMonitorAudioProgress();
            this.setState({
                playing: false
            });
        }
    };

    progressDrag = e => {
        e.stopPropagation();
        e.preventDefault();
        this.updateProgressByMouse(e, false);
    };

    progressDragStart = e => {
        e.stopPropagation();
        e.preventDefault();
        this.draggingProgress = true;
        this.stopMonitorAudioProgress();
        window.addEventListener('mousemove', this.progressDrag);
    };

    windowDragEnd = e => {
        if (this.draggingProgress) {
            window.removeEventListener('mousemove', this.progressDrag);
            this.updateProgressByMouse(e, true);
            this.draggingProgress = false;
        }
    };

    progressKeyDown = e => {
        let isValidKey = false;

        switch (e.keyCode) {
            case KEY_CODE.RIGHT:
            case KEY_CODE.UP:
                isValidKey = true;
                this.updateProgressByKeyboard(e, false, STEP);
                break;
            case KEY_CODE.LEFT:
            case KEY_CODE.DOWN:
                isValidKey = true;
                this.updateProgressByKeyboard(e, false, -STEP);
                break;
            case KEY_CODE.PAGEUP:
                isValidKey = true;
                this.updateProgressByKeyboard(e, false, STEP * 10);
                break;
            case KEY_CODE.PAGEDOWN:
                isValidKey = true;
                this.updateProgressByKeyboard(e, false, -STEP * 10);
                break;
            case KEY_CODE.END:
                isValidKey = true;
                this.updateProgressByKeyboard(e, false, this.state.duration || 60);
                break;
            case KEY_CODE.HOME:
                isValidKey = true;
                this.updateProgressByKeyboard(e, false, -this.state.duration || -60);
                break;
        }

        if (isValidKey) {
            e.stopPropagation();
            e.preventDefault();
        }
    };

    progressKeyUp = e => {
        let isValidKey = _.find(KEY_CODE, function(value) {
            return value === e.keyCode;
        });

        if (isValidKey) {
            e.stopPropagation();
            e.preventDefault();
            this.updateProgressByKeyboard(e, true, 0);
        }
    };

    getPlayButton() {
        if (this.isInVisible()) {
            return null;
        }
        let {playing} = this.state;

        return (
            <Button
                className="rc-audio-icon-wrapper"
                testAutomationId={`audioPlay${this.props.id}`}
                ref={this.focusHelper.setEl}
                onClick={() => {
                    this.play();
                }}
                aria-pressed={playing}
                aria-label={playing ? this.props.pauseLabel : this.props.playLabel}>
                <Icon name={playing ? 'pause' : 'play'} />
            </Button>
        );
    }

    getProgressBar() {
        if (!this.isFullMode()) {
            return null;
        }
        let {progressIndicatorPos, duration, currentTime} = this.state;
        let progressIndicatorPercent = progressIndicatorPos * 100;
        let percent = progressIndicatorPercent + '%';
        let {id} = this.props;
        let indicatorStyle: any = {};
        let ariaValueNow;

        if (this.initialized) {
            indicatorStyle.left = percent;
            if (
                progressIndicatorPos >
                1 - this.progressIndicatorWidth / (this.refs[PROGRESS_BAR] as any).offsetWidth
            ) {
                indicatorStyle.left = '100%';
                progressIndicatorPercent = 100;
                indicatorStyle.WebkitTransform = 'translateX(-100%)';
                indicatorStyle.transform = indicatorStyle.WebkitTransform;
            }
            ariaValueNow = Math.round(progressIndicatorPercent);
        } else {
            ariaValueNow = 0;
        }

        return (
            <div
                ref={PROGRESS_BAR}
                className={`audio-process-bar ${PROGRESS_BAR}`}
                data-test-automation-id={`audioProgress${this.props.id}`}>
                <div
                    className="audio-process-bar-total"
                    data-test-automation-id={`audioProgressTotal${id}`}
                />
                <div
                    className="audio-process-bar-left"
                    style={{width: percent}}
                    data-test-automation-id={`audioProgressLeft${id}`}
                />
                <div
                    role="slider"
                    tabIndex={0}
                    aria-label={this.props.timeSlider}
                    aria-valuemin={0}
                    aria-valuemax={100}
                    aria-valuenow={ariaValueNow}
                    aria-valuetext={
                        duration && !_.isNaN(duration)
                            ? formatSeconds(currentTime)
                            : ariaValueNow + '%'
                    }
                    ref={PROGRESS_INDICATOR}
                    className="audio-process-indicator"
                    style={indicatorStyle}
                    data-test-automation-id={`audioProgressIndicator${id}`}
                />
            </div>
        );
    }

    getDuration() {
        if (!this.isFullMode()) {
            return null;
        }

        let {currentTime, duration} = this.state;
        return (
            <div
                className="audio-duration"
                data-test-automation-id={`audioDuration${this.props.id}`}>
                {formatSeconds(currentTime)}&nbsp;/&nbsp;{formatSeconds(duration)}
            </div>
        );
    }

    getVolumnControl() {
        if (!this.isFullMode()) {
            return null;
        }
        let {id} = this.props;
        let {volume, isMuted} = this.state;
        return (
            <VolumeControl id={id} volume={volume} isMuted={isMuted} setVolume={this.setVolume} />
        );
    }

    getRecordButton() {
        if (this.isInVisible()) {
            return null;
        }
        let {onRecord, id} = this.props;

        if (!onRecord || !_.isFunction(onRecord)) {
            return null;
        }
        return (
            <Button
                aria-label={this.props.recordLabel}
                className="rc-audio-icon-wrapper rc-audio-record"
                testAutomationId={`audioRecord${id}`}
                onClick={e => {
                    e.preventDefault();
                    onRecord();
                }}>
                <Icon name="record" aria-hidden="true" />
            </Button>
        );
    }

    getDownloadBtn() {
        if (this.isInVisible()) {
            return null;
        }
        let {downloadUrl} = this.props;
        return downloadUrl ? (
            <div className="rc-audio-download">
                <a
                    role="button"
                    aria-label={this.props.downloadLabel}
                    data-test-automation-id={`audioDownload${this.props.id}`}
                    href={downloadUrl}
                    target="_blank">
                    <Icon name="circle-download" />
                </a>
            </div>
        ) : null;
    }

    play(forcePlay = false) {
        if (!this.props.audioUrl) {
            return;
        }
        let {playing} = this.state;
        if (forcePlay) {
            playing = false;
        }

        this.player = this.player || this.createAudio();
        this.player.volume = this.state.isMuted ? 0 : this.state.volume;

        let {canPause} = this.props;
        if (!playing) {
            this.player.play();
        } else if (!!canPause) {
            this.player.pause();
        }
        this.monitorAudioProgress();

        let duration = this.player.duration;
        if (!duration || _.isNaN(duration)) {
            duration = 0;
        }

        this.setState({
            playing: !playing,
            currentTime: this.player.currentTime,
            duration
        });
    }

    monitorAudioProgress() {
        if (this.player && !this.player.ontimeupdate) {
            this.player.ontimeupdate = () => {
                let {currentTime, duration} = this.player;
                this.setState({
                    currentTime,
                    progressIndicatorPos: duration > 0 ? currentTime / duration : 0
                });
            };
        }
    }

    stopMonitorAudioProgress() {
        if (this.player && this.player.ontimeupdate) {
            this.player.ontimeupdate = null;
        }
    }

    createAudio() {
        let audio = new Audio(this.props.audioUrl.replace('.wav', '.mp3'));
        let {onPlay} = this.props;

        audio.onended = () => {
            let {progressIndicatorPos} = this.state;
            if (this.player.ontimeupdate) {
                progressIndicatorPos = 0;
            }
            this.setState({
                currentTime: 0,
                progressIndicatorPos,
                playing: false
            });
        };

        audio.onloadeddata = () => {
            this.setState({
                duration: this.player.duration
            });
        };

        audio.onerror = () => {
            let {onLoadError, loadingError} = this.props;
            if (onLoadError) {
                onLoadError(loadingError);
            } else {
                showAlert(loadingError);
            }
        };

        audio.onplay = () => {
            EventBus.emit(PLAYING_EVENT, this);
            if (onPlay) {
                onPlay();
            }
        };

        return audio;
    }

    updateProgressByMouse(e, play = true) {
        let progressBar = this.refs[PROGRESS_BAR] as any;
        let distance = e.clientX - progressBar.getBoundingClientRect().left;

        let processPercentage = getProcessedPercentage(
            distance,
            progressBar.offsetWidth,
            this.progressIndicatorWidth / 2
        );

        this.updateProgress(processPercentage, play);
    }

    updateProgressByKeyboard(e, play = true, step = STEP) {
        let duration = this.state.duration || 60; //if the audio wasn't loaded, pretend it's 1 minutes long
        let progressBar = this.refs[PROGRESS_BAR] as any;
        let progressIndicator = this.refs[PROGRESS_INDICATOR] as any;
        let positionStep = (step / duration) * progressBar.offsetWidth;
        let originalPosition =
            progressIndicator.getBoundingClientRect().left -
            progressBar.getBoundingClientRect().left;
        let distance =
            originalPosition +
            positionStep +
            (this.progressIndicatorWidth / 2) * (step < 0 ? -1 : 1);
        let processPercentage = play
            ? this.state.progressIndicatorPos
            : getProcessedPercentage(
                  distance,
                  progressBar.offsetWidth,
                  this.progressIndicatorWidth / 2
              );
        this.stopMonitorAudioProgress();
        this.updateProgress(processPercentage, play);
    }

    updateProgress(processPercentage, play = true) {
        let {currentTime, duration, progressIndicatorPos} = this.state;
        if (duration === 0) {
            //audio not loaded
            if (progressIndicatorPos !== processPercentage) {
                this.setState({
                    progressIndicatorPos: processPercentage
                });
            }
        } else {
            currentTime = duration * processPercentage;
            if (currentTime === duration) {
                currentTime = duration - 0.01;
            }
            if (play) {
                this.player.currentTime = currentTime;
                this.player.play();
                this.monitorAudioProgress();
                this.setState({
                    currentTime,
                    progressIndicatorPos: processPercentage,
                    playing: true
                });
            } else {
                this.setState({
                    progressIndicatorPos: processPercentage
                });
            }
        }
    }

    setVolume = (currentVolume, isMuted) => {
        let {volume} = this.state;

        if (_.isUndefined(isMuted)) {
            if (!currentVolume) {
                currentVolume = volume;
                isMuted = true;
            } else {
                isMuted = false;
            }
        }

        if (this.player) {
            this.player.volume = isMuted ? 0 : currentVolume;
        }

        this.setState({
            volume: currentVolume,
            isMuted
        });
    };

    isFullMode() {
        return this.props.mode === MODES.FULL;
    }

    isInVisible() {
        return this.props.mode === MODES.INVISIBLE;
    }

    onPlayerBlur = event => {
        const audioPlayerBlurred = !this.audioPlayerWrapper.contains(event.relatedTarget);
        if (audioPlayerBlurred && this.props.onBlur) {
            this.props.onBlur(event);
        }
    };

    render() {
        let {id, mode, onRecord} = this.props;
        let audioClassName = classnames('rc-audio', {
            'rc-audio-invisible': mode === MODES.INVISIBLE
        });
        let className = classnames('rc-audio-player', {
            'rc-audio-player-full': mode === MODES.FULL,
            'rc-audio-player-recorder': !!onRecord
        });

        return (
            <div
                id={id}
                className={audioClassName}
                onBlur={this.onPlayerBlur}
                ref={ref => (this.audioPlayerWrapper = ref)}>
                <div className={className} data-test-automation-id={`audioPlayer${id}`}>
                    {this.getPlayButton()}
                    {this.getProgressBar()}
                    {this.getDuration()}
                    {this.getVolumnControl()}
                </div>
                {this.getRecordButton()}
                {this.getDownloadBtn()}
            </div>
        );
    }
}
