package solutions.tretter.mindmachine.audio

import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.math.PI
import kotlin.math.sin

class BinauralAudioEngine {
    private var track: AudioTrack? = null
    private var job: Job? = null
    private var scope: CoroutineScope? = null

    @Volatile private var carrierHz: Float = 200f
    @Volatile private var differenceHz: Float = 6f

    fun setFrequencies(carrierHz: Float, differenceHz: Float) {
        this.carrierHz = carrierHz
        this.differenceHz = differenceHz
    }

    fun start(carrierHz: Float, differenceHz: Float) {
        stop()
        setFrequencies(carrierHz, differenceHz)
        val sampleRate = 44100
        val bufferSize = AudioTrack.getMinBufferSize(
            sampleRate,
            AudioFormat.CHANNEL_OUT_STEREO,
            AudioFormat.ENCODING_PCM_16BIT
        ).coerceAtLeast(4096)

        val audioTrack = AudioTrack(
            AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build(),
            AudioFormat.Builder().setEncoding(AudioFormat.ENCODING_PCM_16BIT).setSampleRate(sampleRate).setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build(),
            bufferSize,
            AudioTrack.MODE_STREAM,
            AudioManager.AUDIO_SESSION_ID_GENERATE
        )
        track = audioTrack
        val localScope = CoroutineScope(Dispatchers.Default)
        scope = localScope
        audioTrack.play()

        job = localScope.launch {
            val shorts = ShortArray(bufferSize)
            var phaseL = 0.0
            var phaseR = 0.0
            while (isActive) {
                // Read the latest modulation values once per buffer.
                val carrier = this@BinauralAudioEngine.carrierHz
                val diff = this@BinauralAudioEngine.differenceHz
                val leftHz = carrier - diff / 2f
                val rightHz = carrier + diff / 2f

                for (i in shorts.indices step 2) {
                    phaseL += 2 * PI * leftHz / sampleRate
                    phaseR += 2 * PI * rightHz / sampleRate
                    shorts[i] = (sin(phaseL) * Short.MAX_VALUE * 0.15).toInt().toShort()
                    shorts[i + 1] = (sin(phaseR) * Short.MAX_VALUE * 0.15).toInt().toShort()
                }
                audioTrack.write(shorts, 0, shorts.size)
            }
        }
    }

    fun stop() {
        job?.cancel()
        job = null
        scope?.cancel()
        scope = null
        track?.runCatching {
            pause()
            flush()
            stop()
            release()
        }
        track = null
    }
}
