package com.example.call

import android.content.Context
import android.media.*
import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.nio.ByteBuffer
import java.util.concurrent.atomic.AtomicBoolean

/**
 * AudioEngine — Android native layer for low-latency audio I/O.
 *
 * MethodChannel  "com.example.call/audio_control"
 *   startCapture(sampleRate: Int, source: Int) → "ok"
 *   stopCapture()  → "ok"
 *   startPlayback(sampleRate: Int) → "ok"
 *   stopPlayback() → "ok"
 *   writePlayback(samples: ByteArray) → "ok"   (Int16 LE samples)
 *   setSpeakerMode(enabled: Boolean) → "ok"
 *   getAudioRouteInfo() → Map<String,Any>
 *
 * EventChannel   "com.example.call/audio_capture"
 *   Streams ByteArray (Int16 LE) chunks captured from AudioRecord.
 *   Each event is exactly CAPTURE_CHUNK_SAMPLES × 2 bytes.
 */
object AudioEngine {

    private const val TAG = "AudioEngine"

    // ── Channel names ────────────────────────────────────────────────
    const val METHOD_CHANNEL = "com.example.call/audio_control"
    const val EVENT_CHANNEL  = "com.example.call/audio_capture"

    // ── Audio parameters ─────────────────────────────────────────────
    // Matches the modem sample rate; must be 8000 Hz for cellular compatibility.
    private const val SAMPLE_RATE = 8000

    // Number of Int16 samples delivered per EventChannel event.
    // 160 samples = 20 ms at 8 kHz → one LPC subframe.
    private const val CAPTURE_CHUNK_SAMPLES = 160

    // Internal AudioRecord buffer: 4× chunk size to reduce overruns.
    private const val RECORD_BUFFER_SAMPLES = CAPTURE_CHUNK_SAMPLES * 4

    // Internal AudioTrack buffer: 80 ms headroom.
    private const val PLAYBACK_BUFFER_SAMPLES = SAMPLE_RATE / 1000 * 80

    // ── State ────────────────────────────────────────────────────────
    private var audioRecord: AudioRecord? = null
    private var audioTrack: AudioTrack? = null
    private var captureThread: Thread? = null
    private val capturing = AtomicBoolean(false)
    private val playing   = AtomicBoolean(false)

    private var eventSink: EventChannel.EventSink? = null
    private val mainHandler = Handler(Looper.getMainLooper())

    private lateinit var appContext: Context

    // ── Registration ─────────────────────────────────────────────────
    fun register(context: Context, messenger: BinaryMessenger) {
        appContext = context.applicationContext

        MethodChannel(messenger, METHOD_CHANNEL).setMethodCallHandler { call, result ->
            handleMethod(call, result)
        }
        EventChannel(messenger, EVENT_CHANNEL).setStreamHandler(object : EventChannel.StreamHandler {
            override fun onListen(args: Any?, sink: EventChannel.EventSink?) {
                eventSink = sink
            }
            override fun onCancel(args: Any?) {
                eventSink = null
            }
        })
    }

