package com.birdsounds.identify.presentation

import com.theeasiestway.opus.Constants
import com.theeasiestway.opus.Opus

import android.Manifest
import android.content.Context

import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.media.audiofx.AutomaticGainControl
import android.media.audiofx.NoiseSuppressor
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresPermission
import encodePcmToAac
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.time.Instant

/**
 * A helper class to provide methods to record audio input from the MIC to the internal storage.
 */

import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import java.nio.ByteOrder

fun vibrateDouble(
    context: Context,
    firstDuration: Long = 100,
    pauseDuration: Long = 200,
    secondDuration: Long = 100
) {
    // Get the vibrator service
    val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        val vibratorManager =
            context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
        vibratorManager.defaultVibrator
    } else {
        @Suppress("DEPRECATION")
        context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
    }

    // Create a pattern for double vibration: 0ms delay, firstDuration vibrate, pauseDuration pause, secondDuration vibrate
    val vibrationPattern = longArrayOf(0, firstDuration, pauseDuration, secondDuration)

    // The -1 parameter means don't repeat the pattern
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, -1)
        vibrator.vibrate(vibrationEffect)
    } else {
        // Deprecated method for older API levels
        @Suppress("DEPRECATION")
        vibrator.vibrate(vibrationPattern, -1)
    }
}


fun vibrate(context: Context, duration: Long = 500) {
    val vibrator = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
        val vibratorManager =
            context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
        vibratorManager.defaultVibrator
    } else {
        @Suppress("DEPRECATION")
        context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
    }

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        val vibrationEffect = VibrationEffect.createOneShot(
            duration, // duration in milliseconds
            VibrationEffect.DEFAULT_AMPLITUDE // strength of vibration
        )
        vibrator.vibrate(vibrationEffect)
    } else {
        @Suppress("DEPRECATION")
        vibrator.vibrate(duration) // Vibration for 500ms
    }
}

@Suppress("DEPRECATION")
class SoundRecorder(
    context_in: Context,
    outputFileName: String
) {

    val codec_opus = Opus();
    private var state = State.IDLE
    private var context = context_in

    private enum class State {
        IDLE, RECORDING
    }


    @RequiresPermission(Manifest.permission.RECORD_AUDIO)
    suspend fun record() {


        suspendCancellableCoroutine<Unit> { cont ->
            var noiseSuppressor: NoiseSuppressor? = null
            var automaticGainControl: AutomaticGainControl? = null
            var chunk_index: Int = 0
            val audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION
            val sampleRateInHz = 48000
            val channelConfig = AudioFormat.CHANNEL_IN_MONO
            val audioFormat = AudioFormat.ENCODING_PCM_16BIT

            val frameSize = Constants.FrameSize._120();
            val chunkSize = frameSize.v * 2;  // Mono * 2 bytes per sample
            val seconds_per_message = 3
            val counts_until_reset = seconds_per_message * sampleRateInHz / frameSize.v;
            val bufferSize =
                AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);

            val audioRecord = AudioRecord(
                /* audioSource = */ audioSource,
                /* sampleRateInHz = */ sampleRateInHz,
                /* channelConfig = */ channelConfig,
                /* audioFormat = */ audioFormat,
                /* bufferSizeInBytes = */ bufferSize
            )


            val thread = Thread {
//                var sent_first: Boolean = false
                val outputByteStream = ByteBuffer.allocate(1000000)
                audioRecord.startRecording()
                var last_tstamp: Long = Instant.now().toEpochMilli();
                var count: Int = 0;
                var min_val = 1000000f
                var max_val = -1000000f
                while (true) /**/ {
                    if (Thread.interrupted()) {
                        audioRecord.release();
                        Log.w(TAG, "Finished thread")
                        break
                    }

                    if (count == 0) {
                        codec_opus.encoderInit(
                            Constants.SampleRate._48000(),
                            Constants.Channels.mono(),
                            Constants.Application.audio()
                        );
                        outputByteStream.clear();
                    }

                    val frame = ByteArray(chunkSize)
                    var offset = 0
                    var remained = frame.size
                    while (remained > 0) {
                        val read: Int = audioRecord.read(frame, offset, remained)
                        offset += read
                        remained -= read
                    }
                    val s_arry = ShortArray(frame.size / 2)
                    ByteBuffer.wrap(frame).order(
                        ByteOrder.LITTLE_ENDIAN
                    ).asShortBuffer().get(s_arry);
                    min_val = Math.min(s_arry.min().toFloat(), min_val)
                    max_val = Math.max(s_arry.max().toFloat(), max_val);


                    val encoded: ByteArray? =
                        codec_opus.encode(bytes = frame, frameSize = frameSize);
                    if (encoded != null) {
                        outputByteStream.put(encoded.size.toByte());
                        outputByteStream.put(encoded);
                    }

                    count += 1;
                    if (count == counts_until_reset) {
                        Log.d(TAG, "At count reset at ${count}");
                        codec_opus.encoderRelease();
                        count = 0;
                        val writtenBytes = outputByteStream.position()
                        Log.e(TAG, "Wrote ${writtenBytes} bytes!")
                        val duplicate = outputByteStream.duplicate()
                        duplicate.flip() // Prepare for reading
//                        outputByteStream.flip()
                        val bytes = ByteArray(writtenBytes)
                        duplicate.get(bytes)
//                        val result = ByteArray(outputByteStream.remaining());
                        var tstamp: Long = Instant.now().toEpochMilli()
                        val tstamp_buffer = ByteBuffer.allocate(Long.SIZE_BYTES)
                        val tstamp_bytes = tstamp_buffer.putLong(tstamp).array()
                        var byte_send: ByteArray = tstamp_bytes + bytes;
                        Log.e(
                            TAG,
                            "Sending message of 3s with size ${byte_send.size} + ${byte_send[0].toString()}"
                        )
                        MessageSender.sendMessage("/audio", byte_send, context)
                        Log.d(TAG, "${min_val} ${max_val} ")
                        min_val = 1000000f
                        max_val = -1000000f
                    }


                }

            }

            thread.start()
//            thread.join();

            cont.invokeOnCancellation {
                thread.interrupt()
                audioRecord.stop()
                audioRecord.release()
                state = State.IDLE
            }
            state = State.IDLE
        }
    }

    companion object {
        private const val TAG = "SoundRecorder"
    }
}
