package com.unciv.app.desktop

import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.tools.texturepacker.TexturePacker
import com.badlogic.gdx.utils.Json
import java.io.File

/**
 * Entry point: _ImagePacker.[packImages] ()_
 * 
 * Re-packs our texture assets into atlas + png File pairs, which will be loaded by the game.
 * With the exception of the ExtraImages folder and the Font system these are the only
 * graphics used (The source Image folders are unused at run time except here).
 * 
 * [TexturePacker] documentation is [here](https://github.com/libgdx/libgdx/wiki/Texture-packer)
 */
internal object ImagePacker {
    private fun getDefaultSettings() = TexturePacker.Settings().apply {
        // Apparently some chipsets, like NVIDIA Tegra 3 graphics chipset (used in Asus TF700T tablet),
        // don't support non-power-of-two texture sizes - kudos @yuroller!
        // https://github.com/yairm210/UnCiv/issues/1340

        /**
         * These should be as big as possible in order to accommodate ALL the images together in one big file.
         * Why? Because the rendering function of the main screen renders all the images consecutively, and every time it needs to switch between textures,
         * this causes a delay, leading to horrible lag if there are enough switches.
         * The cost of this specific solution is that the entire game.png needs be be kept in-memory constantly.
         * Now here we come to what Fred Colon would call an Imp Arse.
         * On the one hand, certain tilesets (ahem 5hex ahem) are really big.
         * You wouldn't believe how hugely mindbogglingly big they are. So theoretically we should want all of their images to be together.
         * HOWEVER certain chipsets (see https://github.com/yairm210/Unciv/issues/3330) only seem to support to up to 2048 width*height so this is maximum we can have.
         * Practically this means that big custom tilesets will have to reload the texture a lot when covering the map and so the
         *    panning on the map will tend to lag a lot :(
         *
         *    TL;DR this should be 2048.
         */
        maxWidth = 2048
        maxHeight = 2048

        // Trying to disable the subdirectory combine lead to even worse results. Don't.
        combineSubdirectories = true
        pot = true  // powers of two only for width/height
        fast = true  // with pot on this just sorts by width
        // settings.rotation - do not set. Allows rotation, potentially packing tighter.
        //      Proper rendering is mostly automatic - except borders which overwrite rotation.

        // Set some additional padding and enable duplicatePadding to prevent image edges from bleeding into each other due to mipmapping
        paddingX = 8
        paddingY = 8
        duplicatePadding = true
        filterMin = Texture.TextureFilter.MipMapLinearLinear
        filterMag = Texture.TextureFilter.MipMapLinearLinear // I'm pretty sure this doesn't make sense for magnification, but setting it to Linear gives strange results
    }

    fun packImages() {
        val startTime = System.currentTimeMillis()

        val defaultSettings = getDefaultSettings()

        // Scan for Image folders and build one atlas each
        packImagesPerMod("..", ".", defaultSettings)

        // pack for mods as well
        val modDirectory = File("mods")
        if (modDirectory.exists()) {
            for (mod in modDirectory.listFiles()!!) {
                if (!mod.isHidden) {
                    try {
                        packImagesPerMod(mod.path, mod.path, defaultSettings)
                    } catch (ex: Throwable) {
                        println("Exception in ImagePacker: ${ex.message}")
                    }
                }
            }
        }

        val texturePackingTime = System.currentTimeMillis() - startTime
        println("Packing textures - " + texturePackingTime + "ms")
    }

    // Scan multiple image folders and generate an atlas for each - if outdated
    private fun packImagesPerMod(input: String, output: String, defaultSettings: TexturePacker.Settings) {
        if (!File("$input${File.separator}Images").exists()) return  // So we don't run this from within a fat JAR
        val atlasList = mutableListOf<String>()
        for ((file, packFileName) in imageFolders(input)) {
            atlasList += packFileName
            packImagesIfOutdated(defaultSettings, file, output, packFileName)
        }
        atlasList.remove("game")
        val listFile = File("$output${File.separator}Atlases.json")
        if (atlasList.isEmpty()) listFile.delete()
        else listFile.writeText(atlasList.sorted().joinToString(",","[","]"))
    }

    // Process one Image folder, checking for atlas older than contained images first
    private fun packImagesIfOutdated(defaultSettings: TexturePacker.Settings, input: String, output: String, packFileName: String) {
        fun File.listTree(): Sequence<File> = when {
            this.isFile -> sequenceOf(this)
            this.isDirectory -> this.listFiles()!!.asSequence().flatMap { it.listTree() }
            else -> sequenceOf()
        }

        // Check if outdated
        val atlasFile = File("$output${File.separator}$packFileName.atlas")
        if (atlasFile.exists() && File("$output${File.separator}$packFileName.png").exists()) {
            val atlasModTime = atlasFile.lastModified()
            if (File(input).listTree().none { it.extension in listOf("png", "jpg", "jpeg") && it.lastModified() > atlasModTime }) return
        }

        // An image folder can optionally have a TexturePacker settings file
        val settingsFile = File("$input${File.separator}TexturePacker.settings")
        val settings = if (settingsFile.exists())
            Json().fromJson(TexturePacker.Settings::class.java, settingsFile.reader())
        else defaultSettings

        TexturePacker.process(settings, input, output, packFileName)
    }

    // Iterator providing all Image folders to process with the destination atlas name
    private data class ImageFolderResult(val folder: String, val atlasName: String)
    private fun imageFolders(path: String) = sequence {
        val parent = File(path)
        for (folder in parent.listFiles()!!) {
            if (!folder.isDirectory) continue
            if (folder.nameWithoutExtension != "Images") continue
            val atlasName = if (folder.name == "Images") "game" else folder.extension
            yield(ImageFolderResult(folder.path, atlasName))
        }
    }
}
