/*
 * SPDX-License-Identifier: MPL-2.0
 * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
 */

package emu.skyline.input

import android.content.Intent
import android.graphics.Canvas
import android.os.Bundle
import android.view.KeyEvent
import android.view.ViewTreeObserver
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.marginTop
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.R
import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.GenericListItem
import emu.skyline.adapter.controller.*
import emu.skyline.databinding.ControllerActivityBinding
import emu.skyline.input.dialog.ButtonDialog
import emu.skyline.input.dialog.RumbleDialog
import emu.skyline.input.dialog.StickDialog
import emu.skyline.input.onscreen.OnScreenEditActivity
import emu.skyline.utils.PreferenceSettings
import javax.inject.Inject

/**
 * This activity is used to change the settings for a specific controller
 */
@AndroidEntryPoint
class ControllerActivity : AppCompatActivity() {
    private val binding by lazy { ControllerActivityBinding.inflate(layoutInflater) }

    /**
     * The index of the controller this activity manages
     */
    val id by lazy { intent.getIntExtra("index", 0) }

    private val adapter = GenericAdapter()

    /**
     * This is a map between a button and it's corresponding [ControllerViewItem] in [adapter]
     */
    val buttonMap = mutableMapOf<ButtonId, ControllerViewItem>()

    /**
     * This is a map between an axis and it's corresponding [ControllerStickViewItem] in [adapter]
     */
    val axisMap = mutableMapOf<AxisId, ControllerStickViewItem>()

    val stickItems = mutableListOf<ControllerStickViewItem>()

    val buttonItems = mutableListOf<ControllerButtonViewItem>()

    @Inject
    lateinit var preferenceSettings : PreferenceSettings

    @Inject
    lateinit var inputManager : InputManager

    /**
     * This function updates the [adapter] based on information from [InputManager]
     */
    private fun update() {
        val items = mutableListOf<GenericListItem<*>>()

        try {
            val controller = inputManager.controllers[id]!!

            items.add(ControllerTypeViewItem(controller.type, onControllerTypeClick))

            if (controller.type == ControllerType.None)
                return

            if (id == 0) {
                items.add(ControllerHeaderItem(getString(R.string.osc)))

                val oscSummary = { checked : Boolean -> getString(if (checked) R.string.osc_shown else R.string.osc_not_shown) }
                items.add(ControllerCheckBoxViewItem(getString(R.string.osc_enable), oscSummary.invoke(preferenceSettings.onScreenControl), preferenceSettings.onScreenControl) { item, position ->
                    item.summary = oscSummary.invoke(item.checked)
                    preferenceSettings.onScreenControl = item.checked
                    adapter.notifyItemChanged(position)
                })

                items.add(ControllerCheckBoxViewItem(getString(R.string.osc_recenter_sticks), "", preferenceSettings.onScreenControlRecenterSticks) { item, position ->
                    preferenceSettings.onScreenControlRecenterSticks = item.checked
                    adapter.notifyItemChanged(position)
                })

                items.add(ControllerViewItem(content = getString(R.string.osc_edit), onClick = {
                    startActivity(Intent(this, OnScreenEditActivity::class.java))
                }))
            }

            var wroteTitle = false

            for (item in GeneralType.values()) {
                if (item.compatibleControllers == null || item.compatibleControllers.contains(controller.type)) {
                    if (!wroteTitle) {
                        items.add(ControllerHeaderItem(getString(R.string.general)))
                        wroteTitle = true
                    }

                    items.add(ControllerGeneralViewItem(id, item, onControllerGeneralClick))
                }
            }

            if (controller.type.sticks.isNotEmpty())
                items.add(ControllerHeaderItem(getString(R.string.sticks)))

            for (stick in controller.type.sticks) {
                val stickItem = ControllerStickViewItem(id, stick, onControllerStickClick)

                items.add(stickItem)
                stickItems.add(stickItem)
                buttonMap[stick.button] = stickItem
                axisMap[stick.xAxis] = stickItem
                axisMap[stick.yAxis] = stickItem
            }

            val dpadButtons = Pair(R.string.dpad, arrayOf(ButtonId.DpadUp, ButtonId.DpadDown, ButtonId.DpadLeft, ButtonId.DpadRight))
            val faceButtons = Pair(R.string.face_buttons, arrayOf(ButtonId.A, ButtonId.B, ButtonId.X, ButtonId.Y))
            val shoulderTriggerButtons = Pair(R.string.shoulder_trigger, arrayOf(ButtonId.L, ButtonId.R, ButtonId.ZL, ButtonId.ZR))
            val shoulderRailButtons = Pair(R.string.shoulder_rail, arrayOf(ButtonId.LeftSL, ButtonId.LeftSR, ButtonId.RightSL, ButtonId.RightSR))

            val buttonArrays = arrayOf(dpadButtons, faceButtons, shoulderTriggerButtons, shoulderRailButtons)

            for (buttonArray in buttonArrays) {
                val filteredButtons = controller.type.buttons.filter { it in buttonArray.second }

                if (filteredButtons.isNotEmpty())
                    items.add(ControllerHeaderItem(getString(buttonArray.first)))

                for (button in filteredButtons) {
                    val buttonItem = ControllerButtonViewItem(id, button, onControllerButtonClick)

                    items.add(buttonItem)
                    buttonItems.add(buttonItem)
                    buttonMap[button] = buttonItem
                }
            }

            items.add(ControllerHeaderItem(getString(R.string.misc_buttons))) // The menu button will always exist
            for (button in controller.type.buttons.filterNot { item -> buttonArrays.any { item in it.second } }.plus(ButtonId.Menu)) {
                val buttonItem = ControllerButtonViewItem(id, button, onControllerButtonClick)

                items.add(buttonItem)
                buttonItems.add(buttonItem)
                buttonMap[button] = buttonItem
            }
        } finally {
            adapter.setItems(items)
        }
    }

