package com.hmg.hmgDr.util.audio

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord
import android.media.AudioTrack
import android.media.MediaRecorder.AudioSource
import android.os.Process
import android.util.Log

import com.opentok.android.BaseAudioDevice

import java.nio.ByteBuffer
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock

class CustomAudioDevice(context: Context) : BaseAudioDevice() {

    private val m_context: Context = context
    private var m_audioTrack: AudioTrack? = null
    private var m_audioRecord: AudioRecord? = null

    // Capture & render buffers
    private var m_playBuffer: ByteBuffer? = null
    private var m_recBuffer: ByteBuffer? = null
    private val m_tempBufPlay: ByteArray
    private val m_tempBufRec: ByteArray
    private val m_rendererLock: ReentrantLock = ReentrantLock(true)
    private val m_renderEvent: Condition = m_rendererLock.newCondition()

    @Volatile
    private var m_isRendering = false

    @Volatile
    private var m_shutdownRenderThread = false
    private val m_captureLock: ReentrantLock = ReentrantLock(true)
    private val m_captureEvent: Condition = m_captureLock.newCondition()

    @Volatile
    private var m_isCapturing = false

    @Volatile
    private var m_shutdownCaptureThread = false
    private val m_captureSettings: AudioSettings
    private val m_rendererSettings: AudioSettings

    // Capturing delay estimation
    private var m_estimatedCaptureDelay = 0

    // Rendering delay estimation
    private var m_bufferedPlaySamples = 0
    private var m_playPosition = 0
    private var m_estimatedRenderDelay = 0
    private val m_audioManager: AudioManager
    private var isRendererMuted = false

    companion object {
        private const val LOG_TAG = "opentok-defaultaudio"
        private const val SAMPLING_RATE = 44100
        private const val NUM_CHANNELS_CAPTURING = 1
        private const val NUM_CHANNELS_RENDERING = 1
        private const val MAX_SAMPLES = 2 * 480 * 2 // Max 10 ms @ 48 kHz
    }

    init {
        try {
            m_playBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
            m_recBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
        } catch (e: Exception) {
            Log.e(LOG_TAG, "${e.message}.")
        }
        m_tempBufPlay = ByteArray(MAX_SAMPLES)
        m_tempBufRec = ByteArray(MAX_SAMPLES)
        m_captureSettings = AudioSettings(
            SAMPLING_RATE,
            NUM_CHANNELS_CAPTURING
        )
        m_rendererSettings = AudioSettings(
            SAMPLING_RATE,
            NUM_CHANNELS_RENDERING
        )
        m_audioManager = m_context
            .getSystemService(Context.AUDIO_SERVICE) as AudioManager
        m_audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
    }

    override fun initCapturer(): Boolean {

        // get the minimum buffer size that can be used
        val minRecBufSize: Int = AudioRecord.getMinBufferSize(
            m_captureSettings
                .sampleRate,
            if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
            AudioFormat.ENCODING_PCM_16BIT
        )

        // double size to be more safe
        val recBufSize = minRecBufSize * 2

        // release the object
        if (m_audioRecord != null) {
            m_audioRecord!!.release()
            m_audioRecord = null
        }
        try {
            m_audioRecord = AudioRecord(
                AudioSource.VOICE_COMMUNICATION,
                m_captureSettings.sampleRate,
                if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
                AudioFormat.ENCODING_PCM_16BIT, recBufSize
            )
        } catch (e: Exception) {
            Log.e(LOG_TAG, "${e.message}.")
            return false
        }

        // check that the audioRecord is ready to be used
        if (m_audioRecord!!.state != AudioRecord.STATE_INITIALIZED) {
            Log.i(
                LOG_TAG, "Audio capture is not initialized "
                        + m_captureSettings.sampleRate
            )
            return false
        }
        m_shutdownCaptureThread = false
        Thread(m_captureThread).start()
        return true
    }

    override fun destroyCapturer(): Boolean {
        m_captureLock.lock()
        // release the object
        m_audioRecord?.release()
        m_audioRecord = null
        m_shutdownCaptureThread = true
        m_captureEvent.signal()
        m_captureLock.unlock()
        return true
    }

    override fun getEstimatedCaptureDelay(): Int {
        return m_estimatedCaptureDelay
    }

