package net.torvald.terrarum.modulebasegame

import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.Screen
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.FrameBuffer

import net.torvald.dataclass.CircularArray
import net.torvald.terrarum.blockproperties.BlockPropUtil
import net.torvald.terrarum.blockstats.BlockStats
import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.console.*
import net.torvald.terrarum.gameactors.*
import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver
import net.torvald.terrarum.gamecontroller.IngameController
import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator
import net.torvald.terrarum.weather.WeatherMixer
import net.torvald.terrarum.worlddrawer.BlocksDrawer
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.worlddrawer.LightmapRenderer
import net.torvald.terrarum.worlddrawer.WorldCamera

import java.util.ArrayList
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

import javax.swing.JOptionPane

import com.badlogic.gdx.graphics.OrthographicCamera
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.modulebasegame.console.AVTracker
import net.torvald.terrarum.modulebasegame.console.ActorsList
import net.torvald.terrarum.console.Authenticator
import net.torvald.terrarum.console.SetGlobalLightOverride
import net.torvald.terrarum.itemproperties.ItemCodex
import net.torvald.terrarum.modulebasegame.gameactors.*
import net.torvald.terrarum.modulebasegame.imagefont.Watch7SegMain
import net.torvald.terrarum.modulebasegame.imagefont.WatchDotAlph
import net.torvald.terrarum.modulebasegame.ui.*
import net.torvald.terrarum.ui.*
import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
import net.torvald.terrarum.modulebasegame.worldgenerator.WorldGenerator
import kotlin.system.measureNanoTime


/**
 * Created by minjaesong on 2017-06-16.
 */

class Ingame(batch: SpriteBatch) : IngameInstance(batch) {


    private val ACTOR_UPDATE_RANGE = 4096

    lateinit var world: GameWorld
    lateinit var historicalFigureIDBucket: ArrayList<Int>

    /**
     * list of Actors that is sorted by Actors' referenceID
     */
    //val ACTORCONTAINER_INITIAL_SIZE = 64
    val PARTICLES_MAX = AppLoader.getConfigInt("maxparticles")
    //val actorContainer = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
    //val actorContainerInactive = ArrayList<Actor>(ACTORCONTAINER_INITIAL_SIZE)
    val particlesContainer = CircularArray<ParticleBase>(PARTICLES_MAX)
    val uiContainer = ArrayList<UICanvas>()

