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";
import Color = require("color");

export interface AudioWaveformProps {
    duration: number; // length of media clip
    rawDuration: number; // length of underlying media data
    mediaPath: string;
    layoutDoc: Doc;
    clipStart: number;
    clipEnd: number;
    zoomFactor: number;
    PanelHeight: number;
    PanelWidth: 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; }
    @computed get zoomFactor() { return this.props.zoomFactor; }
    @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec("number"), []); }

    audioBucketField = (start: number, end: number, zoomFactor: number) => "audioBuckets/" + "/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_") + "/" + (zoomFactor * 10);

    componentWillUnmount() {
        this._disposer?.();
    }
    componentDidMount() {
        this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
            ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
                if (!this.props.layoutDoc[fieldKey]) {
                    // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
                    const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec("number"));
                    this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice(clipStart / this.props.rawDuration * waveform.length, clipEnd / this.props.rawDuration * waveform.length));
                    setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
                }
            }, { fireImmediately: true });

    }

    // decodes the audio file into peaks for generating the waveform
    createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
        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 = clipStart / this.props.rawDuration;
                        const endInd = clipEnd / this.props.rawDuration;
                        const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
                        const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);

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


export interface WaveformProps {
    barWidth: number;
    width: () => number;
    height: () => number;
    peaks: number[];
    color: string;
}

// @observer
// export class Waveform extends React.Component<WaveformProps> {
//     private _canvas: HTMLCanvasElement | null = null;

//     get width() { return this.props.width(); }
//     get height() { return this.props.height(); }
//     get peaks() { return this.props.peaks; }

//     componentDidMount() {
//         this.drawBars();
//     }

//     drawBars() {
//         const waveCanvasCtx = this._canvas?.getContext("2d");

//         if (waveCanvasCtx) {
//             const pixelRatio = window.devicePixelRatio;
//             console.log(pixelRatio);

//             const displayWidth = Math.round(this.width);
//             const displayHeight = Math.round(this.height);
//             waveCanvasCtx.canvas.width = this.width;
//             waveCanvasCtx.canvas.height = this.height;
//             waveCanvasCtx.canvas.style.width = `${displayWidth}px`;
//             waveCanvasCtx.canvas.style.height = `${displayHeight}px`;

//             waveCanvasCtx.clearRect(0, 0, this.width, this.height);

//             const hasMinVals = [].some.call(this.peaks, (val) => val < 0);
//             let filteredPeaks = this.peaks;
//             if (hasMinVals) {
//                 // If the first value is negative, add 1 to the filtered indices
//                 let indexOffset = 0;
//                 if (this.peaks[0] < 0) {
//                     indexOffset = 1;
//                 }
//                 filteredPeaks = [].filter.call(
//                     this.peaks,
//                     (_, index) => (index + indexOffset) % 2 == 0
//                 );
//             }

//             const $ = 0.5;
//             const height = this.height;
//             const offsetY = 0;
//             const halfH = this.height / 2;
//             const length = filteredPeaks.length;
//             const bar = this.props.barWidth;
//             const gap = 2;
//             const step = bar + gap;

//             let absmax = 1;
//             absmax = this.absMax(filteredPeaks);

//             const scale = length / this.width;

//             waveCanvasCtx.fillStyle = this.props.color;

//             for (let i = 0; i < this.width; i += step) {
//                 let h = Math.round(filteredPeaks[Math.floor(i * scale)] / absmax * halfH)
//                 if (h === 0) {
//                     h = 1
//                 }
//                 waveCanvasCtx.fillRect(i + $, halfH - h + offsetY, bar + $, h * 2)
//             }
//         }
//     }

//     absMax = (values: number[]) => {
//         let max = -Infinity;
//         for (const i in values) {
//             const num = Math.abs(values[i]);
//             if (num > max) {
//                 max = num;
//             }
//         }

//         return max;
//     }


//     render() {
//         return this.props.peaks ? (
//             <canvas
//                 ref={(instance) => {
//                     this._canvas = instance;
//                 }}
//             />
//         ) : null
//     }
// }