    override fun startCapturer(): Boolean {
        // start recording
        try {
            m_audioRecord!!.startRecording()
        } catch (e: IllegalStateException) {
            e.printStackTrace()
            return false
        }
        m_captureLock.lock()
        m_isCapturing = true
        m_captureEvent.signal()
        m_captureLock.unlock()
        return true
    }

    override fun stopCapturer(): Boolean {
        m_captureLock.lock()
        try {
            // only stop if we are recording
            if (m_audioRecord!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
                // stop recording
                try {
                    m_audioRecord!!.stop()
                } catch (e: IllegalStateException) {
                    e.printStackTrace()
                    return false
                }
            }
        } finally {
            // Ensure we always unlock
            m_isCapturing = false
            m_captureLock.unlock()
        }
        return true
    }

    private val m_captureThread = Runnable {
        val samplesToRec = SAMPLING_RATE / 100
        var samplesRead = 0
        try {
            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        while (!m_shutdownCaptureThread) {
            m_captureLock.lock()
            samplesRead = try {
                if (!m_isCapturing) {
                    m_captureEvent.await()
                    continue
                } else {
                    if (m_audioRecord == null) {
                        continue
                    }
                    val lengthInBytes = ((samplesToRec shl 1)
                            * NUM_CHANNELS_CAPTURING)
                    val readBytes: Int = m_audioRecord!!.read(
                        m_tempBufRec, 0,
                        lengthInBytes
                    )
                    m_recBuffer!!.rewind()
                    m_recBuffer!!.put(m_tempBufRec)
                    (readBytes shr 1) / NUM_CHANNELS_CAPTURING
                }
            } catch (e: Exception) {
                Log.e(LOG_TAG, "RecordAudio try failed: " + e.message)
                continue
            } finally {
                // Ensure we always unlock
                m_captureLock.unlock()
            }
            audioBus.writeCaptureData(m_recBuffer, samplesRead)
            m_estimatedCaptureDelay = samplesRead * 1000 / SAMPLING_RATE
        }
    }

    override fun initRenderer(): Boolean {

        // get the minimum buffer size that can be used
        val minPlayBufSize: Int = AudioTrack.getMinBufferSize(
            m_rendererSettings
                .sampleRate,
            if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
            AudioFormat.ENCODING_PCM_16BIT
        )
        var playBufSize = minPlayBufSize
        if (playBufSize < 6000) {
            playBufSize *= 2
        }

        // release the object
        if (m_audioTrack != null) {
            m_audioTrack!!.release()
            m_audioTrack = null
        }
        try {
            m_audioTrack = AudioTrack(
                AudioManager.STREAM_VOICE_CALL,
                m_rendererSettings.sampleRate,
                if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_16BIT, playBufSize,
                AudioTrack.MODE_STREAM
            )
        } catch (e: Exception) {
            Log.e(LOG_TAG, "${e.message}.")
            return false
        }

        // check that the audioRecord is ready to be used
        if (m_audioTrack!!.state != AudioTrack.STATE_INITIALIZED) {
            Log.i(
                LOG_TAG, "Audio renderer not initialized "
                        + m_rendererSettings.sampleRate
            )
            return false
        }
        m_bufferedPlaySamples = 0
        outputMode = OutputMode.SpeakerPhone
        m_shutdownRenderThread = false
        Thread(m_renderThread).start()
        return true
    }

    override fun destroyRenderer(): Boolean {
        m_rendererLock.lock()
        // release the object
        m_audioTrack!!.release()
        m_audioTrack = null
        m_shutdownRenderThread = true
        m_renderEvent.signal()
        m_rendererLock.unlock()
        unregisterHeadsetReceiver()
        m_audioManager.isSpeakerphoneOn = false
        m_audioManager.mode = AudioManager.MODE_NORMAL
        return true
    }

    override fun getEstimatedRenderDelay(): Int {
        return m_estimatedRenderDelay
    }

    override fun startRenderer(): Boolean {
        // start playout
        try {
            m_audioTrack!!.play()
        } catch (e: IllegalStateException) {
            e.printStackTrace()
            return false
        }
        m_rendererLock.lock()
        m_isRendering = true
        m_renderEvent.signal()
        m_rendererLock.unlock()
        return true
    }

    override fun stopRenderer(): Boolean {
        m_rendererLock.lock()
        try {
            // only stop if we are playing
            if (m_audioTrack!!.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                // stop playout
                try {
                    m_audioTrack!!.stop()
                } catch (e: IllegalStateException) {
                    e.printStackTrace()
                    return false
                }

                // flush the buffers
                m_audioTrack!!.flush()
            }
        } finally {
            // Ensure we always unlock, both for success, exception or error
            // return.
            m_isRendering = false
            m_rendererLock.unlock()
        }
        return true
    }

    private val m_renderThread = Runnable {
        val samplesToPlay = SAMPLING_RATE / 100
        try {
            Process
                .setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        while (!m_shutdownRenderThread) {
            m_rendererLock.lock()
            try {
                if (!m_isRendering) {
                    m_renderEvent.await()
                    continue
                } else {
                    m_rendererLock.unlock()

                    // Don't lock on audioBus calls
                    m_playBuffer!!.clear()
                    val samplesRead: Int = audioBus.readRenderData(
                        m_playBuffer, samplesToPlay
                    )

                    // Log.d(LOG_TAG, "Samples read: " + samplesRead);
                    m_rendererLock.lock()
                    if (!isRendererMuted) {
                        // After acquiring the lock again
                        // we must check if we are still playing
                        if (m_audioTrack == null
                            || !m_isRendering
                        ) {
                            continue
                        }
                        val bytesRead = ((samplesRead shl 1)
                                * NUM_CHANNELS_RENDERING)
                        m_playBuffer!!.get(m_tempBufPlay, 0, bytesRead)
                        val bytesWritten: Int = m_audioTrack!!.write(
                            m_tempBufPlay, 0,
                            bytesRead
                        )

                        // increase by number of written samples
                        m_bufferedPlaySamples += ((bytesWritten shr 1)
                                / NUM_CHANNELS_RENDERING)

                        // decrease by number of played samples
                        val pos: Int = m_audioTrack!!.getPlaybackHeadPosition()
                        if (pos < m_playPosition) {
                            // wrap or reset by driver
                            m_playPosition = 0
                        }
                        m_bufferedPlaySamples -= pos - m_playPosition
                        m_playPosition = pos

                        // we calculate the estimated delay based on the
                        // buffered samples
                        m_estimatedRenderDelay = (m_bufferedPlaySamples * 1000
                                / SAMPLING_RATE)
                    }
                }
            } catch (e: Exception) {
                Log.e(LOG_TAG, "Exception: " + e.message)
                e.printStackTrace()
            } finally {
                m_rendererLock.unlock()
            }
        }
    }

    override fun getCaptureSettings(): AudioSettings {
        return m_captureSettings
    }

    override fun getRenderSettings(): AudioSettings {
        return m_rendererSettings
    }

    /**
     * Communication modes handling
     */
    override fun setOutputMode(mode: OutputMode): Boolean {
        super.setOutputMode(mode)
        if (mode == OutputMode.Handset) {
            unregisterHeadsetReceiver()
            m_audioManager.isSpeakerphoneOn = false
        } else {
            m_audioManager.isSpeakerphoneOn = true
            registerHeadsetReceiver()
        }
        return true
    }

    private val m_headsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            if (intent.action!!.compareTo(Intent.ACTION_HEADSET_PLUG) == 0) {
                val state: Int = intent.getIntExtra("state", 0)
                m_audioManager.isSpeakerphoneOn = state == 0
            }
        }
    }
    private var m_receiverRegistered = false
    private fun registerHeadsetReceiver() {
        if (!m_receiverRegistered) {
            val receiverFilter = IntentFilter(
                Intent.ACTION_HEADSET_PLUG
            )
            m_context.registerReceiver(m_headsetReceiver, receiverFilter)
            m_receiverRegistered = true
        }
    }

    private fun unregisterHeadsetReceiver() {
        if (m_receiverRegistered) {
            try {
                m_context.unregisterReceiver(m_headsetReceiver)
            } catch (e: IllegalArgumentException) {
                e.printStackTrace()
            }
            m_receiverRegistered = false
        }
    }

    override fun onPause() {
        if (outputMode == OutputMode.SpeakerPhone) {
            unregisterHeadsetReceiver()
        }
    }

    override fun onResume() {
        if (outputMode == OutputMode.SpeakerPhone) {
            registerHeadsetReceiver()
        }
    }

    fun setRendererMute(isRendererMuted: Boolean) {
        this.isRendererMuted = isRendererMuted
    }
}
