package com.tutpro.baresip.plus

import android.os.Build
import android.os.Bundle
import android.util.TypedValue
import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updatePadding
import com.tutpro.baresip.plus.databinding.ActivityAudioBinding

class AudioActivity : AppCompatActivity() {

    private lateinit var binding: ActivityAudioBinding
    private lateinit var micGain: EditText
    private lateinit var opusBitRate: EditText
    private lateinit var opusPacketLoss: EditText
    private lateinit var aec: CheckBox
    private lateinit var speakerPhone: CheckBox
    private lateinit var audioDelay: EditText

    private var save = false
    private var restart = false
    private var callVolume = BaresipService.callVolume
    private var oldMicGain = ""
    private var oldAudioModules = mutableMapOf<String, Boolean>()
    private var oldOpusBitrate = ""
    private var oldOpusPacketLoss = ""
    private var oldAec = false
    private var toneCountry = BaresipService.toneCountry

    private val onBackPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            goBack()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        binding = ActivityAudioBinding.inflate(layoutInflater)
        setContentView(binding.root)

        ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v: View, insets: WindowInsetsCompat ->
            val systemBars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            if (Build.VERSION.SDK_INT >= 35)
                binding.AudioView.updatePadding(top = systemBars.top + 56)
            WindowInsetsCompat.CONSUMED
        }

        if (!Utils.isDarkTheme(this))
            WindowInsetsControllerCompat(window, binding.root).isAppearanceLightStatusBars = true

        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        Utils.addActivity("audio")

        val callVolSpinner = binding.VolumeSpinner
        val volKeys = arrayListOf("None", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
        val volVals = arrayListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        val curVal = callVolume
        val curKey = volKeys[curVal]
        volKeys.removeAt(curVal)
        volVals.removeAt(curVal)
        volKeys.add(0, curKey)
        volVals.add(0, curVal)
        val callVolAdapter = ArrayAdapter(
            this, android.R.layout.simple_spinner_item,
            volKeys
        )
        callVolAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        callVolSpinner.adapter = callVolAdapter
        callVolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(
                    parent: AdapterView<*>,
                    view: View?,
                    position: Int,
                    id: Long
            ) {
                callVolume = volVals[volKeys.indexOf(parent.selectedItem.toString())]
            }

            override fun onNothingSelected(parent: AdapterView<*>) {
            }
        }

        if (!BaresipService.agcAvailable) {
            micGain = binding.MicGain
            oldMicGain = Config.variable("augain")
            micGain.setText(oldMicGain)
        } else {
            binding.MicGainLayout.visibility = View.GONE
        }

        speakerPhone = binding.SpeakerPhone
        speakerPhone.isChecked = BaresipService.speakerPhone

        val modules = Config.variables("module")

        val audioModulesList = binding.AudioModulesList
        var id = 1000
        for (module in audioModules) {
            val rl = RelativeLayout(this)
            val rlParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                    RelativeLayout.LayoutParams.WRAP_CONTENT)
            rlParams.marginStart = 16
            rl.layoutParams = rlParams
            val tv = TextView(this)
            tv.id = id++
            val tvParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                    RelativeLayout.LayoutParams.WRAP_CONTENT)
            tvParams.addRule(RelativeLayout.ALIGN_PARENT_START)
            tvParams.addRule(RelativeLayout.CENTER_VERTICAL)
            tvParams.addRule(RelativeLayout.START_OF, id)
            tv.layoutParams = tvParams
            tv.text = String.format(getString(R.string.bullet_item), module)
            tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
            tv.setTextColor(ContextCompat.getColor(this, R.color.colorItemText))
            rl.addView(tv)
            val cb = CheckBox(this)
            cb.id = id++
            val cbParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                    RelativeLayout.LayoutParams.WRAP_CONTENT)
            cbParams.addRule(RelativeLayout.ALIGN_PARENT_END)
            cbParams.addRule(RelativeLayout.CENTER_VERTICAL)
            cb.layoutParams = cbParams
            cb.gravity = Gravity.END
            cb.isChecked = modules.contains("${module}.so")
            oldAudioModules[module] = cb.isChecked
            rl.addView(cb)
            audioModulesList.addView(rl)
        }

        opusBitRate = binding.OpusBitRate
        oldOpusBitrate = Config.variable("opus_bitrate")
        opusBitRate.setText(oldOpusBitrate)

        opusPacketLoss = binding.OpusPacketLoss
        oldOpusPacketLoss = Config.variable("opus_packet_loss")
        opusPacketLoss.setText(oldOpusPacketLoss)

        aec = binding.Aec
        oldAec = modules.contains("webrtc_aecm.so")
        aec.isChecked = oldAec

        audioDelay = binding.AudioDelay
        audioDelay.setText("${BaresipService.audioDelay}")

        val toneCountrySpinner = binding.ToneCountrySpinner
        val toneCountryKeys = arrayListOf("bg", "br", "de", "cz", "es", "fi", "fr", "uk", "jp", "no", "nz", "se", "ru", "us")
        val toneCountryVals = arrayListOf("BG", "BR", "DE", "CZ", "ES", "FI", "FR", "GB", "JP", "NO", "NZ", "SE", "RU", "US")
        val keyIx = toneCountryKeys.indexOf(toneCountry)
        val keyVal = toneCountryVals.elementAt(keyIx)
        toneCountryKeys.removeAt(keyIx)
        toneCountryVals.removeAt(keyIx)
        toneCountryKeys.add(0, toneCountry)
        toneCountryVals.add(0, keyVal)
        val toneCountryAdapter = ArrayAdapter(this,android.R.layout.simple_spinner_item,
            toneCountryVals)
        toneCountryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        toneCountrySpinner.adapter = toneCountryAdapter
        toneCountrySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
                toneCountry = toneCountryKeys[toneCountryVals.indexOf(parent.selectedItem.toString())]
            }
            override fun onNothingSelected(parent: AdapterView<*>) {
            }
        }

        bindTitles()

        onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {

        super.onCreateOptionsMenu(menu)
        val inflater = menuInflater
        inflater.inflate(R.menu.check_icon, menu)
        return true

    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {

        if (BaresipService.activities.indexOf("audio") == -1)
            return true

        when (item.itemId) {

            R.id.checkIcon -> {

                if (BaresipService.callVolume != callVolume) {
                    BaresipService.callVolume = callVolume
                    Config.replaceVariable("call_volume", callVolume.toString())
                    save = true
                }

                var gain = "1.0"
                if (!BaresipService.agcAvailable) {
                    gain = micGain.text.toString().trim()
                    if (!gain.contains("."))
                        gain = "$gain.0"
                    if (gain != oldMicGain) {
                        if (!checkMicGain(gain)) {
                            Utils.alertView(
                                this, getString(R.string.notice),
                                "${getString(R.string.invalid_microphone_gain)}: $gain."
                            )
                            micGain.setText(oldMicGain)
                            return false
                        }
                        if (gain == "1.0") {
                            Api.module_unload("augain")
                            Config.removeVariableValue("module", "augain.so")
                            Config.replaceVariable("augain", "1.0")
                        } else {
                            if (oldMicGain == "1.0") {
                                if (Api.module_load("augain") != 0) {
                                    Utils.alertView(
                                        this, getString(R.string.error),
                                        getString(R.string.failed_to_load_module)
                                    )
                                    micGain.setText(oldMicGain)
                                    return false
                                }
                                Config.addVariable("module", "augain.so")
                            }
                            Config.replaceVariable("augain", gain)
                            Api.cmd_exec("augain $gain")
                        }
                        save = true
                    }
                }

                if (speakerPhone.isChecked != BaresipService.speakerPhone) {
                    BaresipService.speakerPhone = speakerPhone.isChecked
                    Config.replaceVariable("speaker_phone",
                        if (BaresipService.speakerPhone) "yes" else "no")
                    save = true
                }

                var id = 1001
                for (module in audioModules) {
                    val box = findViewById<CheckBox>(id++)
                    if (box.isChecked && !oldAudioModules[module]!!) {
                        if (Api.module_load("${module}.so") != 0) {
                            Utils.alertView(this, getString(R.string.error),
                                    "${getString(R.string.failed_to_load_module)}: ${module}.so")
                            return false
                        }
                        Config.addVariable("module", "${module}.so")
                        save = true
                    }
                    if (!box.isChecked && oldAudioModules[module]!!) {
                        Api.module_unload("${module}.so")
                        Config.removeVariableValue("module", "${module}.so")
                        for (ua in BaresipService.uas)
                            ua.account.removeAudioCodecs(module)
                        AccountsActivity.saveAccounts()
                        save = true
                    }
                    id++
                }

                val opusBitRate = opusBitRate.text.toString().trim()
                if (opusBitRate != oldOpusBitrate) {
                    if (!checkOpusBitRate(opusBitRate)) {
                        Utils.alertView(this, getString(R.string.notice),
                                "${getString(R.string.invalid_opus_bitrate)}: $opusBitRate.")
                        return false
                    }
                    Config.replaceVariable("opus_bitrate", opusBitRate)
                    restart = true
                    save = true
                }

                val opusPacketLoss = opusPacketLoss.text.toString().trim()
                if (opusPacketLoss != oldOpusPacketLoss) {
                    if (!checkOpusPacketLoss(opusPacketLoss)) {
                        Utils.alertView(this, getString(R.string.notice),
                                "${getString(R.string.invalid_opus_packet_loss)}: $opusPacketLoss")
                        return false
                    }
                    Config.replaceVariable("opus_packet_loss", opusPacketLoss)
                    restart = true
                    save = true
                }

                if (aec.isChecked != oldAec) {
                    if (aec.isChecked) {
                        Config.addVariable("module", "webrtc_aecm.so")
                        if (gain != "1.0") {
                            restart = true
                        } else {
                            if (Api.module_load("webrtc_aecm.so") != 0) {
                                Utils.alertView(
                                    this, getString(R.string.error),
                                    getString(R.string.failed_to_load_module)
                                )
                                aec.isChecked = false
                                return false
                            }
                        }
                        BaresipService.webrtcAec = true
                    } else {
                        Api.module_unload("webrtc_aecm.so")
                        Config.removeVariableValue("module", "webrtc_aecm.so")
                        BaresipService.webrtcAec = false
                    }
                    save = true
                }

                val audioDelay = audioDelay.text.toString().trim()
                if (audioDelay != BaresipService.audioDelay.toString()) {
                    if (!checkAudioDelay(audioDelay)) {
                        Utils.alertView(this, getString(R.string.notice),
                                String.format(getString(R.string.invalid_audio_delay), audioDelay))
                        return false
                    }
                    Config.replaceVariable("audio_delay", audioDelay)
                    BaresipService.audioDelay = audioDelay.toLong()
                    save = true
                }

                if (BaresipService.toneCountry != toneCountry) {
                    BaresipService.toneCountry = toneCountry
                    Config.replaceVariable("tone_country", toneCountry)
                    save = true
                }

                if (save)
                    Config.save()

                setResult(if (restart) RESULT_OK else RESULT_CANCELED)

                BaresipService.activities.remove("audio")
                finish()
                return true
            }

            android.R.id.home -> {
                goBack()
                return true
            }

        }

        return super.onOptionsItemSelected(item)

    }

    private fun goBack() {
        BaresipService.activities.remove("audio")
        finish()
    }

    private fun bindTitles() {
        binding.VolumeTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.default_call_volume),
                    getString(R.string.default_call_volume_help))
        }
        binding.MicGainTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.microphone_gain),
                getString(R.string.microphone_gain_help)
            )
        }
        binding.SpeakerPhoneTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.speaker_phone),
                getString(R.string.speaker_phone_help))
        }
        binding.AudioModulesTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.audio_modules_title),
                    getString(R.string.audio_modules_help))
        }
        binding.OpusBitRateTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.opus_bit_rate),
                    getString(R.string.opus_bit_rate_help))
        }
        binding.OpusPacketLossTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.opus_packet_loss),
                    getString(R.string.opus_packet_loss_help))
        }
        binding.AecTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.aec),
                    getString(R.string.aec_help))
        }
        binding.AudioDelayTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.audio_delay),
                    getString(R.string.audio_delay_help))
        }
        binding.ToneCountryTitle.setOnClickListener {
            Utils.alertView(this, getString(R.string.tone_country),
                getString(R.string.tone_country_help))
        }
    }

    private fun checkMicGain(micGain: String): Boolean {
        val number =
            try {
                micGain.toDouble()
            } catch (e: NumberFormatException) {
                return false
            }
        return number >= 1.0
    }

    private fun checkOpusBitRate(opusBitRate: String): Boolean {
        val number = opusBitRate.toIntOrNull() ?: return false
        return (number >= 6000) && (number <= 510000)
    }

    private fun checkOpusPacketLoss(opusPacketLoss: String): Boolean {
        val number = opusPacketLoss.toIntOrNull() ?: return false
        return (number >= 0) && (number <= 100)
    }

    private fun checkAudioDelay(audioDelay: String): Boolean {
        val number = audioDelay.toIntOrNull() ?: return false
        return (number >= 100) && (number <= 3000)
    }

    companion object {
        val audioModules = listOf("opus", "amr", "g722", "g7221", "g726", "g729", "codec2", "g711")
    }

}
