import { assignNativeAudioNodeAudioParamValue } from '../helpers/assign-native-audio-node-audio-param-value';
import { assignNativeAudioNodeOption } from '../helpers/assign-native-audio-node-option';
import { assignNativeAudioNodeOptions } from '../helpers/assign-native-audio-node-options';
import { wrapAudioBufferSourceNodeStartMethodConsecutiveCalls } from '../helpers/wrap-audio-buffer-source-node-start-method-consecutive-calls';
import { wrapAudioScheduledSourceNodeStartMethodNegativeParameters } from '../helpers/wrap-audio-scheduled-source-node-start-method-negative-parameters';
import { wrapAudioScheduledSourceNodeStopMethodNegativeParameters } from '../helpers/wrap-audio-scheduled-source-node-stop-method-negative-parameters';
import { TNativeAudioBufferSourceNodeFactoryFactory } from '../types';

export const createNativeAudioBufferSourceNodeFactory: TNativeAudioBufferSourceNodeFactoryFactory = (
    addSilentConnection,
    cacheTestResult,
    testAudioBufferSourceNodeStartMethodConsecutiveCallsSupport,
    testAudioBufferSourceNodeStartMethodOffsetClampingSupport,
    testAudioBufferSourceNodeStopMethodNullifiedBufferSupport,
    testAudioScheduledSourceNodeStartMethodNegativeParametersSupport,
    testAudioScheduledSourceNodeStopMethodConsecutiveCallsSupport,
    testAudioScheduledSourceNodeStopMethodNegativeParametersSupport,
    wrapAudioBufferSourceNodeStartMethodOffsetClampling,
    wrapAudioBufferSourceNodeStopMethodNullifiedBuffer,
    wrapAudioScheduledSourceNodeStopMethodConsecutiveCalls
) => {
    return (nativeContext, options) => {
        const nativeAudioBufferSourceNode = nativeContext.createBufferSource();

        assignNativeAudioNodeOptions(nativeAudioBufferSourceNode, options);

        assignNativeAudioNodeAudioParamValue(nativeAudioBufferSourceNode, options, 'playbackRate');

        assignNativeAudioNodeOption(nativeAudioBufferSourceNode, options, 'buffer');

        // Bug #149: Safari does not yet support the detune AudioParam.

        assignNativeAudioNodeOption(nativeAudioBufferSourceNode, options, 'loop');
        assignNativeAudioNodeOption(nativeAudioBufferSourceNode, options, 'loopEnd');
        assignNativeAudioNodeOption(nativeAudioBufferSourceNode, options, 'loopStart');

        // Bug #69: Safari does allow calls to start() of an already scheduled AudioBufferSourceNode.
        if (
            !cacheTestResult(testAudioBufferSourceNodeStartMethodConsecutiveCallsSupport, () =>
                testAudioBufferSourceNodeStartMethodConsecutiveCallsSupport(nativeContext)
            )
        ) {
            wrapAudioBufferSourceNodeStartMethodConsecutiveCalls(nativeAudioBufferSourceNode);
        }

        // Bug #154 & #155: Safari does not handle offsets which are equal to or greater than the duration of the buffer.
        if (
            !cacheTestResult(testAudioBufferSourceNodeStartMethodOffsetClampingSupport, () =>
                testAudioBufferSourceNodeStartMethodOffsetClampingSupport(nativeContext)
            )
        ) {
            wrapAudioBufferSourceNodeStartMethodOffsetClampling(nativeAudioBufferSourceNode);
        }

        // Bug #162: Safari does throw an error when stop() is called on an AudioBufferSourceNode which has no buffer assigned to it.
        if (
            !cacheTestResult(testAudioBufferSourceNodeStopMethodNullifiedBufferSupport, () =>
                testAudioBufferSourceNodeStopMethodNullifiedBufferSupport(nativeContext)
            )
        ) {
            wrapAudioBufferSourceNodeStopMethodNullifiedBuffer(nativeAudioBufferSourceNode, nativeContext);
        }

        // Bug #44: Safari does not throw a RangeError yet.
        if (
            !cacheTestResult(testAudioScheduledSourceNodeStartMethodNegativeParametersSupport, () =>
                testAudioScheduledSourceNodeStartMethodNegativeParametersSupport(nativeContext)
            )
        ) {
            wrapAudioScheduledSourceNodeStartMethodNegativeParameters(nativeAudioBufferSourceNode);
        }

        // Bug #19: Safari does not ignore calls to stop() of an already stopped AudioBufferSourceNode.
        if (
            !cacheTestResult(testAudioScheduledSourceNodeStopMethodConsecutiveCallsSupport, () =>
                testAudioScheduledSourceNodeStopMethodConsecutiveCallsSupport(nativeContext)
            )
        ) {
            wrapAudioScheduledSourceNodeStopMethodConsecutiveCalls(nativeAudioBufferSourceNode, nativeContext);
        }

        // Bug #44: Only Firefox does not throw a RangeError yet.
        if (
            !cacheTestResult(testAudioScheduledSourceNodeStopMethodNegativeParametersSupport, () =>
                testAudioScheduledSourceNodeStopMethodNegativeParametersSupport(nativeContext)
            )
        ) {
            wrapAudioScheduledSourceNodeStopMethodNegativeParameters(nativeAudioBufferSourceNode);
        }

        // Bug #175: Safari will not fire an ended event if the AudioBufferSourceNode is unconnected.
        addSilentConnection(nativeContext, nativeAudioBufferSourceNode);

        return nativeAudioBufferSourceNode;
    };
};
