import React from "react";
import { connect } from "react-redux";

import Controls from "./components/controls";
import Visualizer from "./components/visualizer";
import { State } from "./store";

type AudioState = {
    audioContext: AudioContext;
    audioSource: HTMLAudioElement;
    audioSourceNode: MediaElementAudioSourceNode;
    audioVolume: GainNode;
};

type MusicPlayerProps = {
    playing: boolean;
    muted: boolean;
    source?: string;
};

class MusicPlayer extends React.Component<MusicPlayerProps, State> {
    private audioState: AudioState;

    constructor(props: MusicPlayerProps) {
        super(props);

        const context = new AudioContext();
        const source = new Audio();
        const sourceNode = context.createMediaElementSource(source);
        const volume = context.createGain();

        sourceNode.connect(volume);
        volume.connect(context.destination);

        this.audioState = {
            audioContext: context,
            audioSourceNode: sourceNode,
            audioSource: source,
            audioVolume: volume,
        };
    }

    render() {
        return (
            <div className="is-flex-grow-1 is-flex is-flex-direction-column">
                <div className="is-flex-grow-1 is-overflow-hidden">
                    <Visualizer
                        audioContext={this.audioState.audioContext}
                        audioSource={this.audioState.audioSourceNode}
                    />
                </div>
                <div className="is-flex-grow-0">
                    <Controls />
                </div>
            </div>
        );
    }

    async componentDidUpdate() {
        const context = this.audioState.audioContext;
        const source = this.audioState.audioSource;
        const volume = this.audioState.audioVolume;

        // First, set the audio source (if it changed)
        if (this.props.source && source.src != this.props.source) {
            source.src = this.props.source;
        }

        if (this.props.playing) {
            // Chrome is super awkward about AudioContext, and won't
            // even allow creating one without complaining about
            // wanting user input, so we need to resume the context
            // before we can actually play.
            //
            // Luckily, this has no adverse effects on Firefox.
            try {
                await context.resume();
                await source.play();
            } catch (error) {
                if (error instanceof DOMException) {
                    console.error(`Could not play audio: ${error.message}`);
                } else {
                    throw error;
                }
            }
        } else {
            source.pause();
        }

        if (!this.props.muted) {
            volume.gain.setValueAtTime(1, context.currentTime);
        } else {
            volume.gain.setValueAtTime(0, context.currentTime);
        }
    }
}

function mapStateToProps(state: State): MusicPlayerProps {
    return {
        playing: state.musicState.playing,
        muted: state.musicState.muted,
        source: state.musicState.source,
    };
}

export default connect(mapStateToProps)(MusicPlayer);
