/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.focus.settings

import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.util.AttributeSet
import android.widget.RadioButton
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.edit
import androidx.core.content.res.TypedArrayUtils.getAttr
import androidx.core.content.withStyledAttributes
import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceViewHolder
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
import org.mozilla.focus.R
import androidx.preference.R as preferenceR

/**
 * Interface for radio buttons that can be grouped together.
 */
interface GroupableRadioButton {
    /**
     * Updates the checked state of the radio button.
     */
    fun updateRadioValue(isChecked: Boolean)

    /**
     * Adds the given [radioButton] to this radio button's group.
     */
    fun addToRadioGroup(radioButton: GroupableRadioButton)
}

/**
 * Connect all the given radio buttons into a group,
 * so that when one radio is checked the others are unchecked.
 */
fun addToRadioGroup(vararg radios: GroupableRadioButton) {
    for (i in 0..radios.lastIndex) {
        for (j in (i + 1)..radios.lastIndex) {
            radios[i].addToRadioGroup(radios[j])
            radios[j].addToRadioGroup(radios[i])
        }
    }
}

/**
 * Unchecks all radio buttons in this [Iterable].
 */
fun Iterable<GroupableRadioButton>.uncheckAll() {
    forEach { it.updateRadioValue(isChecked = false) }
}

/**
 * A custom [Preference] that displays a radio button.
 */
@SuppressLint("RestrictedApi")
open class RadioButtonPreference @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
) : Preference(context, attrs), GroupableRadioButton {
    private val radioGroups = mutableListOf<GroupableRadioButton>()
    private var summaryView: TextView? = null
    private var titleView: TextView? = null
    private var radioButton: RadioButton? = null
    private var shouldSummaryBeParsedAsHtmlContent: Boolean = true
    private var defaultValue: Boolean = false
    private var clickListener: (() -> Unit)? = null

    private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)

    val isChecked: Boolean
        get() = radioButton?.isChecked == true

    init {
        layoutResource = R.layout.preference_radio_button

        context.withStyledAttributes(
            attrs,
            R.styleable.RadioButtonPreference,
            getAttr(
                context,
                preferenceR.attr.preferenceStyle,
                android.R.attr.preferenceStyle,
            ),
            0,
        ) {
            defaultValue = when {
                hasValue(R.styleable.RadioButtonPreference_defaultValue) ->
                    getBoolean(R.styleable.RadioButtonPreference_defaultValue, false)
                hasValue(R.styleable.RadioButtonPreference_android_defaultValue) ->
                    getBoolean(R.styleable.RadioButtonPreference_android_defaultValue, false)
                else -> false
            }
        }
    }

    override fun addToRadioGroup(radioButton: GroupableRadioButton) {
        radioGroups.add(radioButton)
    }

    /**
     * Sets a listener to be invoked when the preference is clicked.
     */
    fun onClickListener(listener: (() -> Unit)) {
        clickListener = listener
    }

    override fun setEnabled(enabled: Boolean) {
        super.setEnabled(enabled)
        if (enabled) {
            summaryView?.alpha = FULL_ALPHA
            titleView?.alpha = FULL_ALPHA
        } else {
            summaryView?.alpha = HALF_ALPHA
            titleView?.alpha = HALF_ALPHA
        }
    }

    override fun onBindViewHolder(holder: PreferenceViewHolder) {
        super.onBindViewHolder(holder)

        bindRadioButton(holder)

        bindTitle(holder)

        bindSummaryView(holder)

        setOnPreferenceClickListener {
            updateRadioValue(true)

            toggleRadioGroups()
            clickListener?.invoke()
            true
        }
    }

    override fun updateRadioValue(isChecked: Boolean) {
        persistBoolean(isChecked)
        radioButton?.isChecked = isChecked
        preferences.edit {
            putBoolean(key, isChecked)
        }
        onPreferenceChangeListener?.onPreferenceChange(this, isChecked)
    }

    private fun bindRadioButton(holder: PreferenceViewHolder) {
        radioButton = holder.findViewById(R.id.radio_button) as RadioButton
        radioButton?.isChecked = preferences.getBoolean(key, defaultValue)
        radioButton?.setStartCheckedIndicator()
    }

    private fun toggleRadioGroups() {
        if (radioButton?.isChecked == true) {
            radioGroups.uncheckAll()
        }
    }

    private fun bindTitle(holder: PreferenceViewHolder) {
        titleView = holder.findViewById(R.id.title) as TextView
        titleView?.alpha = if (isEnabled) FULL_ALPHA else HALF_ALPHA

        title?.takeIf { it.isNotEmpty() }?.let {
            titleView?.text = it
        }
    }

    private fun bindSummaryView(holder: PreferenceViewHolder) {
        summaryView = holder.findViewById(R.id.widget_summary) as TextView

        summaryView?.alpha = if (isEnabled) FULL_ALPHA else HALF_ALPHA
        summaryView?.let {
            if (summary.isNullOrEmpty()) {
                it.isVisible = false
            } else {
                it.text = if (shouldSummaryBeParsedAsHtmlContent) {
                    HtmlCompat.fromHtml(summary.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT)
                } else {
                    summary
                }

                it.isVisible = true
            }
        }
    }

    /**
     * In devices with Android 6, when we use android:button="@null" android:drawableStart doesn't work via xml
     * as a result we have to apply it programmatically. More info about this issue https://github.com/mozilla-mobile/fenix/issues/1414
     */
    private fun RadioButton.setStartCheckedIndicator() {
        val attr = context.theme.resolveAttribute(android.R.attr.listChoiceIndicatorSingle)
        val buttonDrawable = AppCompatResources.getDrawable(context, attr)
        buttonDrawable?.apply {
            setBounds(0, 0, intrinsicWidth, intrinsicHeight)
        }
        putCompoundDrawablesRelative(start = buttonDrawable)
    }

    companion object {
        const val HALF_ALPHA = 0.5F
        const val FULL_ALPHA = 1F
    }
}
