package com.daya.live_teaching.audioManager;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.util.Log;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import cn.rongcloud.rtc.core.rongRTC.DevicesUtils;

/**
 * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
 */
public class AppRTCAudioManager {
    private static final String TAG = "AppRTCAudioManager";

    /**
     * AudioDevice is the names of possible audio devices that we currently
     * support.
     */
    // TODO(henrika): add support for BLUETOOTH as well.
    public enum AudioDevice {
        SPEAKER_PHONE,
        WIRED_HEADSET,
        EARPIECE,
    }

    private final Context      apprtcContext;
    private final Runnable     onStateChangeListener;
    private       boolean      initialized           = false;
    private       AudioManager audioManager;
    private       int          savedAudioMode        = AudioManager.MODE_INVALID;
    private       boolean      savedIsSpeakerPhoneOn = false;
    private       boolean      savedIsMicrophoneMute = false;

    private static boolean isCurrentSpeakerOn = true;

    // For now; always use the speaker phone as default device selection when
    // there is a choice between SPEAKER_PHONE and EARPIECE.
    // TODO(henrika): it is possible that EARPIECE should be preferred in some
    // cases. If so, we should set this value at construction instead.
    private final AudioDevice defaultAudioDevice = AudioDevice.SPEAKER_PHONE;

    // Proximity sensor object. It measures the proximity of an object in cm
    // relative to the view screen of a device and can therefore be used to
    // assist device switching (close to ear <=> use headset earpiece if
    // available, far from ear <=> use speaker phone).
    private AppRTCProximitySensor proximitySensor = null;

    // Contains the currently selected audio device.
    private AudioDevice selectedAudioDevice;

    // Contains a list of available audio devices. A Set collection is used to
    // avoid duplicate elements.
    private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();

    // Broadcast receiver for wired headset intent broadcasts.
    private BroadcastReceiver wiredHeadsetReceiver;

    // This method is called when the proximity sensor reports a state change,
    // e.g. from "NEAR to FAR" or from "FAR to NEAR".
    private void onProximitySensorChangedState() {
        // The proximity sensor should only be activated when there are exactly two
        // available audio devices.
        if (audioDevices.size() == 2
                && audioDevices.contains(AudioDevice.EARPIECE)
                && audioDevices.contains(
                AudioDevice.SPEAKER_PHONE)) {
            if (proximitySensor.sensorReportsNearState()) {
                // Sensor reports that a "handset is being held up to a person's ear",
                // or "something is covering the light sensor".
                setAudioDevice(AudioDevice.EARPIECE);
            } else {
                // Sensor reports that a "handset is removed from a person's ear", or
                // "the light sensor is no longer covered".
                setAudioDevice(AudioDevice.SPEAKER_PHONE);
            }
        }
    }

    /**
     * Construction
     */
    public static AppRTCAudioManager create(Context context,
                                            Runnable deviceStateChangeListener) {
        return new AppRTCAudioManager(context, deviceStateChangeListener);
    }

    private AppRTCAudioManager(Context context,
                               Runnable deviceStateChangeListener) {
        apprtcContext = context;
        onStateChangeListener = deviceStateChangeListener;
        audioManager = ((AudioManager) context.getSystemService(
                Context.AUDIO_SERVICE));

        // Create and initialize the proximity sensor.
        // Tablet devices (e.g. Nexus 7) does not support proximity sensors.
        // Note that, the sensor will not be active until start() has been called.
        proximitySensor = AppRTCProximitySensor.create(context, new Runnable() {
            // This method will be called each time a state change is detected.
            // Example: user holds his hand over the device (closer than ~5 cm),
            // or removes his hand from the device.
            public void run() {
                //          if (isCurrentSpeakerOn && !BluetoothUtil.hasBluetoothA2dpConnected())
                //            onProximitySensorChangedState();
            }
        });
        AppRTCUtils.logDeviceInfo(TAG);
    }

