package dev.fyloz.plannervio.ui.view

import com.beust.klaxon.Klaxon
import com.jfoenix.controls.JFXButton
import com.jfoenix.svg.SVGGlyph
import dev.fyloz.plannervio.ui.Plannervio
import dev.fyloz.plannervio.ui.i18n.MergedResourceBundle
import dev.fyloz.plannervio.ui.logger
import dev.fyloz.plannervio.ui.style.Style
import javafx.beans.property.ReadOnlyStringWrapper
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.event.EventTarget
import javafx.geometry.Pos
import javafx.scene.Node
import javafx.scene.layout.HBox
import javafx.scene.layout.VBox
import javafx.scene.paint.Color
import tornadofx.*
import java.util.*

const val KEYWORDS_BUNDLE_NAME = "lang.keywords"

/** A SVG icon. */
data class SvgIcon(val name: String, val author: String, val path: String)

/** The path to the file containing svg icons. */
private const val SVG_ICONS_FILE_PATH = "/images/icons.json"

/** A [List] containing all available [SvgIcon]s. */
val svgIcons = loadSvgIcons()

/** Load all [SvgIcon]s from the file at the path specified by [SVG_ICONS_FILE_PATH]. */
private fun loadSvgIcons(): List<SvgIcon> {
    val doc = Plannervio::class.java.getResource(SVG_ICONS_FILE_PATH).readText()
    val allIcons: List<SvgIcon> = Klaxon().parseArray(doc) ?: throw IllegalStateException("Could not load SVG icons.")
    logger.debug("Loaded ${allIcons.size} SVG icons.")
    return allIcons
}

/** Create a [SVGGlyph] for the given [svgIconName] with the given [size] and [color]. */
fun svgGlyph(svgIconName: SvgIconName, size: Double = 16.0, color: Color = Color.BLACK): SVGGlyph {
    val svgIcon = svgIcons.firstOrNull { svgIconName.iconName == it.name }
            ?: throw IllegalStateException("Cannot create a SVGGlyph for the icon ${svgIconName.iconName} because no icon loaded was found with this name.")
    val glyph = SVGGlyph(svgIcon.path, color)
    glyph.size = size
    return glyph
}

/** A button linked to a [tab]. */
class TabButton(tab: Tab, active: Boolean = false) : JFXButton() {
    /** The [Tab] linked to the button. */
    val tabProperty by lazy { SimpleObjectProperty(tab) }
    var tab: Tab by tabProperty

    /** If the button is active. */
    val activeProperty = SimpleBooleanProperty(this, "active", active)
    var active: Boolean
        get() = activeProperty.get()
        set(value) {
            toggleClass(Style.active, value)
            activeProperty.set(value)
        }
}

/** A [SvgIcon] and a [Label] wrapped in a [HBox]. */
class LabeledIcon(svgIconName: SvgIconName, text: String, iconSize: Double = 16.0, iconColor: Color = Color.BLACK) : HBox() {
    /** The name of the [SvgIcon]. */
    val iconNameProperty by lazy { ReadOnlyStringWrapper(svgIconName.iconName) }
    val iconName: String by iconNameProperty

    /** The text of the [Label]. */
    val textProperty by lazy { SimpleStringProperty(text) }
    var text: String by textProperty

    override fun getUserAgentStylesheet(): String = LabeledIconStyle().base64URL.toExternalForm()

    init {
        addClass(LabeledIconStyle.labeledIcon)
        children.setAll(
                svgGlyph(svgIconName, iconSize, iconColor),
                label(textProperty)
        )
    }

    private class LabeledIconStyle : Stylesheet() {
        companion object {
            val labeledIcon by cssclass()
        }

        init {
            labeledIcon {
                alignment = Pos.CENTER_LEFT

                label {
                    padding = box(0.px, 0.px, 0.px, 10.px)
                }
            }
        }
    }
}

/** A toggleable icon [JFXButton], with an [enabledColor] and a [disabledColor]. */
class ToggleableIconButton(svgIconName: SvgIconName, enabledColor: Color, disabledColor: Color, backgroundColor: Color = Color.WHITE, size: Double = 16.0, enabled: Boolean = false) : JFXButton() {
    val enabledProperty by lazy { SimpleBooleanProperty(enabled) }
    var enabled by enabledProperty

