//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
package com.microsoft.cognitiveservices.speech.audio;

import android.content.Context;

import com.microsoft.cognitiveservices.speech.SpeechConfig;
import com.microsoft.cognitiveservices.speech.util.Contracts;
import com.microsoft.cognitiveservices.speech.util.ContextHolder;
import com.microsoft.cognitiveservices.speech.util.IntRef;
import com.microsoft.cognitiveservices.speech.util.SafeHandle;
import com.microsoft.cognitiveservices.speech.util.SafeHandleType;

import java.lang.AutoCloseable;

/**
 * Represents audio processing options used with audio config class.
 * Note: close() must be called in order to release underlying resources held by the object.
 */
public class AudioProcessingOptions implements AutoCloseable {

    // load the native library.
    static {
        // trigger loading of native library
        try {
            Class.forName(SpeechConfig.class.getName());
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(ex);
        }
    }

    /**
     * Creates an AudioProcessingOptions object with audio processing flags.
     * This method should only be used when the audio input is from a microphone array.
     * On Windows, this method will try to query the microphone array geometry from the audio driver. Audio data is also read from speaker reference channel.
     * On Linux, it assumes that the microphone is a single channel microphone.
     * @param audioProcessingFlags Specifies flags to control the audio processing performed by Speech SDK. It is bitwise OR of constants from AudioProcessingConstants class.
     * @return The audio processing options object being created.
     */
    public static AudioProcessingOptions create(int audioProcessingFlags) {
        IntRef processingOptionsHandle = new IntRef(0);
        Contracts.throwIfFail(create(processingOptionsHandle, audioProcessingFlags));
        return new AudioProcessingOptions(processingOptionsHandle);
    }

    /**
     * Creates an AudioProcessingOptions object with audio processing flags and preset microphone array geometry.
     * @param audioProcessingFlags Specifies flags to control the audio processing performed by Speech SDK. It is bitwise OR of constants from AudioProcessingConstants class.
     * @param microphoneArrayGeometry Specifies the type of microphone array geometry.
     * @return The audio processing options object being created.
     */
    public static AudioProcessingOptions create(int audioProcessingFlags, PresetMicrophoneArrayGeometry microphoneArrayGeometry) {
        return create(audioProcessingFlags, microphoneArrayGeometry, SpeakerReferenceChannel.None);
    }

    /**
     * Creates an AudioProcessingOptions object with audio processing flags, preset microphone array geometry and speaker reference channel position.
     * @param audioProcessingFlags Specifies flags to control the audio processing performed by Speech SDK. It is bitwise OR of constants from AudioProcessingConstants class.
     * @param microphoneArrayGeometry Specifies the type of microphone array geometry.
     * @param speakerReferenceChannel Specifies the speaker reference channel position in the input audio.
     * @return The audio processing options object being created.
     */
    public static AudioProcessingOptions create(int audioProcessingFlags, PresetMicrophoneArrayGeometry microphoneArrayGeometry, SpeakerReferenceChannel speakerReferenceChannel) {
        Contracts.throwIfNull(microphoneArrayGeometry, "microphoneArrayGeometry");
        Contracts.throwIfNull(speakerReferenceChannel, "speakerReferenceChannel");
        IntRef processingOptionsHandle = new IntRef(0);
        Contracts.throwIfFail(createFromPresetMicrophoneArrayGeometry(processingOptionsHandle, audioProcessingFlags, microphoneArrayGeometry.ordinal(), speakerReferenceChannel.ordinal()));
        return new AudioProcessingOptions(processingOptionsHandle);
    }

    /**
     * Creates an AudioProcessingOptions object with audio processing flags and custom microphone array geometry.
     * @param audioProcessingFlags Specifies flags to control the audio processing performed by Speech SDK. It is bitwise OR of constants from AudioProcessingConstants class.
     * @param microphoneArrayGeometry Specifies the microphone array geometry.
     * @return The audio processing options object being created.
     */
    public static AudioProcessingOptions create(int audioProcessingFlags, MicrophoneArrayGeometry microphoneArrayGeometry) {
        return create(audioProcessingFlags, microphoneArrayGeometry, SpeakerReferenceChannel.None);
    }

    /**
     * Creates an AudioProcessingOptions object with audio processing flags, custom microphone array geometry and speaker reference channel position.
     * @param audioProcessingFlags Specifies flags to control the audio processing performed by Speech SDK. It is bitwise OR of constants from AudioProcessingConstants class.
     * @param microphoneArrayGeometry Specifies the microphone array geometry.
     * @param speakerReferenceChannel Specifies the speaker reference channel position in the input audio.
     * @return The audio processing options object being created.
     */
    public static AudioProcessingOptions create(int audioProcessingFlags, MicrophoneArrayGeometry microphoneArrayGeometry, SpeakerReferenceChannel speakerReferenceChannel) {
        Contracts.throwIfNull(microphoneArrayGeometry, "microphoneArrayGeometry");
        Contracts.throwIfNull(speakerReferenceChannel, "speakerReferenceChannel");
        IntRef processingOptionsHandle = new IntRef(0);
        Contracts.throwIfFail(createFromMicrophoneArrayGeometry(processingOptionsHandle, audioProcessingFlags, microphoneArrayGeometry, speakerReferenceChannel.ordinal()));
        return new AudioProcessingOptions(processingOptionsHandle);
    }