    public void init() {
        Log.d(TAG, "init");
        if (initialized) {
            return;
        }

        // Store current audio state so we can restore it when close() is called.
        savedAudioMode = audioManager.getMode();
        savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
        savedIsMicrophoneMute = audioManager.isMicrophoneMute();

        // Request audio focus before making any device switch.
        audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

        // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
        // required to be in this mode when playout and/or recording starts for
        // best possible VoIP performance.
        // TODO(henrika): we migh want to start with RINGTONE mode here instead.
        int mode = DevicesUtils.getAudioMode() == AudioManager.STREAM_MUSIC ? AudioManager.MODE_NORMAL
                : AudioManager.MODE_IN_COMMUNICATION;
        Log.d(TAG, "setMode =" + mode);
        audioManager.setMode(AudioManager.MODE_NORMAL);

        // Always disable microphone mute during a WebRTC call.
        setMicrophoneMute(false);

        // Do initial selection of audio device. This setting can later be changed
        // either by adding/removing a wired headset or by covering/uncovering the
        // proximity sensor.
        //修改在app启动前耳机已经插上，打开app时还是通过speaker播放的问题。
        //wiredHeadsetReceiver可以收到启动app前已经插上耳机的Intent
        //if (!BluetoothUtil.hasBluetoothA2dpConnected())
        //  updateAudioDeviceState(hasWiredHeadset());

        // Register receiver for broadcast intents related to adding/removing a
        // wired headset (Intent.ACTION_HEADSET_PLUG).
        registerForWiredHeadsetIntentBroadcast();

        initialized = true;
    }

    public void close() {
        Log.d(TAG, "close");
        if (!initialized) {
            return;
        }

        unregisterForWiredHeadsetIntentBroadcast();

        // Restore previously stored audio states.
        setSpeakerphoneOn(savedIsSpeakerPhoneOn);
        setMicrophoneMute(savedIsMicrophoneMute);
        audioManager.setMode(savedAudioMode);
        audioManager.abandonAudioFocus(null);
        if (BluetoothUtil.hasBluetoothA2dpConnected()) {
            audioManager.stopBluetoothSco();
            audioManager.setBluetoothScoOn(false);
        }

        if (proximitySensor != null) {
            proximitySensor.stop();
            proximitySensor = null;
        }

        initialized = false;
    }

    /**
     * Changes selection of the currently active audio device.
     */
    public void setAudioDevice(AudioDevice device) {
        Log.d(TAG, "setAudioDevice(device=" + device + ")");
        AppRTCUtils.assertIsTrue(audioDevices.contains(device));

        switch (device) {
            case SPEAKER_PHONE:
                setSpeakerphoneOn(true);
                selectedAudioDevice = AudioDevice.SPEAKER_PHONE;
                break;
            case EARPIECE:
                setSpeakerphoneOn(false);
                selectedAudioDevice = AudioDevice.EARPIECE;
                break;
            case WIRED_HEADSET:
                setSpeakerphoneOn(false);
                selectedAudioDevice = AudioDevice.WIRED_HEADSET;
                break;
            default:
                Log.e(TAG, "Invalid audio device selection");
                break;
        }
        onAudioManagerChangedState();
    }