    private val actorsRenderBehind = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
    private val actorsRenderMiddle = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
    private val actorsRenderMidTop = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)
    private val actorsRenderFront  = ArrayList<ActorWithBody>(ACTORCONTAINER_INITIAL_SIZE)

    lateinit var playableActorDelegate: PlayableActorDelegate // player must exist; use dummy player if there is none (required for camera)
        private set
    inline val player: ActorHumanoid // currently POSSESSED actor :)
        get() = playableActorDelegate.actor

    //var screenZoom = 1.0f   // definition moved to IngameInstance
    //val ZOOM_MAXIMUM = 4.0f // definition moved to IngameInstance
    //val ZOOM_MINIMUM = 0.5f // definition moved to IngameInstance

    companion object {
        //val lightmapDownsample = 4f //2f: still has choppy look when the camera moves but unnoticeable when blurred


        /** Sets camera position so that (0,0) would be top-left of the screen, (width, height) be bottom-right. */
        fun setCameraPosition(batch: SpriteBatch, camera: Camera, newX: Float, newY: Float) {
            camera.position.set((-newX + AppLoader.halfScreenW).round(), (-newY + AppLoader.halfScreenH).round(), 0f)
            camera.update()
            batch.projectionMatrix = camera.combined
        }



        /**
         * Usage:
         *
         * override var referenceID: Int = generateUniqueReferenceID()
         */
        fun generateUniqueReferenceID(renderOrder: Actor.RenderOrder): ActorID {
            fun hasCollision(value: ActorID) =
                    try {
                        Terrarum.ingame!!.theGameHasActor(value) ||
                        value < ItemCodex.ACTORID_MIN ||
                        value !in when (renderOrder) {
                            Actor.RenderOrder.BEHIND -> Actor.RANGE_BEHIND
                            Actor.RenderOrder.MIDDLE -> Actor.RANGE_MIDDLE
                            Actor.RenderOrder.MIDTOP -> Actor.RANGE_MIDTOP
                            Actor.RenderOrder.FRONT  -> Actor.RANGE_FRONT
                        }
                    }
                    catch (gameNotInitialisedException: KotlinNullPointerException) {
                        false
                    }

            var ret: Int
            do {
                ret = HQRNG().nextInt().and(0x7FFFFFFF) // set new ID
            } while (hasCollision(ret)) // check for collision
            return ret
        }
    }


    private val worldFBOformat = if (Terrarum.environment == RunningEnvironment.MOBILE) Pixmap.Format.RGBA4444 else Pixmap.Format.RGBA8888
    private val lightFBOformat = Pixmap.Format.RGB888

    var worldDrawFrameBuffer = FrameBuffer(worldFBOformat, AppLoader.screenW, AppLoader.screenH, false)
    var worldGlowFrameBuffer = FrameBuffer(worldFBOformat, AppLoader.screenW, AppLoader.screenH, false)
    var worldBlendFrameBuffer = FrameBuffer(worldFBOformat, AppLoader.screenW, AppLoader.screenH, false)
    // RGB elements of Lightmap for Color  Vec4(R, G, B, 1.0)  24-bit
    private lateinit var lightmapFboA: FrameBuffer
    private lateinit var lightmapFboB: FrameBuffer


    init {
    }



    private val useShader: Boolean = false
    private val shaderProgram = 0

    val KEY_LIGHTMAP_RENDER = Input.Keys.F7



    lateinit var debugWindow: UICanvas
    lateinit var notifier: UICanvas

    lateinit var uiPieMenu: UICanvas
    lateinit var uiQuickBar: UICanvas
    lateinit var uiInventoryPlayer: UICanvas
    lateinit var uiInventoryContainer: UICanvas
    lateinit var uiVitalPrimary: UICanvas
    lateinit var uiVitalSecondary: UICanvas
    lateinit var uiVitalItem: UICanvas // itemcount/durability of held block or active ammo of held gun. As for the block, max value is 500.

    private lateinit var uiWatchBasic: UICanvas
    private lateinit var uiWatchTierOne: UICanvas

    private lateinit var uiTooltip: UITooltip

    lateinit var uiCheatMotherfuckerNootNoot: UICheatDetected

    // UI aliases
    lateinit var uiAliases: ArrayList<UICanvas>
        private set
    lateinit var uiAlasesPausing: ArrayList<UICanvas>
        private set

    inline val paused: Boolean
        get() = uiAlasesPausing.map { if (it.isOpened) return true else 0 }.isEmpty() // isEmply is always false, which we want
    /**
     * Set to false if UI is opened; set to true  if UI is closed.
     */
    inline val canPlayerControl: Boolean
        get() = !paused // FIXME temporary behab (block movement if the game is paused or paused by UIs)

    var particlesActive = 0
        private set



    private lateinit var ingameUpdateThread: ThreadIngameUpdate
    private lateinit var updateThreadWrapper: Thread
    //private val ingameDrawThread: ThreadIngameDraw // draw must be on the main thread


    var gameInitialised = false
        private set
    var gameFullyLoaded = false
        private set


    private val TILE_SIZEF = CreateTileAtlas.TILE_SIZE.toFloat()

    //////////////
    // GDX code //
    //////////////



    // invert Y
    fun initViewPort(width: Int, height: Int) {
        // Set Y to point downwards
        camera.setToOrtho(true, width.toFloat(), height.toFloat())

        // Update camera matrix
        camera.update()

        // Set viewport to restrict drawing
        Gdx.gl20.glViewport(0, 0, width, height)
    }


    lateinit var gameLoadMode: GameLoadMode
    lateinit var gameLoadInfoPayload: Any

    enum class GameLoadMode {
        CREATE_NEW, LOAD_FROM
    }

    override fun show() {
        //initViewPort(AppLoader.screenW, AppLoader.screenH)

        // gameLoadMode and gameLoadInfoPayload must be set beforehand!!

        when (gameLoadMode) {
            GameLoadMode.CREATE_NEW -> enter(gameLoadInfoPayload as NewWorldParameters)
            GameLoadMode.LOAD_FROM  -> enter(gameLoadInfoPayload as GameSaveData)
        }

        LightmapRenderer.world = this.world
        //BlocksDrawer.world = this.world
        FeaturesDrawer.world = this.world

        gameInitialised = true
    }

    data class GameSaveData(
            val world: GameWorld,
            val historicalFigureIDBucket: ArrayList<Int>,
            val realGamePlayer: ActorHumanoid
    )

    data class NewWorldParameters(
            val width: Int,
            val height: Int,
            val worldGenSeed: Long
            // other worldgen options
    )

    /**
     * Init instance by loading saved world
     */
    private fun enter(gameSaveData: GameSaveData) {
        if (gameInitialised) {
            println("[Ingame] loaded successfully.")
        }
        else {
            LoadScreen.addMessage("Loading world from save")


            world = gameSaveData.world
            historicalFigureIDBucket = gameSaveData.historicalFigureIDBucket
            playableActorDelegate = PlayableActorDelegate(gameSaveData.realGamePlayer)
            addNewActor(player)



            //initGame()
        }
    }

    /**
     * Init instance by creating new world
     */
    private fun enter(worldParams: NewWorldParameters) {
        if (gameInitialised) {
            println("[Ingame] loaded successfully.")
        }
        else {
            LoadScreen.addMessage("${Terrarum.NAME} version ${AppLoader.getVERSION_STRING()}")
            LoadScreen.addMessage("Creating new world")


            // init map as chosen size
            world = GameWorld(worldParams.width, worldParams.height)

            // generate terrain for the map
            WorldGenerator.attachMap(world)
            WorldGenerator.SEED = worldParams.worldGenSeed
            WorldGenerator.generateMap()


            historicalFigureIDBucket = ArrayList<Int>()


            RoguelikeRandomiser.seed = HQRNG().nextLong()


            // add new player and put it to actorContainer
            //playableActorDelegate = PlayableActorDelegate(PlayerBuilderSigrid())
            //playableActorDelegate = PlayableActorDelegate(PlayerBuilderTestSubject1())
            //addNewActor(player)


            // test actor
            //addNewActor(PlayerBuilderCynthia())


            //initGame()
        }
    }

    private val ingameController = IngameController(this)

    /** Load rest of the game with GL context */
    fun postInit() {
        //LightmapRenderer.world = this.world
        BlocksDrawer.world = this.world
        //FeaturesDrawer.world = this.world


        MegaRainGovernor // invoke MegaRain Governor



        Gdx.input.inputProcessor = ingameController


        initViewPort(AppLoader.screenW, AppLoader.screenH)


        // init console window
        consoleHandler = ConsoleWindow()
        consoleHandler.setPosition(0, 0)


        // init debug window
        debugWindow = BasicDebugInfoWindow()
        debugWindow.setPosition(0, 0)

        // init notifier
        notifier = Notification()
        notifier.setPosition(
                (AppLoader.screenW - notifier.width) / 2, AppLoader.screenH - notifier.height)




        // >- queue up game UIs that should pause the world -<
        // inventory
        /*uiInventoryPlayer = UIInventory(player,
                width = 900,
                height = AppLoader.screenH - 160,
                categoryWidth = 210,
                toggleKeyLiteral = AppLoader.getConfigInt("keyinventory")
        )*/
        /*uiInventoryPlayer.setPosition(
                -uiInventoryPlayer.width,
                70
        )*/
        uiInventoryPlayer = UIInventoryFull(player,
                toggleKeyLiteral = AppLoader.getConfigInt("keyinventory")
        )
        uiInventoryPlayer.setPosition(0, 0)

        // >- lesser UIs -<
        // quick bar
        uiQuickBar = UIQuickBar()
        uiQuickBar.isVisible = true
        uiQuickBar.setPosition((AppLoader.screenW - uiQuickBar.width) / 2 + 12, -10)

        // pie menu
        uiPieMenu = UIPieMenu()
        uiPieMenu.setPosition(AppLoader.halfScreenW, AppLoader.halfScreenH)

        // vital metre
        // fill in getter functions by
        //      (uiAliases[UI_QUICK_BAR]!!.UI as UIVitalMetre).vitalGetterMax = { some_function }
        //uiVitalPrimary = UIVitalMetre(player, { 80f }, { 100f }, Color.red, 2, customPositioning = true)
        //uiVitalPrimary.setAsAlwaysVisible()
        //uiVitalSecondary = UIVitalMetre(player, { 73f }, { 100f }, Color(0x00dfff), 1) customPositioning = true)
        //uiVitalSecondary.setAsAlwaysVisible()
        //uiVitalItem = UIVitalMetre(player, { null }, { null }, Color(0xffcc00), 0, customPositioning = true)
        //uiVitalItem.setAsAlwaysVisible()

        // basic watch-style notification bar (temperature, new mail)
        uiWatchBasic = UIBasicNotifier(player)
        uiWatchBasic.setAsAlwaysVisible()
        uiWatchBasic.setPosition(AppLoader.screenW - uiWatchBasic.width, 0)

        uiWatchTierOne = UITierOneWatch(player)
        uiWatchTierOne.setAsAlwaysVisible()
        uiWatchTierOne.setPosition(AppLoader.screenW - uiWatchTierOne.width, uiWatchBasic.height - 2)


        uiTooltip = UITooltip()


        uiCheatMotherfuckerNootNoot = UICheatDetected()


        // batch-process uiAliases
        uiAliases = arrayListOf(
                // drawn first
                //uiVitalPrimary,
                //uiVitalSecondary,
                //uiVitalItem,

                uiPieMenu,
                uiQuickBar,
                uiWatchBasic,
                uiWatchTierOne,
                uiTooltip
                // drawn last
        )
        uiAlasesPausing = arrayListOf(
                uiInventoryPlayer,
                //uiInventoryContainer,
                consoleHandler,
                uiCheatMotherfuckerNootNoot
        )
        uiAlasesPausing.forEach { addUI(it) } // put them all to the UIContainer
        uiAliases.forEach { addUI(it) } // put them all to the UIContainer



        ingameUpdateThread = ThreadIngameUpdate(this)
        updateThreadWrapper = Thread(ingameUpdateThread, "Terrarum UpdateThread")



        LightmapRenderer.fireRecalculateEvent()





        // some sketchy test code here



    }// END enter


    protected var updateDeltaCounter = 0.0
    protected val updateRate = 1.0 / Terrarum.TARGET_INTERNAL_FPS

    private var firstTimeRun = true

    ///////////////
    // prod code //
    ///////////////
    private class ThreadIngameUpdate(val ingame: Ingame): Runnable {
        override fun run() {
            var updateTries = 0
            while (ingame.updateDeltaCounter >= ingame.updateRate) {
                ingame.updateGame(AppLoader.UPDATE_RATE)
                ingame.updateDeltaCounter -= ingame.updateRate
                updateTries++

                if (updateTries >= Terrarum.UPDATE_CATCHUP_MAX_TRIES) {
                    break
                }
            }
        }
    }

    override fun render(delta: Float) {
        // Q&D solution for LoadScreen and Ingame, where while LoadScreen is working, Ingame now no longer has GL Context
        // there's still things to load which needs GL context to be present
        if (!gameFullyLoaded) {

            if (gameLoadMode == GameLoadMode.CREATE_NEW) {
                playableActorDelegate = PlayableActorDelegate(PlayerBuilderSigrid())

                // go to spawn position
                player.setPosition(
                        world.spawnX * CreateTileAtlas.TILE_SIZE.toDouble(),
                        world.spawnY * CreateTileAtlas.TILE_SIZE.toDouble()
                )

                addNewActor(player)
            }

            postInit()


            gameFullyLoaded = true
        }





        Gdx.graphics.setTitle(AppLoader.GAME_NAME +
                              " $EMDASH F: ${Gdx.graphics.framesPerSecond} (${Terrarum.TARGET_INTERNAL_FPS})" +
                              " $EMDASH M: ${Terrarum.memInUse}M / ${Terrarum.memTotal}M / ${Terrarum.memXmx}M"
        )

        // ASYNCHRONOUS UPDATE AND RENDER //


        /** UPDATE CODE GOES HERE */
        updateDeltaCounter += delta



        if (false && AppLoader.getConfigBoolean("multithread")) { // NO MULTITHREADING: camera don't like concurrent modification (jittery actor movements)
            if (firstTimeRun || updateThreadWrapper.state == Thread.State.TERMINATED) {
                updateThreadWrapper = Thread(ingameUpdateThread, "Terrarum UpdateThread")
                updateThreadWrapper.start()

                if (firstTimeRun) firstTimeRun = false
            }
            // else, NOP;
        }
        else {
            var updateTries = 0
            while (updateDeltaCounter >= updateRate) {

                //updateGame(delta)
                AppLoader.measureDebugTime("Ingame.update") { updateGame(delta) }

                updateDeltaCounter -= updateRate
                updateTries++

                if (updateTries >= Terrarum.UPDATE_CATCHUP_MAX_TRIES) {
                    break
                }
            }
        }



        /** RENDER CODE GOES HERE */
        //renderGame(batch)
        AppLoader.measureDebugTime("Ingame.render") { renderGame(batch) }
    }

    protected fun updateGame(delta: Float) {
        particlesActive = 0


        KeyToggler.update()
        ingameController.update(delta)


        if (!paused) {

            ///////////////////////////
            // world-related updates //
            ///////////////////////////
            BlockPropUtil.dynamicLumFuncTickClock()
            world.updateWorldTime(delta)
            //WorldSimulator(player, delta)
            WeatherMixer.update(delta, player)
            BlockStats.update()
            if (!(CommandDict["setgl"] as SetGlobalLightOverride).lightOverride)
                world.globalLight = WeatherMixer.globalLightNow


            ////////////////////////////
            // camera-related updates //
            ////////////////////////////
            FeaturesDrawer.update(delta)
            WorldCamera.update(world, player)



            ///////////////////////////
            // actor-related updates //
            ///////////////////////////
            repossessActor()

            // determine whether the inactive actor should be activated
            wakeDormantActors()
            // determine whether the actor should keep being activated or be dormant
            KillOrKnockdownActors()
            updateActors(delta)
            particlesContainer.forEach { if (!it.flagDespawn) particlesActive++; it.update(delta) }
            // TODO thread pool(?)
            CollisionSolver.process()
        }


        ////////////////////////
        // ui-related updates //
        ////////////////////////
        uiContainer.forEach { it.update(delta) }
        debugWindow.update(delta)
        notifier.update(delta)

        // update debuggers using javax.swing //
        if (Authenticator.b()) {
            AVTracker.update()
            ActorsList.update()
        }
    }


    private fun renderGame(batch: SpriteBatch) {
        Gdx.gl.glClearColor(.094f, .094f, .094f, 0f)
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
        Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
        Gdx.gl.glEnable(GL20.GL_BLEND)
        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)

        //camera.position.set(-WorldCamera.x.toFloat(), -WorldCamera.y.toFloat(), 0f) // make camara work
        //camera.position.set(0f, 0f, 0f) // make camara work
        //batch.projectionMatrix = camera.combined



        LightmapRenderer.fireRecalculateEvent()



        worldBlendFrameBuffer.inAction(null, null) {
            Gdx.gl.glClearColor(0f,0f,0f,0f)
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
            Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
            Gdx.gl.glEnable(GL20.GL_BLEND)
            Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
        }
        worldDrawFrameBuffer.inAction(null, null) {
            Gdx.gl.glClearColor(0f,0f,0f,0f)
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
            Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
            Gdx.gl.glEnable(GL20.GL_BLEND)
            Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
        }
        worldGlowFrameBuffer.inAction(null, null) {
            Gdx.gl.glClearColor(0f,0f,0f,1f)
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
            Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
            Gdx.gl.glEnable(GL20.GL_BLEND)
            Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
        }


        fun moveCameraToWorldCoord() {
            // using custom code for camera; this is obscure and tricky
            camera.position.set(WorldCamera.gdxCamX, WorldCamera.gdxCamY, 0f) // make camara work
            camera.update()
            batch.projectionMatrix = camera.combined
        }


        ///////////////////////////
        // draw world to the FBO //
        ///////////////////////////
        processBlur(lightmapFboA, lightmapFboB, LightmapRenderer.DRAW_FOR_RGB)

        worldDrawFrameBuffer.inAction(camera, batch) {


            // draw-with-poly doesn't want to co-op with peasant spriteBatch... (it hides sprites)

            batch.inUse {
                batch.shader = null
                batch.color = Color.WHITE
                blendNormal()
            }



            setCameraPosition(0f, 0f)
            BlocksDrawer.renderWall(batch)



            batch.inUse {
                moveCameraToWorldCoord()
                actorsRenderBehind.forEach { it.drawBody(batch) }
                particlesContainer.forEach { it.drawBody(batch) }
            }



            setCameraPosition(0f, 0f)
            BlocksDrawer.renderTerrain(batch)



            batch.inUse {
                /////////////////
                // draw actors //
                /////////////////
                moveCameraToWorldCoord()
                actorsRenderMiddle.forEach { it.drawBody(batch) }
                actorsRenderMidTop.forEach { it.drawBody(batch) }
                player.drawBody(batch)
                actorsRenderFront.forEach { it.drawBody(batch) }
                // --> Change of blend mode <-- introduced by children of ActorWithBody //


                /////////////////////////////
                // draw map related stuffs //
                /////////////////////////////
            }



            setCameraPosition(0f, 0f)
            BlocksDrawer.renderFront(batch, false)



            batch.inUse {
                // --> blendNormal() <-- by BlocksDrawer.renderFront
                FeaturesDrawer.drawEnvOverlay(batch)




                // mix lighpmap canvas to this canvas (Colors -- RGB channel)
                if (!KeyToggler.isOn(Input.Keys.F6)) { // F6 to disable lightmap draw
                    setCameraPosition(0f, 0f)
                    batch.shader = Terrarum.shaderBayer
                    batch.shader.setUniformf("rcount", 64f)
                    batch.shader.setUniformf("gcount", 64f)
                    batch.shader.setUniformf("bcount", 64f) // de-banding

                    val lightTex = lightmapFboB.colorBufferTexture // A or B? flipped in Y means you chose wrong buffer; use one that works correctly
                    lightTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest) // blocky feeling for A E S T H E T I C S

                    if (KeyToggler.isOn(KEY_LIGHTMAP_RENDER)) blendNormal()
                    else blendMul()

                    batch.color = Color.WHITE
                    val xrem = -(WorldCamera.x.toFloat() fmod TILE_SIZEF)
                    val yrem = -(WorldCamera.y.toFloat() fmod TILE_SIZEF)
                    batch.draw(lightTex,
                            xrem,
                            yrem,
                            lightTex.width * lightmapDownsample, lightTex.height * lightmapDownsample
                            //lightTex.width.toFloat(), lightTex.height.toFloat() // for debugging
                    )

                }


                Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // don't know why it is REALLY needed; it really depresses me
                batch.shader = null


                // move camera back to its former position
                // using custom code for camera; this is obscure and tricky
                camera.position.set(WorldCamera.gdxCamX, WorldCamera.gdxCamY, 0f) // make camara work
                camera.update()
                batch.projectionMatrix = camera.combined

            }
        }


        //////////////////////////
        // draw glow to the FBO //
        //////////////////////////
        processBlur(lightmapFboA, lightmapFboB, LightmapRenderer.DRAW_FOR_ALPHA)

        worldGlowFrameBuffer.inAction(camera, batch) {
            batch.inUse {
                batch.shader = null


                batch.color = Color.WHITE
                blendNormal()



                //////////////////////
                // draw actor glows //
                //////////////////////
                moveCameraToWorldCoord()
                actorsRenderBehind.forEach { it.drawGlow(batch) }
                particlesContainer.forEach { it.drawGlow(batch) }
                actorsRenderMiddle.forEach { it.drawGlow(batch) }
                actorsRenderMidTop.forEach { it.drawGlow(batch) }
                player.drawGlow(batch)
                actorsRenderFront.forEach { it.drawGlow(batch) }
                // --> blendNormal() <-- introduced by childs of ActorWithBody //



                // mix lighpmap canvas to this canvas (UV lights -- A channel written on RGB as greyscale image)
                if (!KeyToggler.isOn(Input.Keys.F6)) { // F6 to disable lightmap draw
                    setCameraPosition(0f, 0f)
                    batch.shader = Terrarum.shaderBayer
                    batch.shader.setUniformf("rcount", 64f)
                    batch.shader.setUniformf("gcount", 64f)
                    batch.shader.setUniformf("bcount", 64f) // de-banding

                    val lightTex = lightmapFboB.colorBufferTexture // A or B? flipped in Y means you chose wrong buffer; use one that works correctly
                    lightTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest) // blocky feeling for A E S T H E T I C S

                    if (KeyToggler.isOn(KEY_LIGHTMAP_RENDER)) blendNormal()
                    else blendMul()

                    batch.color = Color.WHITE
                    val xrem = -(WorldCamera.x.toFloat() fmod TILE_SIZEF)
                    val yrem = -(WorldCamera.y.toFloat() fmod TILE_SIZEF)
                    batch.draw(lightTex,
                            xrem,
                            yrem,
                            lightTex.width * lightmapDownsample, lightTex.height * lightmapDownsample
                            //lightTex.width.toFloat(), lightTex.height.toFloat() // for debugging
                    )

                }


                blendNormal()
            }
        }


        worldBlendFrameBuffer.inAction(camera, batch) {
            Gdx.gl.glClearColor(0f, 0f, 0f, 0f)
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)


            // draw blended world
            val worldTex = worldDrawFrameBuffer.colorBufferTexture // WORLD: light_color must be applied beforehand
            val glowTex = worldGlowFrameBuffer.colorBufferTexture // GLOW: light_uvlight must be applied beforehand

            worldTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
            glowTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)

            worldTex.bind(0)
            glowTex.bind(1)


            Terrarum.shaderBlendGlow.begin()
            Terrarum.shaderBlendGlow.setUniformMatrix("u_projTrans", camera.combined)
            Terrarum.shaderBlendGlow.setUniformi("u_texture", 0)
            Terrarum.shaderBlendGlow.setUniformi("tex1", 1)
            Terrarum.fullscreenQuad.render(Terrarum.shaderBlendGlow, GL20.GL_TRIANGLES)
            Terrarum.shaderBlendGlow.end()



            Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // don't know why it is REALLY needed; it really depresses me


            batch.inUse {
                batch.color = Color.WHITE
                blendNormal()

                batch.shader = null
            }
        }


        /////////////////////////
        // draw to main screen //
        /////////////////////////
        camera.setToOrtho(true, AppLoader.screenW.toFloat(), AppLoader.screenH.toFloat())
        batch.projectionMatrix = camera.combined
        batch.inUse {

            batch.shader = null

            setCameraPosition(0f, 0f)
            batch.color = Color.WHITE
            blendNormal()


            ///////////////////////////
            // draw skybox to screen //
            ///////////////////////////

            WeatherMixer.render(camera, world)




            /////////////////////////////////
            // draw framebuffers to screen //
            /////////////////////////////////



            val blendedTex = worldBlendFrameBuffer.colorBufferTexture
            blendedTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
            batch.color = Color.WHITE
            batch.shader = null
            blendNormal()
            batch.draw(blendedTex, 0f, 0f, blendedTex.width.toFloat(), blendedTex.height.toFloat())





            // an old code.
            /*batch.shader = null
            val worldTex = worldDrawFrameBuffer.colorBufferTexture // WORLD: light_color must be applied beforehand
            val glowTex = worldGlowFrameBuffer.colorBufferTexture // GLOW: light_uvlight must be applied beforehand


            batch.draw(worldTex, 0f, 0f, worldTex.width.toFloat(), worldTex.height.toFloat())*/



            batch.shader = null

            ////////////////////////
            // debug informations //
            ////////////////////////

            blendNormal()
            // draw reference ID if debugWindow is open
            if (debugWindow.isVisible) {

                actorContainer.forEachIndexed { i, actor ->
                    if (actor is ActorWithBody) {
                        batch.color = Color.WHITE
                        AppLoader.fontSmallNumbers.draw(batch,
                                actor.referenceID.toString(),
                                actor.hitbox.startX.toFloat(),
                                actor.hitbox.canonicalY.toFloat() + 4
                        )
                    }
                }
            }
            // debug physics
            if (KeyToggler.isOn(Input.Keys.F11)) {
                actorContainer.forEachIndexed { i, actor ->
                    if (actor is ActorWithPhysics) {
                        /*shapeRenderer.inUse(ShapeRenderer.ShapeType.Line) {
                            shapeRenderer.color = Color(1f, 0f, 1f, 1f)
                            //shapeRenderer.lineWidth = 1f
                            shapeRenderer.rect(
                                    actor.hitbox.startX.toFloat(),
                                    actor.hitbox.startY.toFloat(),
                                    actor.hitbox.width.toFloat(),
                                    actor.hitbox.height.toFloat()
                            )
                        }*/

                        // velocity
                        batch.color = Color.CHARTREUSE//GameFontBase.codeToCol["g"]
                        AppLoader.fontSmallNumbers.draw(batch,
                                "${0x7F.toChar()}X ${actor.externalForce.x}",
                                actor.hitbox.startX.toFloat(),
                                actor.hitbox.canonicalY.toFloat() + 4 + 8
                        )
                        AppLoader.fontSmallNumbers.draw(batch,
                                "${0x7F.toChar()}Y ${actor.externalForce.y}",
                                actor.hitbox.startX.toFloat(),
                                actor.hitbox.canonicalY.toFloat() + 4 + 8 * 2
                        )
                    }
                }
            }
            // fluidmap debug
            if (KeyToggler.isOn(Input.Keys.F4)) {
                WorldSimulator.drawFluidMapDebug(batch)
            }




            /////////////////////////////
            // draw some overlays (UI) //
            /////////////////////////////

            uiContainer.forEach {
                if (it != consoleHandler) {
                    batch.color = Color.WHITE
                    it.render(batch, camera)
                }
            }

            debugWindow.render(batch, camera)
            // make sure console draws on top of other UIs
            consoleHandler.render(batch, camera)
            notifier.render(batch, camera)


            blendNormal()
        }

    }

    fun processBlur(lightmapFboA: FrameBuffer, lightmapFboB: FrameBuffer, mode: Int) {
        val blurIterations = 5 // ideally, 4 * radius; must be even/odd number -- odd/even number will flip the image
        val blurRadius = 4f / lightmapDownsample // (5, 4f); using low numbers for pixel-y aesthetics

        var blurWriteBuffer = lightmapFboA
        var blurReadBuffer = lightmapFboB


        lightmapFboA.inAction(null, null) {
            Gdx.gl.glClearColor(0f, 0f, 0f, 0f)
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
        }
        lightmapFboB.inAction(null, null) {
            Gdx.gl.glClearColor(0f, 0f, 0f, 0f)
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
        }


        if (mode == LightmapRenderer.DRAW_FOR_RGB) {
            // initialise readBuffer with untreated lightmap
            blurReadBuffer.inAction(camera, batch) {
                batch.inUse {
                    blendNormal(batch)
                    batch.color = Color.WHITE
                    LightmapRenderer.draw(batch, LightmapRenderer.DRAW_FOR_RGB)
                }
            }
        }
        else {
            // initialise readBuffer with untreated lightmap
            blurReadBuffer.inAction(camera, batch) {
                batch.inUse {
                    blendNormal(batch)
                    batch.color = Color.WHITE
                    LightmapRenderer.draw(batch, LightmapRenderer.DRAW_FOR_ALPHA)
                }
            }
        }



        if (!KeyToggler.isOn(Input.Keys.F8)) {
            for (i in 0 until blurIterations) {
                blurWriteBuffer.inAction(camera, batch) {

                    batch.inUse {
                        val texture = blurReadBuffer.colorBufferTexture

                        texture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)


                        batch.shader = Terrarum.shaderBlur
                        batch.shader.setUniformf("iResolution",
                                blurWriteBuffer.width.toFloat(), blurWriteBuffer.height.toFloat())
                        batch.shader.setUniformf("flip", 1f)
                        if (i % 2 == 0)
                            batch.shader.setUniformf("direction", blurRadius, 0f)
                        else
                            batch.shader.setUniformf("direction", 0f, blurRadius)


                        batch.color = Color.WHITE
                        batch.draw(texture, 0f, 0f)


                        // swap
                        val t = blurWriteBuffer
                        blurWriteBuffer = blurReadBuffer
                        blurReadBuffer = t
                    }
                }
            }
        }
        else {
            blurWriteBuffer = blurReadBuffer
        }
    }



    private fun repossessActor() {
        // check if currently pocessed actor is removed from game
        if (!theGameHasActor(player)) {
            // re-possess canonical player
            if (theGameHasActor(Player.PLAYER_REF_ID))
                changePossession(Player.PLAYER_REF_ID)
            else
                changePossession(0x51621D) // FIXME fallback debug mode (FIXME is there for a reminder visible in ya IDE)
        }
    }

    private fun changePossession(newActor: PlayableActorDelegate) {
        if (!theGameHasActor(player)) {
            throw IllegalArgumentException("No such actor in the game: $newActor")
        }

        playableActorDelegate = newActor
        WorldSimulator(player, AppLoader.UPDATE_RATE)
    }

    private fun changePossession(refid: Int) {
        // TODO prevent possessing other player on multiplayer

        if (!theGameHasActor(refid)) {
            throw IllegalArgumentException(
                    "No such actor in the game: $refid (elemsActive: ${actorContainer.size}, " +
                    "elemsInactive: ${actorContainerInactive.size})")
        }

        // take care of old delegate
        playableActorDelegate!!.actor.collisionType = HumanoidNPC.DEFAULT_COLLISION_TYPE
        // accept new delegate
        playableActorDelegate = PlayableActorDelegate(getActorByID(refid) as ActorHumanoid)
        playableActorDelegate!!.actor.collisionType = ActorWithPhysics.COLLISION_KINEMATIC
        WorldSimulator(player, AppLoader.UPDATE_RATE)
    }

    /** Send message to notifier UI and toggle the UI as opened. */
    fun sendNotification(msg: Array<String>) {
        (notifier as Notification).sendNotification(msg)
    }

    fun wakeDormantActors() {
        var actorContainerSize = actorContainerInactive.size
        var i = 0
        while (i < actorContainerSize) { // loop through actorContainerInactive
            val actor = actorContainerInactive[i]
            if (actor is ActorWithBody && actor.inUpdateRange()) {
                activateDormantActor(actor) // duplicates are checked here
                actorContainerSize -= 1
                i-- // array removed 1 elem, so we also decrement counter by 1
            }
            i++
        }
    }

    /**
     * determine whether the actor should be active or dormant by its distance from the player.
     * If the actor must be dormant, the target actor will be put to the list specifically for them.
     * if the actor is not to be dormant, it will be just ignored.
     */
    fun KillOrKnockdownActors() {
        var actorContainerSize = actorContainer.size
        var i = 0
        while (i < actorContainerSize) { // loop through actorContainer
            val actor = actorContainer[i]
            val actorIndex = i
            // kill actors flagged to despawn
            if (actor.flagDespawn) {
                removeActor(actor)
                actorContainerSize -= 1
                i-- // array removed 1 elem, so we also decrement counter by 1
            }
            // inactivate distant actors
            else if (actor is ActorWithBody && !actor.inUpdateRange()) {
                if (actor !is Projectile) { // if it's a projectile, don't inactivate it; just kill it.
                    actorContainerInactive.add(actor) // naïve add; duplicates are checked when the actor is re-activated
                }
                actorContainer.removeAt(actorIndex)
                actorContainerSize -= 1
                i-- // array removed 1 elem, so we also decrement counter by 1
            }
            i++
        }
    }

    /**
     * Update actors concurrently.
     *
     * NOTE: concurrency for actor updating is currently disabled because of it's poor performance
     */
    fun updateActors(delta: Float) {
        if (false) { // don't multithread this for now, it's SLOWER //if (Terrarum.MULTITHREAD && actorContainer.size > Terrarum.THREADS) {
            val actors = actorContainer.size.toFloat()
            // set up indices
            for (i in 0..Terrarum.THREADS - 1) {
                ThreadParallel.map(
                        i,
                        ThreadActorUpdate(
                                actors.div(Terrarum.THREADS).times(i).roundInt(),
                                actors.div(Terrarum.THREADS).times(i.plus(1)).roundInt() - 1
                        ),
                        "ActorUpdate"
                )
            }

            ThreadParallel.startAll()

            playableActorDelegate?.update(delta)
        }
        else {
            actorContainer.forEach {
                if (it != playableActorDelegate?.actor) {
                    it.update(delta)

                    if (it is Pocketed) {
                        it.inventory.forEach { inventoryEntry ->
                            inventoryEntry.item.effectWhileInPocket(delta)
                            if (it.equipped(inventoryEntry.item)) {
                                inventoryEntry.item.effectWhenEquipped(delta)
                            }
                        }
                    }
                }
            }
            playableActorDelegate?.update(delta)
            //AmmoMeterProxy(player, uiVitalItem.UI as UIVitalMetre)
        }
    }

    fun Double.sqr() = this * this
    fun Int.sqr() = this * this
    fun min(vararg d: Double): Double {
        var ret = Double.MAX_VALUE
        d.forEach { if (it < ret) ret = it }
        return ret
    }
    private fun distToActorSqr(a: ActorWithBody, p: ActorWithBody) =
            min(// take min of normal position and wrapped (x < 0) position
                    (a.hitbox.centeredX - p.hitbox.centeredX).sqr() +
                    (a.hitbox.centeredY - p.hitbox.centeredY).sqr(),
                    (a.hitbox.centeredX - p.hitbox.centeredX + world.width * CreateTileAtlas.TILE_SIZE).sqr() +
                    (a.hitbox.centeredY - p.hitbox.centeredY).sqr(),
                    (a.hitbox.centeredX - p.hitbox.centeredX - world.width * CreateTileAtlas.TILE_SIZE).sqr() +
                    (a.hitbox.centeredY - p.hitbox.centeredY).sqr()
            )
    private fun distToCameraSqr(a: ActorWithBody) =
            min(
                    (a.hitbox.startX - WorldCamera.x).sqr() +
                    (a.hitbox.startY - WorldCamera.y).sqr(),
                    (a.hitbox.startX - WorldCamera.x + world.width * CreateTileAtlas.TILE_SIZE).sqr() +
                    (a.hitbox.startY - WorldCamera.y).sqr(),
                    (a.hitbox.startX - WorldCamera.x - world.width * CreateTileAtlas.TILE_SIZE).sqr() +
                    (a.hitbox.startY - WorldCamera.y).sqr()
            )

    /** whether the actor is within screen */
    private fun ActorWithBody.inScreen() =
            distToCameraSqr(this) <=
            (AppLoader.screenW.plus(this.hitbox.width.div(2)).
                    times(1 / screenZoom).sqr() +
             AppLoader.screenH.plus(this.hitbox.height.div(2)).
                     times(1 / screenZoom).sqr())


    /** whether the actor is within update range */
    private fun ActorWithBody.inUpdateRange() = distToCameraSqr(this) <= ACTOR_UPDATE_RANGE.sqr()

    override fun removeActor(ID: Int) = removeActor(getActorByID(ID))
    /**
     * get index of the actor and delete by the index.
     * we can do this as the list is guaranteed to be sorted
     * and only contains unique values.
     *
     * Any values behind the index will be automatically pushed to front.
     * This is how remove function of [java.util.ArrayList] is defined.
     */
    override fun removeActor(actor: Actor) {
        if (actor.referenceID == player.referenceID || actor.referenceID == 0x51621D) // do not delete this magic
            throw RuntimeException("Attempted to remove player.")
        val indexToDelete = actorContainer.binarySearch(actor.referenceID!!)
        if (indexToDelete >= 0) {
            actorContainer.removeAt(indexToDelete)

            // indexToDelete >= 0 means that the actor certainly exists in the game
            // which means we don't need to check if i >= 0 again
            if (actor is ActorWithBody) {
                when (actor.renderOrder) {
                    Actor.RenderOrder.BEHIND -> {
                        val i = actorsRenderBehind.binarySearch(actor.referenceID!!)
                        actorsRenderBehind.removeAt(i)
                    }
                    Actor.RenderOrder.MIDDLE -> {
                        val i = actorsRenderMiddle.binarySearch(actor.referenceID!!)
                        actorsRenderMiddle.removeAt(i)
                    }
                    Actor.RenderOrder.MIDTOP -> {
                        val i = actorsRenderMidTop.binarySearch(actor.referenceID!!)
                        actorsRenderMidTop.removeAt(i)
                    }
                    Actor.RenderOrder.FRONT  -> {
                        val i = actorsRenderFront.binarySearch(actor.referenceID!!)
                        actorsRenderFront.removeAt(i)
                    }
                }
            }
        }
    }

    /**
     * Check for duplicates, append actor and sort the list
     */
    override fun addNewActor(actor: Actor) {
        if (theGameHasActor(actor.referenceID!!)) {
            throw Error("The actor $actor already exists in the game")
        }
        else {
            actorContainer.add(actor)
            insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor

            if (actor is ActorWithBody) {
                when (actor.renderOrder) {
                    Actor.RenderOrder.BEHIND -> {
                        actorsRenderBehind.add(actor); insertionSortLastElemAV(actorsRenderBehind)
                    }
                    Actor.RenderOrder.MIDDLE -> {
                        actorsRenderMiddle.add(actor); insertionSortLastElemAV(actorsRenderMiddle)
                    }
                    Actor.RenderOrder.MIDTOP -> {
                        actorsRenderMidTop.add(actor); insertionSortLastElemAV(actorsRenderMidTop)
                    }
                    Actor.RenderOrder.FRONT  -> {
                        actorsRenderFront.add(actor); insertionSortLastElemAV(actorsRenderFront)
                    }
                }
            }
        }
    }

    fun activateDormantActor(actor: Actor) {
        if (!isInactive(actor.referenceID!!)) {
            if (isActive(actor.referenceID!!))
                throw Error("The actor $actor is already activated")
            else
                throw Error("The actor $actor already exists in the game")
        }
        else {
            actorContainerInactive.remove(actor)
            actorContainer.add(actor)
            insertionSortLastElem(actorContainer) // we can do this as we are only adding single actor

            if (actor is ActorWithBody) {
                when (actor.renderOrder) {
                    Actor.RenderOrder.BEHIND -> {
                        actorsRenderBehind.add(actor); insertionSortLastElemAV(actorsRenderBehind)
                    }
                    Actor.RenderOrder.MIDDLE -> {
                        actorsRenderMiddle.add(actor); insertionSortLastElemAV(actorsRenderMiddle)
                    }
                    Actor.RenderOrder.MIDTOP -> {
                        actorsRenderMidTop.add(actor); insertionSortLastElemAV(actorsRenderMidTop)
                    }
                    Actor.RenderOrder.FRONT  -> {
                        actorsRenderFront.add(actor); insertionSortLastElemAV(actorsRenderFront)
                    }
                }
            }
        }
    }

    fun addParticle(particle: ParticleBase) {
        particlesContainer.add(particle)
    }

    fun addUI(ui: UICanvas) {
        // check for exact duplicates
        if (uiContainer.contains(ui)) {
            throw IllegalArgumentException(
                    "Exact copy of the UI already exists: The instance of ${ui.javaClass.simpleName}"
            )
        }

        uiContainer.add(ui)
    }

    private fun insertionSortLastElemAV(arr: ArrayList<ActorWithBody>) { // out-projection doesn't work, duh
        lock(ReentrantLock()) {
            var j = arr.lastIndex - 1
            val x = arr.last()
            while (j >= 0 && arr[j] > x) {
                arr[j + 1] = arr[j]
                j -= 1
            }
            arr[j + 1] = x
        }
    }

    fun setTooltipMessage(message: String?) {
        if (message == null) {
            uiTooltip.setAsClose()
        }
        else {
            if (uiTooltip.isClosed || uiTooltip.isClosing) {
                uiTooltip.setAsOpen()
            }
            uiTooltip.message = message
        }
    }

    override fun pause() {
        // TODO no pause when off-focus on desktop
    }

    override fun resume() {
    }

    override fun hide() {
        dispose()
    }



    private var lightmapInitialised = false // to avoid nullability of lightmapFBO

    /**
     * @param width same as AppLoader.screenW
     * @param height same as AppLoader.screenH
     * @see net.torvald.terrarum.Terrarum
     */
    override fun resize(width: Int, height: Int) {

        BlocksDrawer.resize(AppLoader.screenW, AppLoader.screenH)
        LightmapRenderer.resize(AppLoader.screenW, AppLoader.screenH)
        MegaRainGovernor.resize()

        worldDrawFrameBuffer.dispose()
        worldDrawFrameBuffer = FrameBuffer(worldFBOformat, AppLoader.screenW, AppLoader.screenH, false)
        worldGlowFrameBuffer.dispose()
        worldGlowFrameBuffer = FrameBuffer(worldFBOformat, AppLoader.screenW, AppLoader.screenH, false)
        worldBlendFrameBuffer.dispose()
        worldBlendFrameBuffer = FrameBuffer(worldFBOformat, AppLoader.screenW, AppLoader.screenH, false)

        if (lightmapInitialised) {
            lightmapFboA.dispose()
            lightmapFboB.dispose()
        }
        lightmapFboA = FrameBuffer(
                lightFBOformat,
                LightmapRenderer.lightBuffer.width * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
                LightmapRenderer.lightBuffer.height * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
                false
        )
        lightmapFboB = FrameBuffer(
                lightFBOformat,
                LightmapRenderer.lightBuffer.width * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
                LightmapRenderer.lightBuffer.height * LightmapRenderer.DRAW_TILE_SIZE.toInt(),
                false
        )
        lightmapInitialised = true // are you the first time?


        // Set up viewport when window is resized
        initViewPort(AppLoader.screenW, AppLoader.screenH)



        if (gameInitialised) {
            LightmapRenderer.fireRecalculateEvent()
        }


        if (gameFullyLoaded) {
            // resize UIs

            notifier.setPosition(
                    (AppLoader.screenW - notifier.width) / 2, AppLoader.screenH - notifier.height)

            // inventory
            /*uiInventoryPlayer =
                    UIInventory(player,
                            width = 840,
                            height = AppLoader.screenH - 160,
                            categoryWidth = 210
                    )*/


            // basic watch-style notification bar (temperature, new mail)
            uiWatchBasic.setPosition(AppLoader.screenW - uiWatchBasic.width, 0)
            uiWatchTierOne.setPosition(AppLoader.screenW - uiWatchTierOne.width, uiWatchBasic.height - 2)
        }


        println("[Ingame] Resize event")
    }

    override fun dispose() {
        worldDrawFrameBuffer.dispose()
        worldGlowFrameBuffer.dispose()
        worldBlendFrameBuffer.dispose()
        lightmapFboA.dispose()
        lightmapFboB.dispose()

        actorsRenderBehind.forEach { it.dispose() }
        actorsRenderMiddle.forEach { it.dispose() }
        actorsRenderMidTop.forEach { it.dispose() }
        actorsRenderFront.forEach { it.dispose() }

        uiAliases.forEach { it.dispose() }
        uiAlasesPausing.forEach { it.dispose() }


        WatchDotAlph.dispose()
        Watch7SegMain.dispose()
        WatchDotAlph.dispose()

        ItemSlotImageBuilder.dispose()

        MessageWindow.SEGMENT_BLACK.dispose()
        MessageWindow.SEGMENT_WHITE.dispose()
    }


    /**
     * WARNING! this function flushes batch; use this sparingly!
     *
     * Camera will be moved so that (newX, newY) would be sit on the top-left edge.
     */
    fun setCameraPosition(newX: Float, newY: Float) {
        setCameraPosition(batch, camera, newX, newY)
    }


}
