package net.torvald.terrarum.ui

import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.graphics.glutils.Float16FrameBuffer
import com.badlogic.gdx.utils.Disposable
import com.jme3.math.FastMath
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.imagefont.BigAlphNum
import net.torvald.terrarum.imagefont.TinyAlphNum
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.roundToInt


/**
 * Created by minjaesong on 2016-08-04.
 */
object Toolkit : Disposable {

    object Theme {
        val COL_INVENTORY_CELL_BORDER = Color(1f, 1f, 1f, 0.25f)
        val COL_CELL_FILL = Color(0x232528C8)
        val COL_CELL_FILL_ALT = Color(0x3c3835C8)
        val COL_CELL_FILL_OPAQUE = Color(0x232528FF)

        val COL_LIST_DEFAULT = Color.WHITE // white
        val COL_INACTIVE = Color.LIGHT_GRAY
        val COL_SELECTED = Color(0x00f8ff_ff) // cyan, HIGHLY SATURATED
        val COL_MOUSE_UP = Color(0xfff066_ff.toInt()) // yellow (all yellows are of low saturation according to the colour science)
        val COL_DISABLED = Color(0xaaaaaaff.toInt())
        val COL_RED = Color(0xff8888ff.toInt())
        val COL_REDD = Color(0xff4448ff.toInt())

        /*
        Try this for alt colour set:

        COL_SELECTED: FFE800 oklch(92% 0.19255 102) saturated yellow
        COL_MOUSE_UP: 55ECFE oklch(87% 0.127 207) whitened and slightly desaturated cyan

         */
    }


    private val shaderKawaseDown = App.loadShaderFromClasspath("shaders/default.vert", "shaders/kawasedown.frag")
    private val shaderKawaseUp = App.loadShaderFromClasspath("shaders/default.vert", "shaders/kawaseup.frag")
    private val shaderBoxDown = App.loadShaderFromClasspath("shaders/default.vert", "shaders/boxdown.frag")
    private val shaderBoxUp = App.loadShaderFromClasspath("shaders/default.vert", "shaders/boxup.frag")

    private lateinit var fboBlur: Float16FrameBuffer
    private lateinit var fboBlurHalf: Float16FrameBuffer
    private lateinit var fboBlurQuarter: Float16FrameBuffer
    private lateinit var blurWriteQuad: Mesh
    private lateinit var blurWriteQuad2: Mesh
    private lateinit var blurWriteQuad4: Mesh

//    val baloonTile = TextureRegionPack("assets/graphics/gui/message_black_tileable.tga", 36, 36)

    val textureWhiteSquare = Texture(Gdx.files.internal("assets/graphics/ortho_line_tex_2px.tga"))
    val textureWhiteCircle = Texture(Gdx.files.internal("assets/graphics/circle_512.tga"))

    init {
        App.disposables.add(this)

        textureWhiteSquare.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
        textureWhiteCircle.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)

