package com.marvinelsen.willow.ui.controllers

import com.marvinelsen.willow.Model
import com.marvinelsen.willow.config.Config
import com.marvinelsen.willow.config.Pronunciation
import com.marvinelsen.willow.config.Script
import com.marvinelsen.willow.ui.DictionaryEntryFx
import com.marvinelsen.willow.ui.cells.DictionaryEntryCellFactory
import com.marvinelsen.willow.ui.util.createContextMenuForEntry
import javafx.beans.binding.Bindings
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.ListView
import javafx.scene.control.ProgressIndicator
import javafx.scene.control.Tab
import javafx.scene.control.TabPane
import javafx.scene.input.ContextMenuEvent
import javafx.scene.layout.FlowPane
import javafx.scene.web.WebView
import kotlinx.html.body
import kotlinx.html.div
import kotlinx.html.h1
import kotlinx.html.html
import kotlinx.html.li
import kotlinx.html.ol
import kotlinx.html.span
import kotlinx.html.stream.createHTML
import java.util.ResourceBundle

@Suppress("UnusedPrivateMember")
class DetailsController(private val model: Model, private val config: Config) {
    @FXML
    private lateinit var resources: ResourceBundle

    @FXML
    private lateinit var flowPaneHeader: FlowPane

    @FXML
    private lateinit var labelHeadword: Label

    @FXML
    private lateinit var labelPronunciation: Label

    @FXML
    private lateinit var tabPaneDetails: TabPane

    @FXML
    private lateinit var tabCharacters: Tab

    @FXML
    private lateinit var webViewDefinition: WebView

    @FXML
    private lateinit var listViewWords: ListView<DictionaryEntryFx>

    @FXML
    private lateinit var listViewCharacters: ListView<DictionaryEntryFx>

    @FXML
    private lateinit var progressIndicatorCharacters: ProgressIndicator

    @FXML
    private lateinit var progressIndicatorWords: ProgressIndicator

    @FXML
    private lateinit var labelNoCharactersFound: Label

    @FXML
    private lateinit var labelNoWordsFound: Label

    @FXML
    private fun initialize() {
        initializeLabelHeadword()
        initializeLabelPronunciation()
        initializeTabPaneDetails()
        initializeListViewWords()
        initializeListViewCharacters()
        initializeWebViewDefinition()
    }

    private fun initializeLabelHeadword() {
        labelHeadword.apply {
            textProperty().bind(
                Bindings.createStringBinding(
                    {
                        val selectedEntry = model.selectedEntry.value
                        when (config.script.value!!) {
                            Script.SIMPLIFIED -> selectedEntry?.simplifiedProperty?.value
                            Script.TRADITIONAL -> selectedEntry?.traditionalProperty?.value
                        }
                    },
                    config.script,
                    model.selectedEntry
                )
            )
            styleProperty().bind(
                Bindings.concat(
                    "-fx-font-size: ",
                    config.details.headwordFontSize.asString(),
                    "px;"
                )
            )
        }
    }

    private fun initializeLabelPronunciation() {
        labelPronunciation.apply {
            textProperty().bind(
                Bindings.createStringBinding(
                    {
                        val selectedEntry = model.selectedEntry.value
                        when (config.details.pronunciation.value!!) {
                            Pronunciation.PINYIN_WITH_TONE_MARKS ->
                                selectedEntry
                                    ?.pinyinWithToneMarksProperty
                                    ?.value

                            Pronunciation.PINYIN_WITH_TONE_NUMBERS ->
                                selectedEntry
                                    ?.pinyinWithToneNumbersProperty
                                    ?.value

                            Pronunciation.ZHUYIN ->
                                selectedEntry
                                    ?.zhuyinProperty
                                    ?.value
                        }
                    },
                    config.details.pronunciation,
                    model.selectedEntry
                )
            )
            styleProperty().bind(
                Bindings.concat(
                    "-fx-font-size: ",
                    config.details.pronunciationFontSize.asString(),
                    "px;"
                )
            )
        }
    }

    private fun initializeTabPaneDetails() {
        tabPaneDetails.apply {
            disableProperty().bind(Bindings.isNull(model.selectedEntry))

            selectionModel.selectedItemProperty().addListener { _, _, selectedTab ->
                if (model.selectedEntry.value == null) return@addListener

                lazyUpdateTabContent(selectedTab.id)
            }
        }

        model.selectedEntry.addListener { _, _, newEntry ->
            if (newEntry == null) return@addListener

            lazyUpdateTabContent(tabPaneDetails.selectionModel.selectedItem.id)
        }

        tabCharacters.disableProperty().bind(
            Bindings.createBooleanBinding(
                {
                    (model.selectedEntry.value?.traditionalProperty?.value?.length ?: 0) < 2
                },
                model.selectedEntry
            )
        )
    }

