package com.persesgames.map.tiled

import com.persesgames.net.getUrlAsString
import com.persesgames.texture.Texture
import com.persesgames.texture.Textures
import java.util.*
import kotlin.browser.window

/**
 * Created by rnentjes on 22-7-16.
 */

class MapData {
    var version: Int = 1
    var properties: MutableMap<String, String> = HashMap()
    var layers: Array<MapLayer>? = null
    var tilesets: Array<MapTileset>? = null

    var height: Int = 0
    var width: Int = 0

    var nextobjectid: Int = 0
    var orientation: String =  "orthogonal"
    var renderorder: String =  "right-down"
    var tileheight: Int = 0
    var tilewidth: Int = 0
}

class MapLayer {
    var properties: MutableMap<String, String> = HashMap()

    var data: Array<Int>? = null
    var encoding: String = ""
    var x: Int = 0
    var y: Int = 0
    var width: Int = 0
    var height: Int = 0
    var name: String = ""
    var opacity: Float = 1f
    var type: String = ""
    var visible: Boolean = true
    var draworder: String = ""
}

class MapTileset {
    var properties: MutableMap<String, String> = HashMap()

    var firstgid: Int = 0
    var image: String = ""
    var imageheight: Int = 0
    var imagewidth: Int = 0
    var margin: Int = 0
    var name: String = ""
    var spacing: Int = 0
    var tilecount: Int = 0
    var tileheight: Int = 0
    var tilewidth: Int = 0
    var tileproperties: MutableMap<String, MutableMap<String, String>> = HashMap()
}

class TilesetIndex(
  val texture: Texture?,
  val tcLeft: Float,
  val tcTop: Float,
  val tcRight: Float,
  val tcBottom: Float,
  val scale: Float
  ) {
    constructor() : this(null, 0f, 0f, 0f, 0f, 0f)

    fun render(x: Float, y: Float) {
        texture?.queueTileDraw(x, y, tcLeft, tcTop, tcRight, tcBottom, scale)
    }
}

class TiledMap(dir: String = "", url: String) {
    val properties: Map<String, String> = HashMap()
    val data: MapData
    val tileset: Array<String>
    val tiles: Array<TilesetIndex>
    var first = true
    //var tilesetIndex: Array<TilesetIndex> = Array(0, { TilesetIndex() })

    init {
        var tileDir = dir
        if (!tileDir.isEmpty() && !tileDir.endsWith("/")) {
            tileDir = tileDir + "/"
        }

        data = JSON.parse<MapData>(getUrlAsString(tileDir + url))
        println("map data is loaded")
        val tilesets = data.tilesets
        if (tilesets != null) {
            tileset = Array(tilesets.size, { "" })
            var maxGid = 0
            for (index in 0..tilesets.size - 1) {
                tileset[index] = tilesets[index].name
                Textures.load(tilesets[index].name, tileDir + tilesets[index].image)
                maxGid = Math.max(maxGid, tilesets[index].firstgid + tilesets[index].tilecount)
            }

            tiles = Array(maxGid, { TilesetIndex() })
        } else {
            tileset = Array(0, { "" })
            tiles = Array(0, { TilesetIndex() })
        }

        cacheTiles()
    }

    fun cacheTiles() {
        if (!Textures.ready()) {
            window.setTimeout({ cacheTiles() }, 10)
        } else {
            val tilesets = data.tilesets
            var tcLeft = 0f
            var tcTop = 0f
            var tcRight = 0f
            var tcBottom = 0f

            if (tilesets != null) {


                for (tileset in tilesets) {
                    val tilesHor = tileset.imagewidth / tileset.tilewidth
                    val tilesVer = tileset.imageheight / tileset.tileheight
                    val scale = (tileset.tilewidth / tileset.imagewidth.toFloat())

                    for (index in tileset.firstgid..tileset.firstgid + tileset.tilecount) {
                        val texture = Textures.get(tileset.name)

                        val gid = index - tileset.firstgid

                        val xi = gid % tilesHor
                        var yi = gid / tilesHor
                        yi = tilesVer - yi - 1
                        val tw = 1f / tilesHor.toFloat()
                        val th = 1f / tilesVer.toFloat()

                        val pixelW = 0.1f / tileset.tilewidth
                        val pixelH = 0.1f / tileset.tileheight

                        tcLeft = xi * tw
                        tcRight = tcLeft + tw

                        // switch up/down because of texture coord 0,0 in left bottom corner
                        tcBottom = yi * th
                        tcTop = tcBottom + th

                        tcLeft += pixelW
                        tcRight -= pixelW

                        tcBottom += pixelH
                        tcTop -= pixelH

                        tiles[index] = TilesetIndex(texture, tcLeft, tcTop, tcRight, tcBottom, scale)
                    }
                }
            }
        }
    }

    fun drawTile(tile: Int, x: Float, y: Float) {
        tiles[tile].render(x, y)
    }

    fun drawLayer(layerIndex: Int, xo: Float, yo: Float) {
        var x = 0f
        var y = 0f
        val layers = data.layers ?: throw IllegalArgumentException("MapData has no layers ($data)")
        val layer = layers[layerIndex]

        val layerData = layer.data
        if (layerData != null) {
            for (index in layerData.indices) {
                // todo: determine if in view
                // todo: determine tilewidth
                //if (xo+x*128f < Game.view.width && yo + y * 128 < Game.view.height) {
                    drawTile(layerData[index], xo + x * 128f, yo + y * 128f)

                    when (data.renderorder) {
                        "right-down" -> {
                            x++
                            if (x >= layer.width) {
                                x = 0f
                                y--
                            }
                        }
                        else -> {
                            throw IllegalStateException("Renderorder ${data.renderorder} not supported in $this")
                        }
                    }
                //}
            }
        }

        val tilesets = data.tilesets
        if (tilesets != null) {
            for (tileset in tilesets) {
                if (Textures.has(tileset.name)) {
                    val tx = Textures.get(tileset.name)

                    tx.render()
                }
            }
        }


        first = false
    }
}