        CommonResourcePool.addToLoadingList("toolkit_box_border") {
            TextureRegionPack(Gdx.files.internal("./assets/graphics/gui/box_border_flat_tileable.tga"), 1, 1)
        }
        CommonResourcePool.loadAll()
    }

    private val rng = HQRNG()

    override fun dispose() {
//        baloonTile.dispose()
        textureWhiteSquare.dispose()
        textureWhiteCircle.dispose()

        fboBlur.dispose()
        fboBlurHalf.dispose()
        fboBlurQuarter.dispose()

        blurWriteQuad.dispose()
        blurWriteQuad2.dispose()
        blurWriteQuad4.dispose()

        shaderKawaseUp.dispose()
        shaderKawaseDown.dispose()
        shaderBoxDown.dispose()
        shaderBoxUp.dispose()
    }

    val drawWidth: Int
        get() = App.scr.width - if (App.getConfigBoolean("fx_streamerslayout")) App.scr.chatWidth else 0
    val drawWidthf: Float
        get() = drawWidth.toFloat()
    val hdrawWidth: Int
        get() = drawWidth / 2
    val hdrawWidthf: Float
        get() = hdrawWidth.toFloat()

    fun drawCentered(batch: SpriteBatch, image: Texture, screenPosY: Int, ui: UICanvas? = null) {
        val imageW = image.width
        val targetW = ui?.width ?: drawWidth
        batch.draw(image, targetW.minus(imageW).ushr(1).toFloat(), screenPosY.toFloat())
    }
    fun drawCentered(batch: SpriteBatch, image: TextureRegion, screenPosY: Int, ui: UICanvas? = null) {
        val imageW = image.regionWidth
        val targetW = ui?.width ?: drawWidth
        batch.draw(image, targetW.minus(imageW).ushr(1).toFloat(), screenPosY.toFloat())
    }

    fun drawCentered(batch: SpriteBatch, image: Texture, screenPosY: Int, targetW: Int, offsetX: Int = 0, offsetY: Int = 0) {
        val imageW = image.width
        batch.draw(image, targetW.minus(imageW).ushr(1).toFloat() + offsetX, screenPosY.toFloat() + offsetY)
    }
    fun drawCentered(batch: SpriteBatch, image: TextureRegion, screenPosY: Int, targetW: Int, offsetX: Int = 0, offsetY: Int = 0) {
        val imageW = image.regionWidth
        batch.draw(image, targetW.minus(imageW).ushr(1).toFloat() + offsetX, screenPosY.toFloat() + offsetY)
    }

    fun drawTextCentered(batch: SpriteBatch, font: TerrarumSansBitmap, text: String, tbw: Int, tbx: Int, tby: Int) {
        val tw = font.getWidth(text)
        font.draw(batch, text, tbx + (tbw - tw) / 2, tby)
    }
    fun drawTextCentered(batch: SpriteBatch, font: TinyAlphNum, text: String, tbw: Int, tbx: Int, tby: Int) {
        val tw = font.getWidth(text)
        font.draw(batch, text, (tbx + (tbw - tw) / 2).toFloat(), tby.toFloat())
    }
    fun drawTextCentered(batch: SpriteBatch, font: BigAlphNum, text: String, tbw: Int, tbx: Int, tby: Int) {
        val tw = font.getWidth(text)
        font.draw(batch, text, (tbx + (tbw - tw) / 2).toFloat(), tby.toFloat())
    }

    fun fillArea(batch: SpriteBatch, x: Int, y: Int, w: Int, h: Int) {
        batch.draw(textureWhiteSquare, x.toFloat(), y.toFloat(), w.toFloat(), h.toFloat())
    }
    fun fillArea(batch: SpriteBatch, x: Float, y: Float, w: Float, h: Float) {
        batch.draw(textureWhiteSquare, x, y, w, h)
    }
    fun fillCircle(batch: SpriteBatch, x: Int, y: Int, w: Int, h: Int) {
        batch.draw(textureWhiteCircle, x.toFloat(), y.toFloat(), w.toFloat(), h.toFloat())
    }
    fun drawStraightLine(batch: SpriteBatch, x: Float, y: Float, otherEnd: Float, thickness: Float, isVertical: Boolean) {
        if (!isVertical)
            fillArea(batch, x, y, otherEnd - x, thickness)
        else
            fillArea(batch, x, y, thickness, otherEnd - y)
    }
    fun drawStraightLine(batch: SpriteBatch, x: Int, y: Int, otherEnd: Int, thickness: Int, isVertical: Boolean) {
        drawStraightLine(batch, x.toFloat(), y.toFloat(), otherEnd.toFloat(), thickness.toFloat(), isVertical)
    }

    fun drawBoxBorder(batch: SpriteBatch, x: Float, y: Float, w: Float, h: Float) =
            drawBoxBorder(batch, x.roundToInt(), y.roundToInt(), w.roundToInt(), h.roundToInt())

    /**
     * Parameters are THAT OF THE BOX, the border will be drawn OUTSIDE of the params you specified!
     */
    fun drawBoxBorder(batch: SpriteBatch, x: Int, y: Int, w: Int, h: Int) {
        val pack = CommonResourcePool.getAsTextureRegionPack("toolkit_box_border")
        val tx = pack.tileW.toFloat()
        val ty = pack.tileH.toFloat()

        // top edge
        batch.draw(pack.get(1, 0), x.toFloat(), y - ty, w.toFloat(), ty)
        // bottom edge
        batch.draw(pack.get(1, 2), x.toFloat(), y.toFloat() + h, w.toFloat(), ty)
        // left edge
        batch.draw(pack.get(0, 1), x.toFloat() - tx, y.toFloat(), tx, h.toFloat())
        // right edge
        batch.draw(pack.get(2, 1), x.toFloat() + w, y.toFloat(), tx, h.toFloat())

        // top left point
        /*batch.draw(pack.get(0, 0), x - tx, y - ty)
        // top right point
        batch.draw(pack.get(2, 0), x + tx, y - ty)
        // bottom left point
        batch.draw(pack.get(0, 2), x - tx, y + ty)
        // bottom right point
        batch.draw(pack.get(2, 2), x + tx, y + ty)*/

    }

    private lateinit var blurtex0: Texture
    private lateinit var blurtex1: Texture
    private lateinit var blurtex2: Texture
    private lateinit var blurtex3: Texture

    fun blurEntireScreen(batch: SpriteBatch, camera: OrthographicCamera, blurRadius0: Float, x: Int, y: Int, w: Int, h: Int) {
        batch.end()

        val blurRadius = FastMath.pow(blurRadius0, 0.5f)
        val renderTarget = FrameBufferManager.peek()

        /*if (blurRadius > 3f) {
            val radius3 = (blurRadius - 3f) / 8f
            fboBlurHalf.inAction(camera, batch) {
                blurtex0 = renderTarget.colorBufferTexture
                blurtex0.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
                blurtex0.bind(0)
                shaderKawaseDown.bind()
                shaderKawaseDown.setUniformMatrix("u_projTrans", camera.combined)
                shaderKawaseDown.setUniformi("u_texture", 0)
                shaderKawaseDown.setUniformf("halfpixel", radius3 / fboBlurHalf.width, radius3 / fboBlurHalf.height)
                blurWriteQuad2.render(shaderKawaseDown, GL20.GL_TRIANGLE_FAN)
            }

            fboBlurQuarter.inAction(camera, batch) {
                blurtex1 = fboBlurHalf.colorBufferTexture
                blurtex1.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
                blurtex1.bind(0)
                shaderKawaseDown.bind()
                shaderKawaseDown.setUniformMatrix("u_projTrans", camera.combined)
                shaderKawaseDown.setUniformi("u_texture", 0)
                shaderKawaseDown.setUniformf("halfpixel", radius3 / fboBlurQuarter.width, radius3 / fboBlurQuarter.height)
                blurWriteQuad4.render(shaderKawaseDown, GL20.GL_TRIANGLE_FAN)
            }

            fboBlurHalf.inAction(camera, batch) {
                blurtex2 = fboBlurQuarter.colorBufferTexture
                blurtex2.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
                blurtex2.bind(0)
                shaderKawaseUp.bind()
                shaderKawaseUp.setUniformMatrix("u_projTrans", camera.combined)
                shaderKawaseUp.setUniformi("u_texture", 0)
                shaderKawaseUp.setUniformf("halfpixel", radius3 / fboBlurQuarter.width, radius3 / fboBlurQuarter.height)
                blurWriteQuad2.render(shaderKawaseUp, GL20.GL_TRIANGLE_FAN)
            }

            fboBlur.inAction(camera,  batch) {
                blurtex3 = fboBlurHalf.colorBufferTexture
                blurtex3.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
                blurtex3.bind(0)
                shaderKawaseUp.bind()
                shaderKawaseUp.setUniformMatrix("u_projTrans", camera.combined)
                shaderKawaseUp.setUniformi("u_texture", 0)
                shaderKawaseUp.setUniformf("halfpixel", radius3 / fboBlurHalf.width, radius3 / fboBlurHalf.height)
                blurWriteQuad.render(shaderKawaseUp, GL20.GL_TRIANGLE_FAN)
            }
        }*/

        fboBlurHalf.inAction(camera, batch) {
            blurtex2 = renderTarget.colorBufferTexture
            blurtex2.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
            blurtex2.bind(0)
            shaderKawaseDown.bind()
            shaderKawaseDown.setUniformMatrix("u_projTrans", camera.combined)
            shaderKawaseDown.setUniformi("u_texture", 0)
            shaderKawaseDown.setUniformf("halfpixel", blurRadius / fboBlurHalf.width, blurRadius / fboBlurHalf.height)
            blurWriteQuad2.render(shaderKawaseDown, GL20.GL_TRIANGLE_FAN)
        }

        fboBlur.inAction(camera, batch) {
            blurtex3 = fboBlurHalf.colorBufferTexture
            blurtex3.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
            blurtex3.bind(0)
            shaderKawaseUp.bind()
            shaderKawaseUp.setUniformMatrix("u_projTrans", camera.combined)
            shaderKawaseUp.setUniformi("u_texture", 0)
            shaderKawaseUp.setUniformf("halfpixel", blurRadius / fboBlurHalf.width, blurRadius / fboBlurHalf.height)
            blurWriteQuad.render(shaderKawaseUp, GL20.GL_TRIANGLE_FAN)
        }



        Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it

        batch.begin()
        batch.shader = null
        (batch as FlippingSpriteBatch).drawFlipped(fboBlur.colorBufferTexture, x.toFloat(), y.toFloat())
    }

    fun drawBaloon(batch: SpriteBatch, x: Float, y: Float, w: Float, h: Float, opacity: Float = 1f) {
        // centre area
        /*batch.draw(baloonTile.get(1, 1), x, y, w, h)

        // edges
        batch.draw(baloonTile.get(1, 0), x, y - baloonTile.tileH, w, baloonTile.tileH.toFloat())
        batch.draw(baloonTile.get(1, 2), x, y + h, w, baloonTile.tileH.toFloat())
        batch.draw(baloonTile.get(0, 1), x - baloonTile.tileW, y, baloonTile.tileW.toFloat(), h)
        batch.draw(baloonTile.get(2, 1), x + w, y, baloonTile.tileW.toFloat(), h)

        // corners
        batch.draw(baloonTile.get(0, 0), x - baloonTile.tileW, y - baloonTile.tileH)
        batch.draw(baloonTile.get(2, 0), x + w, y - baloonTile.tileH)
        batch.draw(baloonTile.get(2, 2), x + w, y + h)
        batch.draw(baloonTile.get(0, 2), x - baloonTile.tileW, y + h)*/


        batch.color = Theme.COL_CELL_FILL_OPAQUE.cpy().mul(1f,1f,1f,opacity)
        fillArea(batch, x - 4, y - 4, w + 8, h + 8)
        batch.color = Theme.COL_INACTIVE.cpy().mul(1f,1f,1f,opacity)
        drawBoxBorder(batch, x - 4, y - 4, w + 8, h + 8)
    }

    private var init = false

    /**
     * Make sure App.resize is called first!
     */
    fun resize() {
        if (!init) {
            init = true
        }
        else {
            blurWriteQuad.dispose()
            blurWriteQuad2.dispose()
            blurWriteQuad4.dispose()
            fboBlur.dispose()
            fboBlurHalf.dispose()
            fboBlurQuarter.dispose()
        }

        blurWriteQuad = Mesh(
                true, 4, 4,
                VertexAttribute.Position(),
                VertexAttribute.ColorUnpacked(),
                VertexAttribute.TexCoords(0)
        )
        blurWriteQuad2 = Mesh(
                true, 4, 4,
                VertexAttribute.Position(),
                VertexAttribute.ColorUnpacked(),
                VertexAttribute.TexCoords(0)
        )
        blurWriteQuad4 = Mesh(
                true, 4, 4,
                VertexAttribute.Position(),
                VertexAttribute.ColorUnpacked(),
                VertexAttribute.TexCoords(0)
        )

        val fw = App.scr.width//MathUtils.nextPowerOfTwo(App.scr.width)
        val fh = App.scr.height//MathUtils.nextPowerOfTwo(App.scr.height)

        fboBlur = Float16FrameBuffer(
                fw,
                fh,
                false
        )
        fboBlurHalf = Float16FrameBuffer(
                fw / 2,
                fh / 2,
                false
        )
        fboBlurQuarter = Float16FrameBuffer(
                fw / 4,
                fh / 4,
                false
        )

        blurWriteQuad.setVertices(floatArrayOf(
                0f,0f,0f, 1f,1f,1f,1f, 0f,1f,
                fw.toFloat(),0f,0f, 1f,1f,1f,1f, 1f,1f,
                fw.toFloat(), fh.toFloat(),0f, 1f,1f,1f,1f, 1f,0f,
                0f, fh.toFloat(),0f, 1f,1f,1f,1f, 0f,0f))
        blurWriteQuad.setIndices(shortArrayOf(0, 1, 2, 3))

        blurWriteQuad2.setVertices(floatArrayOf(
                0f,0f,0f, 1f,1f,1f,1f, 0f,1f,
                fw.div(2).toFloat(),0f,0f, 1f,1f,1f,1f, 1f,1f,
                fw.div(2).toFloat(), fh.div(2).toFloat(),0f, 1f,1f,1f,1f, 1f,0f,
                0f, fh.div(2).toFloat(),0f, 1f,1f,1f,1f, 0f,0f))
        blurWriteQuad2.setIndices(shortArrayOf(0, 1, 2, 3))

        blurWriteQuad4.setVertices(floatArrayOf(
                0f,0f,0f, 1f,1f,1f,1f, 0f,1f,
                fw.div(4).toFloat(),0f,0f, 1f,1f,1f,1f, 1f,1f,
                fw.div(4).toFloat(), fh.div(4).toFloat(),0f, 1f,1f,1f,1f, 1f,0f,
                0f, fh.div(4).toFloat(),0f, 1f,1f,1f,1f, 0f,0f))
        blurWriteQuad4.setIndices(shortArrayOf(0, 1, 2, 3))
    }
}