    private fun initializeListViewWords() {
        listViewWords.apply {
            cellFactory = DictionaryEntryCellFactory(resources, config)
            items = model.wordsContaining

            disableProperty().bind(Bindings.or(model.isFindingWords, Bindings.isEmpty(model.wordsContaining)))
        }
        progressIndicatorWords.visibleProperty().bind(model.isFindingWords)
        labelNoWordsFound
            .visibleProperty()
            .bind(Bindings.and(Bindings.isEmpty(model.wordsContaining), Bindings.not(model.isFindingWords)))
    }

    private fun initializeListViewCharacters() {
        listViewCharacters.apply {
            cellFactory = DictionaryEntryCellFactory(resources, config)
            items = model.characters

            disableProperty().bind(Bindings.or(model.isFindingCharacters, Bindings.isEmpty(model.characters)))
        }
        progressIndicatorCharacters.visibleProperty().bind(model.isFindingCharacters)
        labelNoCharactersFound
            .visibleProperty()
            .bind(Bindings.and(Bindings.isEmpty(model.characters), Bindings.not(model.isFindingCharacters)))
    }

    private fun initializeWebViewDefinition() {
        webViewDefinition.apply {
            engine.userStyleSheetLocation = this::class.java.getResource("/css/definitions.css")!!.toExternalForm()
        }

        model.selectedEntry.addListener { _, _, newEntry ->
            if (newEntry == null) return@addListener

            webViewDefinition.engine.loadContent(
                createHTML().html {
                    body {
                        if (newEntry.cedictDefinitions.isNotEmpty()) {
                            div(classes = "cedict-definition") {
                                h1 {
                                    +"CC-CEDICT"
                                }
                                ol {
                                    for (definition in newEntry.cedictDefinitions) {
                                        li {
                                            +definition.joinToString(separator = "; ")
                                        }
                                    }
                                }
                            }
                        }
                        if (newEntry.crossStraitsDefinitions.isNotEmpty()) {
                            div(classes = "cross-straits-definition") {
                                h1 {
                                    +"Cross-Straits"
                                }
                                ol {
                                    newEntry.crossStraitsDefinitions.forEach { definition ->
                                        li {
                                            span(classes = "definition") {
                                                +definition.definition
                                            }
                                            if (definition.examples.isNotEmpty()) {
                                                span(classes = "example") {
                                                    +definition.examples.joinToString(
                                                        prefix = "如：",
                                                        separator = "、",
                                                        postfix = "。"
                                                    ) { "「$it」" }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if (newEntry.moedictDefinitions.isNotEmpty()) {
                            div(classes = "moe-definition") {
                                h1 {
                                    +"MOE"
                                }
                                newEntry.moedictDefinitions.groupBy {
                                    it.type ?: ""
                                }.entries.forEach { (type, definitions) ->
                                    if (type != "") {
                                        span(classes = "type") {
                                            +type
                                        }
                                    }

                                    ol {
                                        definitions.forEach { definition ->
                                            li {
                                                span(classes = "definition") {
                                                    +definition.definition
                                                }

                                                definition.examples.forEach { example ->
                                                    span(classes = "example") {
                                                        +example
                                                    }
                                                }

                                                definition.quotes.forEach { quote ->
                                                    span(classes = "quote") {
                                                        +quote
                                                    }
                                                }

                                                definition.synonyms?.let {
                                                    span(classes = "synonyms") {
                                                        +"似：${it.replace(",", "、")}"
                                                    }
                                                }

                                                definition.antonyms?.let {
                                                    span(classes = "antonyms") {
                                                        +"反：${it.replace(",", "、")}"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            )
        }
    }

    @FXML
    private fun headerOnContextMenuRequested(contextMenuEvent: ContextMenuEvent) {
        if (model.selectedEntry.value == null) return

        createContextMenuForEntry(model.selectedEntry.value, resources).show(
            flowPaneHeader.scene.window,
            contextMenuEvent.screenX,
            contextMenuEvent.screenY
        )
    }

    private fun lazyUpdateTabContent(selectedTabId: String?) {
        when (selectedTabId) {
            "tabWords" -> {
                if (model.wordsContaining.isNotEmpty()) return

                model.findWords()
            }

            "tabCharacters" -> {
                if (model.characters.isNotEmpty()) return

                model.findCharacters()
            }

            else -> {}
        }
    }
}
