package net.torvald.terrarum

import com.jme3.math.FastMath
import net.torvald.terrarum.gameactors.floor
import net.torvald.terrarum.gameactors.floorInt
import net.torvald.terrarum.gameworld.toUint
import org.newdawn.slick.*
import org.newdawn.slick.state.BasicGameState
import org.newdawn.slick.state.StateBasedGame
import java.nio.ByteOrder


/**
 * Software rendering test for blur
 *
 * Created by minjaesong on 2017-01-12.
 */
class StateBlurTest : BasicGameState() {

    /** Warning: the image must have a bit depth of 32! (use 32-bit PNG or TGA) */
    private val testImage = Image("./assets/test_texture.tga")
    private val bluredImage = ImageBuffer(testImage.width, testImage.height)

    override fun init(gc: GameContainer, sbg: StateBasedGame) {
        /*testImage.flushPixelData()

        System.arraycopy(
                testImage.texture.textureData, 0,
                bluredImage.rgba, 0, testImage.texture.textureData.size
        )*/
        //kotlin.repeat(3) { fastBoxBlur(bluredImage, 3) }
    }



    override fun update(gc: GameContainer, sbg: StateBasedGame, delta: Int) {
        Terrarum.appgc.setTitle("${Terrarum.NAME} — F: ${Terrarum.appgc.fps}")

        theta += delta / 500f
        if (theta > FastMath.TWO_PI)
            theta -= FastMath.TWO_PI
    }

    override fun getID() = Terrarum.STATE_ID_TEST_BLUR

    var theta = 0f

    override fun render(gc: GameContainer, sbg: StateBasedGame, g: Graphics) {
        System.arraycopy(
                testImage.texture.textureData, 0,
                bluredImage.rgba, 0, testImage.texture.textureData.size
        )
        kotlin.repeat(3) { fastBoxBlur(bluredImage, 3) }

        g.background = Color(0x404040)
        val image = bluredImage.image // ImageBuffer.getImage() always HARDCOPIES texture data by
        // allocating new ByteBuffer. We need variable so that we can destroy() it manually
        g.drawImage(image,
                Terrarum.WIDTH.minus(testImage.width).div(2f).floor() + FastMath.cos(theta) * 120,
                Terrarum.HEIGHT.minus(testImage.height).div(2f).floor() + FastMath.sin(theta) * 40
        )
        g.flush()

        image.destroy() // You are done and you will be terminated, for the perkeleen memory's sake
    }

    private val isLE: Boolean
        get() = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN

    /** three iterations of box blur \simeq gaussian blur */
    fun fastBoxBlur(from: Image, to: ImageBuffer, radius: Int) {

        /** 0xRRGGBBAA */
        fun getPixelData(index: Int): Int {
            val r = from.texture.textureData[4 * index + if (isLE) 0 else 2].toUint()
            val g = from.texture.textureData[4 * index + 1].toUint()
            val b = from.texture.textureData[4 * index + if (isLE) 2 else 0].toUint()

            if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
                return r.shl(16) or g.shl(8) or b
            }
            else {
                return b.shl(16) or g.shl(8) or r
            }
        }

        /** alpha will be passed through */
        fun setPixelData(index: Int, value: Int) {
            val r = value.ushr(24).and(0xff)
            val g = value.ushr(16).and(0xff)
            val b = value.ushr(8).and(0xff)

            to.rgba[4 * index + if (isLE) 0 else 2] = r.toByte()
            to.rgba[4 * index + 1] = g.toByte()
            to.rgba[4 * index + if (isLE) 2 else 0] = b.toByte()
        }

        if (radius < 1) {
            return
        }
        val w = to.texWidth
        val h = to.texHeight
        val wm = w - 1
        val hm = h - 1
        val wh = w * h
        val div = radius + radius + 1
        val r = IntArray(wh)
        val g = IntArray(wh)
        val b = IntArray(wh)
        var rsum: Int
        var gsum: Int
        var bsum: Int
        var x: Int
        var y: Int
        var i: Int
        var p: Int
        var p1: Int
        var p2: Int
        var yp: Int
        var yi: Int
        var yw: Int
        val vmin = IntArray(Math.max(w, h))
        val vmax = IntArray(Math.max(w, h))

        //img.getPixels(pix, 0, w, 0, 0, w, h)

        val dv = IntArray(256 * div)
        i = 0
        while (i < 256 * div) {
            dv[i] = i / div
            i++
        }

        yi = 0
        yw = yi

        y = 0
        while (y < h) {
            bsum = 0
            gsum = bsum
            rsum = gsum
            i = -radius
            while (i <= radius) {
                p = getPixelData(yi + Math.min(wm, Math.max(i, 0)))
                rsum += p and 0xff0000 shr 16
                gsum += p and 0x00ff00 shr 8
                bsum += p and 0x0000ff
                i++
            }
            x = 0
            while (x < w) {

                r[yi] = dv[rsum]
                g[yi] = dv[gsum]
                b[yi] = dv[bsum]

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm)
                    vmax[x] = Math.max(x - radius, 0)
                }
                p1 = getPixelData(yw + vmin[x])
                p2 = getPixelData(yw + vmax[x])

