package net.torvald.terrarum.ui

import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Disposable
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.Second
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.roundInt


/**
 * ## UI Items
 *
 * UI can contain one or more UI elements (called UIItem). Each UIItem can have one or more events programmed to it.
 * Events have their own listener are governed by their GDX event handlers (e.g. mouseMoved).
 * These GDX handlers are what makes the our own handler to work.
 *
 * UIItems have following event handlers: updateLister, keyDownListener, mouseMovedListener, touchDraggedListener, touchDownListener, touchUpListener, scrolledListener, and clickOnceListener.
 * (perhaps clickOnceListener is the one most useful)
 *
 * To make them work without any hassle on your part,
 * all the UIItems must be added to this UICanvas's ```uiItems``` list.
 *
 * See also: [net.torvald.terrarum.ui.UIItem]
 *
 * ## Sub UIs
 *
 * Sub UIs are UICanvases that is child of this UICanvas. They are also managed internally to lessen your burden.
 * Just add all the Sub UIs using ```addSubUI()``` method.
 *
 * ## Position variables
 *
 * PosX/Y and relativeMouseX/Y are explained in ```work_files/terrarum_ui_elements_coord_explained.png```
 *
 * Created by minjaesong on 2015-12-31.
 */
abstract class UICanvas(
        toggleKeyLiteral: Int? = null, toggleButtonLiteral: Int? = null,
        // UI positions itself? (you must g.flush() yourself after the g.translate(Int, Int))
        customPositioning: Boolean = false, // mainly used by vital meter
        doNotWarnConstant: Boolean = false
): Disposable {

    abstract var width: Int
    abstract var height: Int

    inline var posX: Int
        get() = handler.posX
        set(value) { handler.posX = value }
    inline var posY: Int
        get() = handler.posY
        set(value) { handler.posY = value }

    /**
     * Usage: (in StateInGame:) uiHandlerField.ui.handler = uiHandlerField
     */
    val handler = UIHandler(toggleKeyLiteral, toggleButtonLiteral, customPositioning, doNotWarnConstant)

    init {

    }

    /**
     * In milliseconds
     *
     * Timer itself is implemented in the ui.handler.
     */
    abstract var openCloseTime: Second


    protected val uiItems = ArrayList<UIItem>()


    val relativeMouseX: Int
        get() = Terrarum.mouseScreenX - handler.posX
    val relativeMouseY: Int
        get() = Terrarum.mouseScreenY - handler.posY

    /** If mouse is hovering over it regardless of its visibility */
    open val mouseUp: Boolean
        get() = _mouseUpThis || handler.mouseUp
    /** If mouse is hovering over it and mouse is down */
    val mousePushed: Boolean
        get() = mouseUp && Gdx.input.isButtonPressed(AppLoader.getConfigInt("mouseprimary"))

    private val _mouseUpThis: Boolean
        get() = relativeMouseX in 0..width - 1 && relativeMouseY in 0..height - 1

    /** Called by the screen */
    fun update(delta: Float) {
        handler.update(this, delta)
    }
    /** Called by the screen */
    fun render(batch: SpriteBatch, camera: Camera) {
        handler.render(this, batch, camera)
    }


    fun addSubUI(ui: UICanvas) {
        handler.addSubUI(ui)
    }


    /** Override this for the actual update. Note that you must update uiItems by yourself. */
    abstract fun updateUI(delta: Float)
    /**
     * Override this for the actual render. Note that you must render uiItems by yourself.
     *
     * Under normal circumstances, draws are automatically translated as per the handler's X/Y position.
     * This means, don't write like: ```draw(posX + 4, posY + 32)```, do instead: ```draw(4, 32)``` unless you have a good reason to do so.
     *
     * The transparency of the handler is independent of the draw, you must specified the color yourself
     * using handler.opacity or handler.opacityColour
     */
    abstract fun renderUI(batch: SpriteBatch, camera: Camera)

    /**
     * Do not modify ui.handler.openCloseCounter here.
     */
    abstract fun doOpening(delta: Float)

    /**
     * Do not modify ui.handler.openCloseCounter here.
     */
    abstract fun doClosing(delta: Float)

    /**
     * Do not modify ui.handler.openCloseCounter here.
     */
    abstract fun endOpening(delta: Float)

    /**
     * Do not modify ui.handler.openCloseCounter here.
     */
    abstract fun endClosing(delta: Float)

    abstract override fun dispose()

    fun addItem(uiItem: UIItem) {
        uiItems.add(uiItem)
    }

    fun mouseInScreen(x: Int, y: Int) = x in 0 until AppLoader.appConfig.width && y in 0 until AppLoader.appConfig.height

    open fun mouseMoved(screenX: Int, screenY: Int): Boolean {
        if (this.isVisible) {
            uiItems.forEach { it.mouseMoved(screenX, screenY) }
            handler.subUIs.forEach { it.mouseMoved(screenX, screenY) }
            return true
        }
        else return false
    }

    /**
     * Called by the screen's InputProcessor
     *
     * When implementing this, make sure to use ```mouseInScreen()``` function!
     */
    open fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean {
        if (this.isVisible) {
            uiItems.forEach { it.touchDragged(screenX, screenY, pointer) }
            handler.subUIs.forEach { it.touchDragged(screenX, screenY, pointer) }
            return true
        }
        else return false
    }
    /** Called by the screen's InputProcessor */
    open fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
        if (this.isVisible && mouseInScreen(screenX, screenY)) {
            uiItems.forEach { it.touchDown(screenX, screenY, pointer, button) }
            handler.subUIs.forEach { it.touchDown(screenX, screenY, pointer, button) }
            return true
        }
        else return false
    }
    /** Called by the screen's InputProcessor */
    open fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
        if (this.isVisible) {
            uiItems.forEach { it.touchUp(screenX, screenY, pointer, button) }
            handler.subUIs.forEach { it.touchUp(screenX, screenY, pointer, button) }
            return true
        }
        else return false
    }
    /** Called by the screen's InputProcessor */
    open fun scrolled(amount: Int): Boolean {
        if (this.isVisible) {
            uiItems.forEach { it.scrolled(amount) }
            handler.subUIs.forEach { it.scrolled(amount) }
            return true
        }
        else return false
    }
    /** Called by the screen's InputProcessor */
    open fun keyDown(keycode: Int): Boolean {
        if (this.isVisible) {
            uiItems.forEach { it.keyDown(keycode) }
            handler.subUIs.forEach { it.keyDown(keycode) }
            return true
        }
        else return false
    }
    /** Called by the screen's InputProcessor */
    open fun keyUp(keycode: Int): Boolean {
        if (this.isVisible) {
            uiItems.forEach { it.keyUp(keycode) }
            handler.subUIs.forEach { it.keyUp(keycode) }
            return true
        }
        else return false
    }
    /** Called by the screen's InputProcessor */
    open fun keyTyped(character: Char): Boolean {
        // TODO process key typing from the virtual keyboard?

        return false
    }

    open fun resize(width: Int, height: Int) {
        this.width = width
        this.height = height
    }


    // handler func aliases //

    open fun setPosition(x: Int, y: Int) {
        handler.setPosition(x, y)
    }

    fun setAsAlwaysVisible() {
        handler.setAsAlwaysVisible()
    }

    open fun setAsOpen() {
        handler.setAsOpen()
    }

    open fun setAsClose() {
        handler.setAsClose()
    }

    open fun toggleOpening() {
        handler.toggleOpening()
    }

    inline val isOpened: Boolean
        get() = handler.isOpened

    inline val isOpening: Boolean
        get() = handler.isOpening

    inline val isClosing: Boolean
        get() = handler.isClosing

    inline val isClosed: Boolean
        get() = handler.isClosed

    inline var opacity: Float
        get() = handler.opacity
        set(value) { handler.opacity = value }

    inline var scale: Float
        get() = handler.scale
        set(value) { handler.scale = value }

    inline val isTakingControl: Boolean
        get() = handler.isTakingControl

    inline var isVisible: Boolean
        get() = handler.isVisible
        set(value) { handler.isVisible = value }

    // end of handler func aliases


    companion object {
        const val OPENCLOSE_GENERIC = 0.2f

        fun doOpeningFade(ui: UICanvas, openCloseTime: Second) {
            ui.handler.opacity = maxOf(0f, ui.handler.openCloseCounter - 0.02f) / openCloseTime // fade start 1/50 sec late, it's intended
        }
        fun doClosingFade(ui: UICanvas, openCloseTime: Second) {
            ui.handler.opacity = (openCloseTime - ui.handler.openCloseCounter) / openCloseTime
        }
        fun endOpeningFade(ui: UICanvas) {
            ui.handler.opacity = 1f
        }
        fun endClosingFade(ui: UICanvas) {
            ui.handler.opacity = 0f
        }


        fun doOpeningPopOut(ui: UICanvas, openCloseTime: Second, position: Position) {
            when (position) {
                Position.LEFT -> ui.handler.posX = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        -ui.width.toFloat(),
                        0f
                ).roundInt()
                Position.TOP -> ui.handler.posY = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        -ui.height.toFloat(),
                        0f
                ).roundInt()
                Position.RIGHT -> ui.handler.posX = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        AppLoader.screenW.toFloat(),
                        AppLoader.screenW - ui.width.toFloat()
                ).roundInt()
                Position.BOTTOM -> ui.handler.posY = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        AppLoader.screenH.toFloat(),
                        AppLoader.screenH - ui.height.toFloat()
                ).roundInt()
            }
        }
        fun doClosingPopOut(ui: UICanvas, openCloseTime: Second, position: Position) {
            when (position) {
                Position.LEFT -> ui.handler.posX = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        0f,
                        -ui.width.toFloat()
                ).roundInt()
                Position.TOP -> ui.handler.posY = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        0f,
                        -ui.height.toFloat()
                ).roundInt()
                Position.RIGHT -> ui.handler.posX = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        AppLoader.screenW - ui.width.toFloat(),
                        AppLoader.screenW.toFloat()
                ).roundInt()
                Position.BOTTOM -> ui.handler.posY = Movement.fastPullOut(
                        ui.handler.openCloseCounter / openCloseTime,
                        AppLoader.screenH - ui.height.toFloat(),
                        AppLoader.screenH.toFloat()
                ).roundInt()
            }
        }
        fun endOpeningPopOut(ui: UICanvas, position: Position) {
            when (position) {
                Position.LEFT -> ui.handler.posX = 0
                Position.TOP -> ui.handler.posY = 0
                Position.RIGHT -> ui.handler.posX = AppLoader.screenW - ui.width
                Position.BOTTOM -> ui.handler.posY = AppLoader.screenH - ui.height
            }
        }
        fun endClosingPopOut(ui: UICanvas, position: Position) {
            when (position) {
                Position.LEFT -> ui.handler.posX = -ui.width
                Position.TOP -> ui.handler.posY = -ui.height
                Position.RIGHT -> ui.handler.posX = AppLoader.screenW
                Position.BOTTOM -> ui.handler.posY = AppLoader.screenH
            }
        }

        // TODO add blackboard take in/out (sinusoidal)

        enum class Position {
            LEFT, RIGHT, TOP, BOTTOM
        }
    }

    override fun toString(): String = "${this.javaClass.simpleName}@${this.hashCode().toString(16)}"
}