    /**
     * Returns the type of audio processing performed by Speech SDK.
     * @return Bitwise OR of flags from AudioProcessingConstants class indicating the audio processing performed by Speech SDK.
     */
    public int getAudioProcessingFlags() {
        IntRef audioProcessingFlags = new IntRef(0);
        Contracts.throwIfFail(getAudioProcessingFlags(audioProcessingOptionsHandle, audioProcessingFlags));
        return (int)audioProcessingFlags.getValue();
    }

    /**
     * Returns the microphone array geometry of the microphone used for audio input.
     * @return Microphone array geometry of the microphone used for audio input.
     */
    public PresetMicrophoneArrayGeometry getPresetMicrophoneArrayGeometry() {
        IntRef intRef = new IntRef(0);
        Contracts.throwIfFail(getPresetMicrophoneArrayGeometry(audioProcessingOptionsHandle, intRef));
        return PresetMicrophoneArrayGeometry.values()[(int)intRef.getValue()];
    }

    /**
     * Returns the microphone array type of the microphone used for audio input.
     * @return Type of microphone array used for audio input.
     */
    public MicrophoneArrayType getMicrophoneArrayType() {
        IntRef intRef = new IntRef(0);
        Contracts.throwIfFail(getMicrophoneArrayType(audioProcessingOptionsHandle, intRef));
        return MicrophoneArrayType.values()[(int)intRef.getValue()];
    }

    /**
     * Returns the start angle used for beamforming.
     * @return Beamforming start angle.
     */
    public int getBeamformingStartAngle() {
        IntRef beamformingStartAngle = new IntRef(0);
        Contracts.throwIfFail(getBeamformingStartAngle(audioProcessingOptionsHandle, beamformingStartAngle));
        return (int)beamformingStartAngle.getValue();
    }

    /**
     * Returns the end angle used for beamforming.
     * @return Beamforming end angle.
     */
    public int getBeamformingEndAngle() {
        IntRef beamformingEndAngle = new IntRef(0);
        Contracts.throwIfFail(getBeamformingEndAngle(audioProcessingOptionsHandle, beamformingEndAngle));
        return (int)beamformingEndAngle.getValue();
    }

    /**
     * Returns the coordinates of microphones in the microphone array used for audio input.
     * @return An array of MicrophoneCoordinates objects.
     */
    public MicrophoneCoordinates[] getMicrophoneCoordinates() {
        IntRef hr = new IntRef(0);
        MicrophoneCoordinates[] microphoneCoordinates = getMicrophoneCoordinates(audioProcessingOptionsHandle, hr);
        Contracts.throwIfFail(hr.getValue());
        return microphoneCoordinates;
    }

    /**
     * Returns the speaker reference channel position in the audio input.
     * @return Speaker reference channel position in the audio input.
     */
    public SpeakerReferenceChannel getSpeakerReferenceChannel() {
        IntRef intRef = new IntRef(0);
        Contracts.throwIfFail(getSpeakerReferenceChannel(audioProcessingOptionsHandle, intRef));
        return SpeakerReferenceChannel.values()[(int)intRef.getValue()];
    }

    /**
     * Explicitly frees any external resource attached to the object.
     */
    @Override
    public void close() {
        if (audioProcessingOptionsHandle != null) {
            audioProcessingOptionsHandle.close();
            audioProcessingOptionsHandle = null;
        }
    }

    AudioProcessingOptions(IntRef audioProcessingOptions) {
        Contracts.throwIfNull(audioProcessingOptions, "audioProcessingOptions");
        audioProcessingOptionsHandle = new SafeHandle(audioProcessingOptions.getValue(), SafeHandleType.AudioProcessingOptions);
        Contracts.throwIfFail(extractAssets(audioProcessingOptionsHandle, ContextHolder.getInstance().getContext()));
    }

    /*! \cond INTERNAL */

    /**
     * Returns the audio processing options implementation.
     * @return The implementation of the audio processing options.
     */
    public SafeHandle getImpl() {
        return audioProcessingOptionsHandle;
    }

    /*! \endcond */

    private SafeHandle audioProcessingOptionsHandle = null;

    private final static native long create(IntRef audioProcessingOptionsHandle, int audioProcessingFlags);
    private final static native long createFromPresetMicrophoneArrayGeometry(IntRef audioProcessingOptionsHandle, int audioProcessingFlags, int microphoneArrayGeometry, int speakerReferenceChannel);
    private final static native long createFromMicrophoneArrayGeometry(IntRef audioProcessingOptionsHandle, int audioProcessingFlags, MicrophoneArrayGeometry microphoneArrayGeometry, int speakerReferenceChannel);
    private final native long extractAssets(SafeHandle audioProcessingOptionsHandle, Context context);
    private final native long getAudioProcessingFlags(SafeHandle audioProcessingOptionsHandle, IntRef audioProcessingFlags);
    private final native long getPresetMicrophoneArrayGeometry(SafeHandle audioProcessingOptionsHandle, IntRef presetMicrophoneArrayGeometryRef);
    private final native long getMicrophoneArrayType(SafeHandle audioProcessingOptionsHandle, IntRef microphoneArrayTypeRef);
    private final native long getBeamformingStartAngle(SafeHandle audioProcessingOptionsHandle, IntRef beamformingStartAngle);
    private final native long getBeamformingEndAngle(SafeHandle audioProcessingOptionsHandle, IntRef beamformingEndAngle);
    private final native MicrophoneCoordinates[] getMicrophoneCoordinates(SafeHandle audioProcessingOptionsHandle, IntRef hr);
    private final native long getSpeakerReferenceChannel(SafeHandle audioProcessingOptionsHandle, IntRef speakerReferenceChannelRef);

}