    val enabledColorProperty by lazy { SimpleObjectProperty(enabledColor) }
    val enabledColor: Color by enabledColorProperty

    val disabledColorProperty by lazy { SimpleObjectProperty(disabledColor) }
    val disabledColor: Color by disabledColorProperty

    private val svgIcon: SVGGlyph
    private val iconColor: Color
        get() = if (enabled) enabledColor else disabledColor

    init {
        svgIcon = svgGlyph(svgIconName, size, color = iconColor)
        graphic = svgIcon
        ripplerFill = backgroundColor

        enabledProperty.onChange {
            this.enabled = it
            toggle()
        }

        setOnMouseClicked {
            this.enabled = !this.enabled
            toggle()
        }
    }

    private fun toggle() {
        svgIcon.fill = iconColor
    }
}

/** DSL for [TabButton]s. */
fun EventTarget.tabButton(tab: Tab = Tab.BOARDS, op: TabButton.() -> Unit = {}) = TabButton(tab).attachTo(this, op)

/** DSL for [ToggleableIconButton]s. */
fun EventTarget.toggleableIconButton(
        svgIconName: SvgIconName,
        enabledColor: Color,
        disabledColor: Color,
        backgroundColor: Color = Color.WHITE,
        size: Double = 16.0,
        enabled: Boolean = false,
        op: ToggleableIconButton.() -> Unit = {}) =
        ToggleableIconButton(svgIconName, enabledColor, disabledColor, backgroundColor, size, enabled).attachTo(this, op)

fun messages(vararg names: String): ResourceBundle {
    val bundle = MergedResourceBundle(ResourceBundle.getBundle(KEYWORDS_BUNDLE_NAME))
    bundle + names.map { ResourceBundle.getBundle(it) }
    return bundle
}

/** Sets [all] margins of a [Node] contained in a [VBox] to the given size. */
fun Node.vboxMargin(all: Number = 0) {
    this.vboxMargin(all, all)
}

/** Sets [horizontal] and [vertical] margins of a [Node] contained in a [VBox] to the given size. */
fun Node.vboxMargin(horizontal: Number = 0, vertical: Number = 0) {
    this.vboxMargin(vertical, horizontal, vertical, horizontal)
}

/** Sets [top], [right], [bottom] and [left] margins of a [Node] contained in a [VBox] to the given size. */
fun Node.vboxMargin(top: Number = 0, right: Number = 0, bottom: Number = 0, left: Number = 0) {
    VBox.setMargin(this, insets(top.toDouble(), right.toDouble(), bottom.toDouble(), left.toDouble()))
}

/** Sets [all] margins of a [Node] contained in a [HBox] to the given size. */
fun Node.hboxMargin(all: Number = 0) {
    this.hboxMargin(all, all)
}

/** Sets [horizontal] and [vertical] margins of a [Node] contained in a [HBox] to the given size. */
fun Node.hboxMargin(horizontal: Number = 0, vertical: Number = 0) {
    this.hboxMargin(vertical, horizontal, vertical, horizontal)
}

/** Sets [top], [right], [bottom] and [left] margins of a [Node] contained in a [HBox] to the given size. */
fun Node.hboxMargin(top: Number = 0, right: Number = 0, bottom: Number = 0, left: Number = 0) {
    HBox.setMargin(this, insets(top.toDouble(), right.toDouble(), bottom.toDouble(), left.toDouble()))
}

object Keywords {
    const val BOARD_NAME = "board.name"
    const val BOARD_DESCRIPTION = "board.description"
    const val BOARD_LAST_VISITED = "board.lastVisited"
    const val BOARD_FAVORITE = "board.favorite"
    const val BOARD_PROTECTION = "board.protection"
    const val BOARD_PROTECTION_PRIVATE = "board.protection.private"
    const val BOARD_PROTECTION_PUBLIC = "board.protection.public"
    const val BOARD_LOCATION = "board.location"
    const val BOARD_LOCATION_LOCAL = "board.location.local"
}

/** An enum containing the name of used [SvgIcon]s, for easy access. Not all available icons are specified in this enum. */
enum class SvgIconName(val iconName: String) {
    COG("cog"),
    EARTH("earth"),
    FORMAT_LIST_BULLETED("format-list-bulleted"),
    HISTORY("history"),
    LOCK("lock"),
    STAR("star")
}
