#include awaitable.js

const MamiSoundTest = function(settings, audio, manager, library, clickPos) {
    if(!settings.defined('soundRate'))
        settings.define('soundRate').default(1.0).min(0.001).max(2.0).virtual().create();
    if(!settings.defined('soundDetune'))
        settings.define('soundDetune').default(0).min(-1200).max(1200).virtual().create();
    if(!settings.defined('soundLoopStart'))
        settings.define('soundLoopStart').default(0).min(0).virtual().create();
    if(!settings.defined('soundLoopEnd'))
        settings.define('soundLoopEnd').default(0).min(0).virtual().create();
    if(!settings.defined('soundReverse'))
        settings.define('soundReverse').default(false).virtual().create();

    const hasClickPos = Array.isArray(clickPos) && clickPos.length > 1;
    const volumeSetting = settings.info('soundVolume');
    const rateSetting = settings.info('soundRate');
    const detuneSetting = settings.info('soundDetune');
    const loopStartSetting = settings.info('soundLoopStart');
    const loopEndSetting = settings.info('soundLoopEnd');
    const sources = [];

    let libraryButtons;
    let nowPlaying;
    let searchBox, volumeSlider, rateSlider, detuneSlider;
    let loopStartBox, loopEndBox, reverseBox;
    const container = <div class="sndtest">
        <div class="sndtest-view">
            <button onclick={() => { mami.views.pop(); }}>Exit</button>
        </div>
        <div class="sndtest-controls">
            <label class="sndtest-control">
                <div class="sndtest-control-name">Search</div>
                {searchBox = <input type="text" placeholder="type to filter"/>}
            </label>
            <label class="sndtest-control">
                <div class="sndtest-control-name">Volume</div>
                {volumeSlider = <input type="range" min={volumeSetting.min} max={volumeSetting.max} onchange={() => { settings.set('soundVolume', volumeSlider.value); }} ondblclick={() => { settings.delete('soundVolume'); }}/>}
            </label>
            <label class="sndtest-control">
                <div class="sndtest-control-name">Rate</div>
                {rateSlider = <input type="range" min={rateSetting.min * 1000} max={rateSetting.max * 1000} onchange={() => { settings.set('soundRate', rateSlider.value / 1000); }} ondblclick={() => { settings.delete('soundRate'); }}/>}
            </label>
            <label class="sndtest-control">
                <div class="sndtest-control-name">Detune</div>
                {detuneSlider = <input type="range" min={detuneSetting.min} max={detuneSetting.max} onchange={() => { settings.set('soundDetune', detuneSlider.value); }} ondblclick={() => { settings.delete('soundDetune'); }}/>}
            </label>
            <label class="sndtest-control">
                <div class="sndtest-control-name">Loop start</div>
                {loopStartBox = <input type="number" step="0.00001" min={loopStartSetting.min} onchange={() => { settings.set('soundLoopStart', loopStartBox.value); }} ondblclick={() => { settings.delete('soundLoopStart'); }}/>}
            </label>
            <label class="sndtest-control">
                <div class="sndtest-control-name">Loop end</div>
                {loopEndBox = <input type="number" step="0.00001" min={loopEndSetting.min} onchange={() => { settings.set('soundLoopEnd', loopEndBox.value); }} ondblclick={() => { settings.delete('soundLoopEnd'); }}/>}
            </label>
            <label class="sndtest-control">
                <div class="sndtest-control-name">Reverse</div>
                {reverseBox = <input type="checkbox" onchange={() => { settings.set('soundReverse', reverseBox.checked); }}/>}
            </label>
        </div>
        {libraryButtons = <div class="sndtest-library"/>}
        {nowPlaying = <div class="sndtest-playing"/>}
    </div>;

    settings.watch('soundVolume', ev => {
        volumeSlider.value = ev.detail.value;
    });
    settings.watch('soundRate', ev => {
        rateSlider.value = ev.detail.value * 1000;

        for(const source of sources)
            source.setRate(ev.detail.value);
    });
    settings.watch('soundDetune', ev => {
        detuneSlider.value = ev.detail.value;

        for(const source of sources)
            source.setDetune(ev.detail.value);
    });
    settings.watch('soundLoopStart', ev => {
        loopStartBox.value = ev.detail.value;
    });
    settings.watch('soundLoopEnd', ev => {
        loopEndBox.value = ev.detail.value;
    });
    settings.watch('soundReverse', ev => {
        reverseBox.checked = ev.detail.value;
    });

    const startPlay = async info => {
        let controls, state, name;
        const player = <div class="sndtest-player">
            <div class="sndtest-player-details">
                <div class="sndtest-player-title">{info.getTitle()}</div>
                {name = <div class="sndtest-player-name">{info.getName()}</div>}
            </div>
            {controls = <div class="sndtest-player-controls"/>}
            {state = <div class="sndtest-player-state"/>}
        </div>;

        nowPlaying.appendChild(player);

        let buffer, source;
        try {
            state.textContent = 'Loading...';
            buffer = await manager.loadBuffer(info.getSources());
            name.textContent += ` (${buffer.duration})`;

            source = audio.createSource(buffer, settings.get('soundReverse'));
            sources.push(source);

            state.textContent = 'Configuring...';
            const rate = settings.get('soundRate');
            source.setRate(rate);

            const detune = settings.get('soundDetune');
            source.setDetune(detune);

            const loopStart = settings.get('soundLoopStart');
            const loopEnd = settings.get('soundLoopEnd');
            source.setLoop(loopEnd > 0, loopStart, loopEnd);

            controls.appendChild(<button onclick={() => source.stop()}>Stop</button>);

            state.textContent = 'Playing...';
            await source.play();

            state.textContent = 'Finished.';
        } catch(ex) {
            console.error(ex);
            state.textContent = `Error: ${ex}`;
        } finally {
            $ari(sources, source);
            await MamiSleep(2000);
            nowPlaying.removeChild(player);
        }
    };

    const names = library.names();
    for(const name of names) {
        const info = library.info(name);
        libraryButtons.appendChild(<button onclick={() => { startPlay(info); }} data-search={`${info.getTitle().toLowerCase()} ${info.getName().toLowerCase()}`}>{info.getTitle()} ({info.getName()})</button>);
    }

    searchBox.addEventListener('change', () => {
        const str = searchBox.value.trim().toLowerCase();
        for(const button of libraryButtons.children)
            button.classList.toggle('hidden', !button.dataset.search.includes(str));
    });

    return {
        getElement: () => container,
        onViewPop: async () => {
            for(const source of sources)
                source.stop();
        },
        getViewTransition: mode => {
            if(!hasClickPos)
                return;

            if(mode === 'push')
                return ctx => MamiAnimate({
                    async: true,
                    duration: 1500,
                    easing: 'inQuad',
                    start: () => {
                        library.play('mario:keyhole');
                        ctx.toElem.style.transform = 'scale(0) translate(25%, 25%)';
                        ctx.toElem.style.transformOrigin = `${clickPos[0]}px ${clickPos[1]}px`;
                    },
                    update: (t, rt) => ctx.toElem.style.transform = `scale(${t}) translate(${25 * (1 - rt)}%, ${25 * (1 - rt)}%)`,
                    end: () => {
                        ctx.toElem.style.transform = null;
                        ctx.toElem.style.transformOrigin = null;
                    },
                });

            if(mode === 'pop')
                return ctx => MamiAnimate({
                    async: true,
                    duration: 1000,
                    easing: 'outQuad',
                    start: () => {
                        ctx.fromElem.style.transformOrigin = `${clickPos[0]}px ${clickPos[1]}px`;
                    },
                    update: (t, rt) => ctx.fromElem.style.transform = `scale(${1 - t}) rotate(${-1080 * t}deg) translate(${50 * rt}%, ${50 * rt}%)`,
                    end: () => {
                        ctx.fromElem.style.transform = null;
                        ctx.fromElem.style.transformOrigin = null;
                    },
                });
        },
    };
};