    // ── Method handler ───────────────────────────────────────────────
    private fun handleMethod(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "startCapture" -> {
                val rate   = call.argument<Int>("sampleRate") ?: SAMPLE_RATE
                val source = call.argument<Int>("source")     ?: MediaRecorder.AudioSource.UNPROCESSED
                startCapture(rate, source)
                result.success("ok")
            }
            "stopCapture" -> {
                stopCapture()
                result.success("ok")
            }
            "startPlayback" -> {
                val rate = call.argument<Int>("sampleRate") ?: SAMPLE_RATE
                startPlayback(rate)
                result.success("ok")
            }
            "stopPlayback" -> {
                stopPlayback()
                result.success("ok")
            }
            "writePlayback" -> {
                val bytes = call.argument<ByteArray>("samples")
                if (bytes != null) writePlayback(bytes)
                result.success("ok")
            }
            "setSpeakerMode" -> {
                val enabled = call.argument<Boolean>("enabled") ?: true
                setSpeakerMode(enabled)
                result.success("ok")
            }
            "getAudioRouteInfo" -> {
                result.success(getAudioRouteInfo())
            }
            else -> result.notImplemented()
        }
    }

    // ── Capture ──────────────────────────────────────────────────────

    /**
     * Start capturing audio from the microphone.
     *
     * Source priority (best for FSK demodulation):
     *  1. UNPROCESSED (API 24+) — raw mic, no AEC/NS
     *  2. VOICE_COMMUNICATION   — VoIP-tuned processing
     *  3. MIC                   — standard mic
     *
     * @param sampleRate Hz
     * @param preferredSource MediaRecorder.AudioSource constant
     */
    private fun startCapture(sampleRate: Int, preferredSource: Int) {
        if (capturing.get()) {
            Log.w(TAG, "startCapture called while already capturing")
            return
        }

        val sources = listOf(
            preferredSource,
            MediaRecorder.AudioSource.UNPROCESSED,
            MediaRecorder.AudioSource.VOICE_COMMUNICATION,
            MediaRecorder.AudioSource.MIC
        ).distinct()

        val bufferBytes = RECORD_BUFFER_SAMPLES * 2          // Int16 → 2 bytes each
        val format = AudioFormat.Builder()
            .setSampleRate(sampleRate)
            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
            .build()

        var record: AudioRecord? = null
        for (src in sources) {
            try {
                val rec = AudioRecord.Builder()
                    .setAudioSource(src)
                    .setAudioFormat(format)
                    .setBufferSizeInBytes(bufferBytes)
                    .build()
                if (rec.state == AudioRecord.STATE_INITIALIZED) {
                    record = rec
                    Log.i(TAG, "AudioRecord opened with source=$src")
                    break
                } else {
                    rec.release()
                }
            } catch (e: Exception) {
                Log.w(TAG, "Source $src failed: ${e.message}")
            }
        }

        if (record == null) {
            Log.e(TAG, "Could not open AudioRecord with any source")
            return
        }

        audioRecord = record
        capturing.set(true)

        captureThread = Thread({
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO)
            val chunk = ShortArray(CAPTURE_CHUNK_SAMPLES)
            record.startRecording()
            while (capturing.get()) {
                val read = record.read(chunk, 0, chunk.size)
                if (read > 0) {
                    // Convert ShortArray → ByteArray (Int16 LE) for Dart
                    val bytes = ByteArray(read * 2)
                    val buf = ByteBuffer.wrap(bytes).order(java.nio.ByteOrder.LITTLE_ENDIAN)
                    for (i in 0 until read) buf.putShort(chunk[i])
                    // Dispatch to Dart via EventChannel on main thread
                    val payload = bytes.copyOf()
                    mainHandler.post { eventSink?.success(payload) }
                }
            }
            record.stop()
            record.release()
        }, "AudioCapture")
        captureThread!!.start()
    }

    private fun stopCapture() {
        capturing.set(false)
        captureThread?.join(500)
        captureThread = null
        audioRecord = null
    }

    // ── Playback ─────────────────────────────────────────────────────

    /**
     * Start the AudioTrack in streaming mode.
     * Dart pushes PCM chunks via writePlayback().
     */
    private fun startPlayback(sampleRate: Int) {
        if (playing.get()) return

        val bufBytes = PLAYBACK_BUFFER_SAMPLES * 2
        val track = AudioTrack.Builder()
            .setAudioAttributes(
                AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                    .build()
            )
            .setAudioFormat(
                AudioFormat.Builder()
                    .setSampleRate(sampleRate)
                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                    .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                    .build()
            )
            .setBufferSizeInBytes(bufBytes)
            .setTransferMode(AudioTrack.MODE_STREAM)
            .build()

        track.play()
        audioTrack = track
        playing.set(true)
        Log.i(TAG, "AudioTrack started, sampleRate=$sampleRate")
    }

    /**
     * Write Int16 LE PCM bytes to the AudioTrack buffer.
     * Called from Dart whenever the FSK modulator or LPC decoder produces output.
     */
    private fun writePlayback(bytes: ByteArray) {
        val track = audioTrack ?: return
        if (!playing.get()) return
        // Write non-blocking; discard if buffer full (transient)
        track.write(bytes, 0, bytes.size, AudioTrack.WRITE_NON_BLOCKING)
    }

    private fun stopPlayback() {
        playing.set(false)
        audioTrack?.stop()
        audioTrack?.release()
        audioTrack = null
    }

    // ── Routing ──────────────────────────────────────────────────────

    /**
     * Switch between speakerphone (loudspeaker mode) and earpiece.
     *
     * FSK acoustic coupling requires SPEAKER mode so the phone's bottom mic
     * can pick up audio coming from the speaker. When receiving decoded voice,
     * you may optionally switch to earpiece for privacy.
     */
    private fun setSpeakerMode(enabled: Boolean) {
        val am = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
        am.mode = AudioManager.MODE_IN_COMMUNICATION
        am.isSpeakerphoneOn = enabled
        Log.i(TAG, "Speaker mode: $enabled")
    }

    private fun getAudioRouteInfo(): Map<String, Any> {
        val am = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
        return mapOf(
            "speakerOn"  to am.isSpeakerphoneOn,
            "mode"       to am.mode,
            "sampleRate" to SAMPLE_RATE,
            "chunkSize"  to CAPTURE_CHUNK_SAMPLES
        )
    }
}
