import React = require("react");
import axios from "axios";
import { action, computed, reaction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import Waveform from "react-audio-waveform";
import { Doc } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, NumCast } from "../../fields/Types";
import { numberRange } from "../../Utils";
import "./AudioWaveform.scss";
import { Colors } from "./global/globalEnums";

export interface AudioWaveformProps {
    duration: number; // length of media clip
    rawDuration: number; // length of underlying media data
    mediaPath: string;
    layoutDoc: Doc;
    trimming: boolean;
    clipStart: number;
    clipEnd: number;
    PanelHeight: () => number;
}

@observer
export class AudioWaveform extends React.Component<AudioWaveformProps> {
    public static NUMBER_OF_BUCKETS = 100;
    _disposer: IReactionDisposer | undefined;
    @computed get _waveHeight() {
        return Math.max(50, this.props.PanelHeight());
    }

    @computed get clipStart() { return this.props.clipStart; }
    @computed get clipEnd() { return this.props.clipEnd; }
    audioBucketField = (start: number, end: number) => { return "audioBuckets-" + start.toFixed(2) + "-" + end.toFixed(2); }
    @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)], listSpec("number"), []); }
    componentWillUnmount() {
        this._disposer?.();
    }
    componentDidMount() {
        this._disposer = reaction(() => [this.clipStart, this.clipEnd, this.audioBuckets.length],
            (range) => {
                if (range[2] !== AudioWaveform.NUMBER_OF_BUCKETS) {
                    if (!this.props.layoutDoc[this.audioBucketField(range[0], range[1])]) {
                        // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
                        this.props.layoutDoc[this.audioBucketField(range[0], range[1])] = new List<number>(numberRange(AudioWaveform.NUMBER_OF_BUCKETS));
                        setTimeout(this.createWaveformBuckets);
                    }
                }
            }, { fireImmediately: true });

    }

    // decodes the audio file into peaks for generating the waveform
    createWaveformBuckets = async () => {
        axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then(
            (response) => {
                const context = new window.AudioContext();
                context.decodeAudioData(
                    response.data,
                    action((buffer) => {
                        const rawDecodedAudioData = buffer.getChannelData(0);
                        const startInd = this.clipStart / this.props.rawDuration;
                        const endInd = this.clipEnd / this.props.rawDuration;
                        const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));

                        const bucketDataSize = Math.floor(
                            decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS
                        );
                        const brange = Array.from(Array(bucketDataSize));
                        const bucketList = numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map(
                            (i: number) =>
                                brange.reduce(
                                    (p, x, j) =>
                                        Math.abs(
                                            Math.max(p, decodedAudioData[i * bucketDataSize + j])
                                        ),
                                    0
                                ) / 2
                        );
                        this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)] = new List<number>(bucketList);
                    })
                );
            }
        );
    }
    render() {
        return (
            <div className="audioWaveform">
                <Waveform
                    color={Colors.MEDIUM_BLUE}
                    height={this._waveHeight}
                    barWidth={0.1}
                    pos={this.props.duration}
                    duration={this.props.duration}
                    peaks={this.audioBuckets}
                    progressColor={Colors.MEDIUM_BLUE}
                />
            </div>
        );
    }
}