    /**
     * Returns current set of available/selectable audio devices.
     */
    public Set<AudioDevice> getAudioDevices() {
        return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices));
    }

    /**
     * Returns the currently selected audio device.
     */
    public AudioDevice getSelectedAudioDevice() {
        return selectedAudioDevice;
    }

    /**
     * Registers receiver for the broadcasted intent when a wired headset is
     * plugged in or unplugged. The received intent will have an extra
     * 'state' value where 0 means unplugged, and 1 means plugged.
     */
    private void registerForWiredHeadsetIntentBroadcast() {
        IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);

        /** Receiver which handles changes in wired headset availability. */
        wiredHeadsetReceiver = new BroadcastReceiver() {
            private static final int STATE_UNPLUGGED = 0;
            private static final int STATE_PLUGGED = 1;
            private static final int HAS_NO_MIC = 0;
            private static final int HAS_MIC = 1;

            @Override
            public void onReceive(Context context, Intent intent) {
                if (BluetoothUtil.hasBluetoothA2dpConnected())
                    return;
                int state = intent.getIntExtra("state", STATE_UNPLUGGED);
                int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
                String name = intent.getStringExtra("name");
                Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo()
                        + ": "
                        + "a=" + intent.getAction()
                        + ", s=" + (state == STATE_UNPLUGGED ? "unplugged" : "plugged")
                        + ", m=" + (microphone == HAS_MIC ? "mic" : "no mic")
                        + ", n=" + name
                        + ", sb=" + isInitialStickyBroadcast());

                boolean hasWiredHeadset = (state == STATE_PLUGGED) ? true : false;
                switch (state) {
                    case STATE_UNPLUGGED:
                        updateAudioDeviceState(hasWiredHeadset);
                        audioManager.setSpeakerphoneOn(isCurrentSpeakerOn);
                        break;
                    case STATE_PLUGGED:
                        if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) {
                            updateAudioDeviceState(hasWiredHeadset);
                        }
                        break;
                    default:
                        Log.e(TAG, "Invalid state");
                        break;
                }
            }
        };
        apprtcContext.registerReceiver(wiredHeadsetReceiver, filter);
    }

    /**
     * Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent.
     */
    private void unregisterForWiredHeadsetIntentBroadcast() {
        apprtcContext.unregisterReceiver(wiredHeadsetReceiver);
        wiredHeadsetReceiver = null;
    }

    /**
     * Sets the speaker phone mode.
     */
    private void setSpeakerphoneOn(boolean on) {
        boolean wasOn = audioManager.isSpeakerphoneOn();
        if (wasOn == on) {
            return;
        }

        audioManager.setSpeakerphoneOn(on);
    }

    public void onToggleSpeaker(boolean mute) {
        isCurrentSpeakerOn = !mute;
        if (selectedAudioDevice == AudioDevice.WIRED_HEADSET) {
            return;
        }
        setSpeakerphoneOn(!mute);
        //    setMicrophoneMute(mute);
    }

    /**
     * Sets the microphone mute state.
     */
    private void setMicrophoneMute(boolean on) {
        boolean wasMuted = audioManager.isMicrophoneMute();
        if (wasMuted == on) {
            return;
        }
        audioManager.setMicrophoneMute(on);
    }

    /**
     * Gets the current earpiece state.
     */
    private boolean hasEarpiece() {
        return apprtcContext.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_TELEPHONY);
    }

    /**
     * Checks whether a wired headset is connected or not.
     * This is not a valid indication that audio playback is actually over
     * the wired headset as audio routing depends on other conditions. We
     * only use it as an early indicator (during initialization) of an attached
     * wired headset.
     */
    @Deprecated
    private boolean hasWiredHeadset() {
        return audioManager.isWiredHeadsetOn();
    }

    public AudioManager getAudioManager() {
        return audioManager;
    }

    /**
     * Update list of possible audio devices and make new device selection.
     */
    private void updateAudioDeviceState(boolean hasWiredHeadset) {
        // Update the list of available audio devices.
        audioDevices.clear();
        if (hasWiredHeadset) {
            // If a wired headset is connected, then it is the only possible option.
            audioDevices.add(AudioDevice.WIRED_HEADSET);
        } else {
            // No wired headset, hence the audio-device list can contain speaker
            // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
            audioDevices.add(AudioDevice.SPEAKER_PHONE);
            if (hasEarpiece()) {
                audioDevices.add(AudioDevice.EARPIECE);
            }
        }
        Log.d(TAG, "audioDevices: " + audioDevices);

        // Switch to correct audio device given the list of available audio devices.
        if (hasWiredHeadset) {
            setAudioDevice(AudioDevice.WIRED_HEADSET);
        } else {
            setAudioDevice(defaultAudioDevice);
        }
    }

    /**
     * Called each time a new audio device has been added or removed.
     */
    private void onAudioManagerChangedState() {
        Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices
                + ", selected=" + selectedAudioDevice);

        // Enable the proximity sensor if there are two available audio devices
        // in the list. Given the current implementation, we know that the choice
        // will then be between EARPIECE and SPEAKER_PHONE.
        if (audioDevices.size() == 2) {
            AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE)
                    && audioDevices.contains(AudioDevice.SPEAKER_PHONE));
            // Start the proximity sensor.
            proximitySensor.start();
        } else if (audioDevices.size() == 1) {
            // Stop the proximity sensor since it is no longer needed.
            proximitySensor.stop();
        } else {
            Log.e(TAG, "Invalid device list");
        }

        if (onStateChangeListener != null) {
            // Run callback to notify a listening client. The client can then
            // use public getters to query the new state.
            onStateChangeListener.run();
        }
    }
}
