import { assignNativeAudioNodeOptions } from '../helpers/assign-native-audio-node-options';
import { interceptConnections } from '../helpers/intercept-connections';
export const createNativePannerNodeFakerFactory = (connectNativeAudioNodeToNativeAudioNode, createInvalidStateError, createNativeChannelMergerNode, createNativeGainNode, createNativeScriptProcessorNode, createNativeWaveShaperNode, createNotSupportedError, disconnectNativeAudioNodeFromNativeAudioNode, getFirstSample, monitorConnections) => {
    return (nativeContext, { coneInnerAngle, coneOuterAngle, coneOuterGain, distanceModel, maxDistance, orientationX, orientationY, orientationZ, panningModel, positionX, positionY, positionZ, refDistance, rolloffFactor, ...audioNodeOptions }) => {
        const pannerNode = nativeContext.createPanner();
        // Bug #125: Safari does not throw an error yet.
        if (audioNodeOptions.channelCount > 2) {
            throw createNotSupportedError();
        }
        // Bug #126: Safari does not throw an error yet.
        if (audioNodeOptions.channelCountMode === 'max') {
            throw createNotSupportedError();
        }
        assignNativeAudioNodeOptions(pannerNode, audioNodeOptions);
        const SINGLE_CHANNEL_OPTIONS = {
            channelCount: 1,
            channelCountMode: 'explicit',
            channelInterpretation: 'discrete'
        };
        const channelMergerNode = createNativeChannelMergerNode(nativeContext, {
            ...SINGLE_CHANNEL_OPTIONS,
            channelInterpretation: 'speakers',
            numberOfInputs: 6
        });
        const inputGainNode = createNativeGainNode(nativeContext, { ...audioNodeOptions, gain: 1 });
        const orientationXGainNode = createNativeGainNode(nativeContext, { ...SINGLE_CHANNEL_OPTIONS, gain: 1 });
        const orientationYGainNode = createNativeGainNode(nativeContext, { ...SINGLE_CHANNEL_OPTIONS, gain: 0 });
        const orientationZGainNode = createNativeGainNode(nativeContext, { ...SINGLE_CHANNEL_OPTIONS, gain: 0 });
        const positionXGainNode = createNativeGainNode(nativeContext, { ...SINGLE_CHANNEL_OPTIONS, gain: 0 });
        const positionYGainNode = createNativeGainNode(nativeContext, { ...SINGLE_CHANNEL_OPTIONS, gain: 0 });
        const positionZGainNode = createNativeGainNode(nativeContext, { ...SINGLE_CHANNEL_OPTIONS, gain: 0 });
        const scriptProcessorNode = createNativeScriptProcessorNode(nativeContext, 256, 6, 1);
        const waveShaperNode = createNativeWaveShaperNode(nativeContext, {
            ...SINGLE_CHANNEL_OPTIONS,
            curve: new Float32Array([1, 1]),
            oversample: 'none'
        });
        let lastOrientation = [orientationX, orientationY, orientationZ];
        let lastPosition = [positionX, positionY, positionZ];
        const buffer = new Float32Array(1);
        // tslint:disable-next-line:deprecation
        scriptProcessorNode.onaudioprocess = ({ inputBuffer }) => {
            const orientation = [
                getFirstSample(inputBuffer, buffer, 0),
                getFirstSample(inputBuffer, buffer, 1),
                getFirstSample(inputBuffer, buffer, 2)
            ];
            if (orientation.some((value, index) => value !== lastOrientation[index])) {
                pannerNode.setOrientation(...orientation); // tslint:disable-line:deprecation
                lastOrientation = orientation;
            }
            const positon = [
                getFirstSample(inputBuffer, buffer, 3),
                getFirstSample(inputBuffer, buffer, 4),
                getFirstSample(inputBuffer, buffer, 5)
            ];
            if (positon.some((value, index) => value !== lastPosition[index])) {
                pannerNode.setPosition(...positon); // tslint:disable-line:deprecation
                lastPosition = positon;
            }
        };
        Object.defineProperty(orientationYGainNode.gain, 'defaultValue', { get: () => 0 });
        Object.defineProperty(orientationZGainNode.gain, 'defaultValue', { get: () => 0 });
        Object.defineProperty(positionXGainNode.gain, 'defaultValue', { get: () => 0 });
        Object.defineProperty(positionYGainNode.gain, 'defaultValue', { get: () => 0 });
        Object.defineProperty(positionZGainNode.gain, 'defaultValue', { get: () => 0 });
        const nativePannerNodeFaker = {
            get bufferSize() {
                return undefined;
            },
            get channelCount() {
                return pannerNode.channelCount;
            },
            set channelCount(value) {
                // Bug #125: Safari does not throw an error yet.
                if (value > 2) {
                    throw createNotSupportedError();
                }
                inputGainNode.channelCount = value;
                pannerNode.channelCount = value;
            },
            get channelCountMode() {
                return pannerNode.channelCountMode;
            },
            set channelCountMode(value) {
                // Bug #126: Safari does not throw an error yet.
                if (value === 'max') {
                    throw createNotSupportedError();
                }
                inputGainNode.channelCountMode = value;
                pannerNode.channelCountMode = value;
            },
            get channelInterpretation() {
                return pannerNode.channelInterpretation;
            },
            set channelInterpretation(value) {
                inputGainNode.channelInterpretation = value;
                pannerNode.channelInterpretation = value;
            },
            get coneInnerAngle() {
                return pannerNode.coneInnerAngle;
            },
            set coneInnerAngle(value) {
                pannerNode.coneInnerAngle = value;
            },
            get coneOuterAngle() {
                return pannerNode.coneOuterAngle;
            },
            set coneOuterAngle(value) {
                pannerNode.coneOuterAngle = value;
            },
            get coneOuterGain() {
                return pannerNode.coneOuterGain;
            },
            set coneOuterGain(value) {
                // Bug #127: Safari does not throw an InvalidStateError yet.
                if (value < 0 || value > 1) {
                    throw createInvalidStateError();
                }
                pannerNode.coneOuterGain = value;
            },
            get context() {
                return pannerNode.context;
            },
            get distanceModel() {
                return pannerNode.distanceModel;
            },
            set distanceModel(value) {
                pannerNode.distanceModel = value;
            },
            get inputs() {
                return [inputGainNode];
            },
            get maxDistance() {
                return pannerNode.maxDistance;
            },
            set maxDistance(value) {
                // Bug #128: Safari does not throw an error yet.
                if (value < 0) {
                    throw new RangeError();
                }
                pannerNode.maxDistance = value;
            },
            get numberOfInputs() {
                return pannerNode.numberOfInputs;
            },
            get numberOfOutputs() {
                return pannerNode.numberOfOutputs;
            },
            get orientationX() {
                return orientationXGainNode.gain;
            },
            get orientationY() {
                return orientationYGainNode.gain;
            },
            get orientationZ() {
                return orientationZGainNode.gain;
            },
            get panningModel() {
                return pannerNode.panningModel;
            },
            set panningModel(value) {
                pannerNode.panningModel = value;
            },
            get positionX() {
                return positionXGainNode.gain;
            },
            get positionY() {
                return positionYGainNode.gain;
            },
            get positionZ() {
                return positionZGainNode.gain;
            },
            get refDistance() {
                return pannerNode.refDistance;
            },
            set refDistance(value) {
                // Bug #129: Safari does not throw an error yet.
                if (value < 0) {
                    throw new RangeError();
                }
                pannerNode.refDistance = value;
            },
            get rolloffFactor() {
                return pannerNode.rolloffFactor;
            },
            set rolloffFactor(value) {
                // Bug #130: Safari does not throw an error yet.
                if (value < 0) {
                    throw new RangeError();
                }
                pannerNode.rolloffFactor = value;
            },
            addEventListener(...args) {
                return inputGainNode.addEventListener(args[0], args[1], args[2]);
            },
            dispatchEvent(...args) {
                return inputGainNode.dispatchEvent(args[0]);
            },
            removeEventListener(...args) {
                return inputGainNode.removeEventListener(args[0], args[1], args[2]);
            }
        };
        if (coneInnerAngle !== nativePannerNodeFaker.coneInnerAngle) {
            nativePannerNodeFaker.coneInnerAngle = coneInnerAngle;
        }
        if (coneOuterAngle !== nativePannerNodeFaker.coneOuterAngle) {
            nativePannerNodeFaker.coneOuterAngle = coneOuterAngle;
        }
        if (coneOuterGain !== nativePannerNodeFaker.coneOuterGain) {
            nativePannerNodeFaker.coneOuterGain = coneOuterGain;
        }
        if (distanceModel !== nativePannerNodeFaker.distanceModel) {
            nativePannerNodeFaker.distanceModel = distanceModel;
        }
        if (maxDistance !== nativePannerNodeFaker.maxDistance) {
            nativePannerNodeFaker.maxDistance = maxDistance;
        }
        if (orientationX !== nativePannerNodeFaker.orientationX.value) {
            nativePannerNodeFaker.orientationX.value = orientationX;
        }
        if (orientationY !== nativePannerNodeFaker.orientationY.value) {
            nativePannerNodeFaker.orientationY.value = orientationY;
        }
        if (orientationZ !== nativePannerNodeFaker.orientationZ.value) {
            nativePannerNodeFaker.orientationZ.value = orientationZ;
        }
        if (panningModel !== nativePannerNodeFaker.panningModel) {
            nativePannerNodeFaker.panningModel = panningModel;
        }
        if (positionX !== nativePannerNodeFaker.positionX.value) {
            nativePannerNodeFaker.positionX.value = positionX;
        }
        if (positionY !== nativePannerNodeFaker.positionY.value) {
            nativePannerNodeFaker.positionY.value = positionY;
        }
        if (positionZ !== nativePannerNodeFaker.positionZ.value) {
            nativePannerNodeFaker.positionZ.value = positionZ;
        }
        if (refDistance !== nativePannerNodeFaker.refDistance) {
            nativePannerNodeFaker.refDistance = refDistance;
        }
        if (rolloffFactor !== nativePannerNodeFaker.rolloffFactor) {
            nativePannerNodeFaker.rolloffFactor = rolloffFactor;
        }
        if (lastOrientation[0] !== 1 || lastOrientation[1] !== 0 || lastOrientation[2] !== 0) {
            pannerNode.setOrientation(...lastOrientation); // tslint:disable-line:deprecation
        }
        if (lastPosition[0] !== 0 || lastPosition[1] !== 0 || lastPosition[2] !== 0) {
            pannerNode.setPosition(...lastPosition); // tslint:disable-line:deprecation
        }
        const whenConnected = () => {
            inputGainNode.connect(pannerNode);
            // Bug #119: Safari does not fully support the WaveShaperNode.
            connectNativeAudioNodeToNativeAudioNode(inputGainNode, waveShaperNode, 0, 0);
            waveShaperNode.connect(orientationXGainNode).connect(channelMergerNode, 0, 0);
            waveShaperNode.connect(orientationYGainNode).connect(channelMergerNode, 0, 1);
            waveShaperNode.connect(orientationZGainNode).connect(channelMergerNode, 0, 2);
            waveShaperNode.connect(positionXGainNode).connect(channelMergerNode, 0, 3);
            waveShaperNode.connect(positionYGainNode).connect(channelMergerNode, 0, 4);
            waveShaperNode.connect(positionZGainNode).connect(channelMergerNode, 0, 5);
            channelMergerNode.connect(scriptProcessorNode).connect(nativeContext.destination);
        };
        const whenDisconnected = () => {
            inputGainNode.disconnect(pannerNode);
            // Bug #119: Safari does not fully support the WaveShaperNode.
            disconnectNativeAudioNodeFromNativeAudioNode(inputGainNode, waveShaperNode, 0, 0);
            waveShaperNode.disconnect(orientationXGainNode);
            orientationXGainNode.disconnect(channelMergerNode);
            waveShaperNode.disconnect(orientationYGainNode);
            orientationYGainNode.disconnect(channelMergerNode);
            waveShaperNode.disconnect(orientationZGainNode);
            orientationZGainNode.disconnect(channelMergerNode);
            waveShaperNode.disconnect(positionXGainNode);
            positionXGainNode.disconnect(channelMergerNode);
            waveShaperNode.disconnect(positionYGainNode);
            positionYGainNode.disconnect(channelMergerNode);
            waveShaperNode.disconnect(positionZGainNode);
            positionZGainNode.disconnect(channelMergerNode);
            channelMergerNode.disconnect(scriptProcessorNode);
            scriptProcessorNode.disconnect(nativeContext.destination);
        };
        return monitorConnections(interceptConnections(nativePannerNodeFaker, pannerNode), whenConnected, whenDisconnected);
    };
};
//# sourceMappingURL=native-panner-node-faker-factory.js.map