                rsum += (p1 and 0xff0000) - (p2 and 0xff0000) shr 16
                gsum += (p1 and 0x00ff00) - (p2 and 0x00ff00) shr 8
                bsum += (p1 and 0x0000ff) - (p2 and 0x0000ff)
                yi++
                x++
            }
            yw += w
            y++
        }

        x = 0
        while (x < w) {
            bsum = 0
            gsum = bsum
            rsum = gsum
            yp = -radius * w
            i = -radius
            while (i <= radius) {
                yi = Math.max(0, yp) + x
                rsum += r[yi]
                gsum += g[yi]
                bsum += b[yi]
                yp += w
                i++
            }
            yi = x
            y = 0
            while (y < h) {
                setPixelData(yi, dv[rsum].shl(24) or dv[gsum].shl(16) or dv[bsum].shl(8))

                if (x == 0) {
                    vmin[y] = Math.min(y + radius + 1, hm) * w
                    vmax[y] = Math.max(y - radius, 0) * w
                }
                p1 = x + vmin[y]
                p2 = x + vmax[y]

                rsum += r[p1] - r[p2]
                gsum += g[p1] - g[p2]
                bsum += b[p1] - b[p2]

                yi += w
                y++
            }
            x++
        }
    }

    fun fastBoxBlur(img: ImageBuffer, radius: Int) {

        /** 0xRRGGBBAA */
        fun getPixelData(index: Int): Int {
            val r = img.rgba[4 * index].toUint()
            val g = img.rgba[4 * index + 1].toUint()
            val b = img.rgba[4 * index + 2].toUint()

            return r.shl(16) or g.shl(8) or b
        }

        /** alpha will be passed through */
        fun setPixelData(index: Int, value: Int) {
            val r = value.ushr(24).and(0xff)
            val g = value.ushr(16).and(0xff)
            val b = value.ushr(8).and(0xff)

            img.rgba[4 * index] = r.toByte()
            img.rgba[4 * index + 1] = g.toByte()
            img.rgba[4 * index + 2] = b.toByte()
        }

        if (radius < 1) {
            return
        }
        val w = img.texWidth
        val h = img.texHeight
        val wm = w - 1
        val hm = h - 1
        val wh = w * h
        val div = radius + radius + 1
        val r = IntArray(wh)
        val g = IntArray(wh)
        val b = IntArray(wh)
        var rsum: Int
        var gsum: Int
        var bsum: Int
        var x: Int
        var y: Int
        var i: Int
        var p: Int
        var p1: Int
        var p2: Int
        var yp: Int
        var yi: Int
        var yw: Int
        val vmin = IntArray(Math.max(w, h))
        val vmax = IntArray(Math.max(w, h))

        //img.getPixels(pix, 0, w, 0, 0, w, h)

        val dv = IntArray(256 * div)
        i = 0
        while (i < 256 * div) {
            dv[i] = i / div
            i++
        }

        yi = 0
        yw = yi

        y = 0
        while (y < h) {
            bsum = 0
            gsum = bsum
            rsum = gsum
            i = -radius
            while (i <= radius) {
                p = getPixelData(yi + Math.min(wm, Math.max(i, 0)))
                rsum += p and 0xff0000 shr 16
                gsum += p and 0x00ff00 shr 8
                bsum += p and 0x0000ff
                i++
            }
            x = 0
            while (x < w) {

                r[yi] = dv[rsum]
                g[yi] = dv[gsum]
                b[yi] = dv[bsum]

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm)
                    vmax[x] = Math.max(x - radius, 0)
                }
                p1 = getPixelData(yw + vmin[x])
                p2 = getPixelData(yw + vmax[x])

                rsum += (p1 and 0xff0000) - (p2 and 0xff0000) shr 16
                gsum += (p1 and 0x00ff00) - (p2 and 0x00ff00) shr 8
                bsum += (p1 and 0x0000ff) - (p2 and 0x0000ff)
                yi++
                x++
            }
            yw += w
            y++
        }

        x = 0
        while (x < w) {
            bsum = 0
            gsum = bsum
            rsum = gsum
            yp = -radius * w
            i = -radius
            while (i <= radius) {
                yi = Math.max(0, yp) + x
                rsum += r[yi]
                gsum += g[yi]
                bsum += b[yi]
                yp += w
                i++
            }
            yi = x
            y = 0
            while (y < h) {
                setPixelData(yi, dv[rsum].shl(24) or dv[gsum].shl(16) or dv[bsum].shl(8))

                if (x == 0) {
                    vmin[y] = Math.min(y + radius + 1, hm) * w
                    vmax[y] = Math.max(y - radius, 0) * w
                }
                p1 = x + vmin[y]
                p2 = x + vmax[y]

                rsum += r[p1] - r[p2]
                gsum += g[p1] - g[p2]
                bsum += b[p1] - b[p2]

                yi += w
                y++
            }
            x++
        }
    }
}