    /**
     * This initializes all of the elements in the activity
     */
    override fun onCreate(state : Bundle?) {
        super.onCreate(state)

        if (id < 0 || id > 7)
            throw IllegalArgumentException()

        title = "${getString(R.string.config_controller)} #${id + 1}"

        setContentView(binding.root)

        setSupportActionBar(binding.titlebar.toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)

        val layoutManager = LinearLayoutManager(this)
        binding.controllerList.layoutManager = layoutManager
        binding.controllerList.adapter = adapter

        var layoutDone = false // Tracks if the layout is complete to avoid retrieving invalid attributes
        binding.coordinatorLayout.viewTreeObserver.addOnTouchModeChangeListener { isTouchMode ->
            val layoutUpdate = { ->
                val params = binding.controllerList.layoutParams as CoordinatorLayout.LayoutParams
                if (!isTouchMode) {
                    binding.titlebar.appBarLayout.setExpanded(true)
                    params.height = binding.coordinatorLayout.height - binding.titlebar.toolbar.height
                } else {
                    params.height = CoordinatorLayout.LayoutParams.MATCH_PARENT
                }

                binding.controllerList.layoutParams = params
                binding.controllerList.requestLayout()
            }

            if (!layoutDone) {
                binding.coordinatorLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        // We need to wait till the layout is done to get the correct height of the toolbar
                        binding.coordinatorLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
                        layoutUpdate()
                        layoutDone = true
                    }
                })
            } else {
                layoutUpdate()
            }
        }

        val dividerItemDecoration = object : DividerItemDecoration(this, DividerItemDecoration.VERTICAL) {
            override fun onDraw(canvas : Canvas, parent : RecyclerView, state : RecyclerView.State) {
                val divider = drawable!!
                for (i in 0 until parent.childCount) {
                    val view = parent.getChildAt(i)
                    val position = parent.getChildAdapterPosition(view)
                    if (position != RecyclerView.NO_POSITION && parent.adapter!!.getItemViewType(position) == adapter.getFactoryViewType(ControllerHeaderBindingFactory)) {
                        val bottom = view.top - view.marginTop
                        val top = bottom - divider.intrinsicHeight
                        divider.setBounds(0, top, parent.width, bottom)
                        divider.draw(canvas)
                    }
                }
            }
        }

        dividerItemDecoration.drawable.let { it?.setTint(getColor(R.color.dividerColor)); it }
        binding.controllerList.addItemDecoration(dividerItemDecoration)

        update()
    }

    /**
     * This causes the input file to be synced when the activity has been paused
     */
    override fun onPause() {
        inputManager.syncFile()
        super.onPause()
    }

    private val onControllerTypeClick = { item : ControllerTypeViewItem, _ : Int ->
        val controller = inputManager.controllers[id]!!

        val types = ControllerType.values().apply { if (id != 0) filter { !it.firstController } }
        val typeNames = types.map { getString(it.stringRes) }.toTypedArray()

        MaterialAlertDialogBuilder(this)
            .setTitle(item.content)
            .setSingleChoiceItems(typeNames, types.indexOf(controller.type)) { dialog, typeIndex ->
                val selectedType = types[typeIndex]
                if (controller.type != selectedType) {
                    if (controller is JoyConLeftController)
                        controller.partnerId?.let { (inputManager.controllers[it] as JoyConRightController).partnerId = null }
                    else if (controller is JoyConRightController)
                        controller.partnerId?.let { (inputManager.controllers[it] as JoyConLeftController).partnerId = null }

                    inputManager.controllers[id] = when (selectedType) {
                        ControllerType.None -> Controller(id, ControllerType.None)
                        ControllerType.HandheldProController -> HandheldController(id)
                        ControllerType.ProController -> ProController(id)
                        ControllerType.JoyConLeft -> JoyConLeftController(id)
                        ControllerType.JoyConRight -> JoyConRightController(id)
                    }

                    update()
                }

                dialog.dismiss()
            }
            .show()
        Unit
    }

    private val onControllerGeneralClick = { item : ControllerGeneralViewItem, _ : Int ->
        when (item.type) {
            GeneralType.PartnerJoyCon -> {
                val controller = inputManager.controllers[id] as JoyConLeftController

                val rJoyCons = inputManager.controllers.values.filter { it.type == ControllerType.JoyConRight }
                val rJoyConNames = (listOf(getString(R.string.none)) + rJoyCons.map { "${getString(R.string.controller)} #${it.id + 1}" }).toTypedArray()

                val partnerNameIndex = controller.partnerId?.let { partnerId ->
                    rJoyCons.withIndex().single { it.value.id == partnerId }.index + 1
                } ?: 0

                MaterialAlertDialogBuilder(this)
                    .setTitle(item.content)
                    .setSingleChoiceItems(rJoyConNames, partnerNameIndex) { dialog, index ->
                        (inputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = null

                        controller.partnerId = if (index == 0) null else rJoyCons[index - 1].id

                        if (controller.partnerId != null)
                            (inputManager.controllers[controller.partnerId ?: -1] as JoyConRightController?)?.partnerId = controller.id

                        item.update()

                        dialog.dismiss()
                    }
                    .show()
            }

            GeneralType.RumbleDevice -> {
                RumbleDialog(item).show(supportFragmentManager, null)
            }

            GeneralType.SetupGuide -> {
                var dialogFragment : BottomSheetDialogFragment? = null

                for (buttonItem in buttonItems.reversed())
                    dialogFragment = ButtonDialog(buttonItem, dialogFragment)

                for (stickItem in stickItems.reversed())
                    dialogFragment = StickDialog(stickItem, dialogFragment)

                dialogFragment?.show(supportFragmentManager, null)
            }
        }
        Unit
    }

    private val onControllerButtonClick = { item : ControllerButtonViewItem, _ : Int ->
        ButtonDialog(item).show(supportFragmentManager, null)
    }

    private val onControllerStickClick = { item : ControllerStickViewItem, _ : Int ->
        StickDialog(item).show(supportFragmentManager, null)
    }

    /**
     * This handles on calling [onBackPressed] when [KeyEvent.KEYCODE_BUTTON_B] is lifted
     */
    override fun onKeyUp(keyCode : Int, event : KeyEvent?) : Boolean {
        if (keyCode == KeyEvent.KEYCODE_BUTTON_B) {
            onBackPressed()
            return true
        }

        return super.onKeyUp(keyCode, event)
    }
}
