sourceSets.main.java.srcDirs = ["src/"]

import arc.files.Fi
import arc.files.ZipFi
import arc.func.Func2
import arc.graphics.Color
import arc.graphics.Pixmap
import arc.packer.TexturePacker
import arc.struct.IntMap
import arc.struct.ObjectMap
import arc.struct.OrderedMap
import arc.struct.Seq
import arc.util.Http
import arc.util.Log
import arc.util.OS
import arc.util.Threads
import arc.util.io.PropertiesUtils
import arc.util.io.Streams

import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

def genFolder = "../core/assets-raw/sprites_out/generated/"
def enableAA = true

@groovy.transform.CompileStatic
static int getRGB(Pixmap image, int ix, int iy) {
    return image.getRaw(Math.max(Math.min(ix, image.width - 1), 0), Math.max(Math.min(iy, image.height - 1), 0))
}

@groovy.transform.CompileStatic
static void antialias(File file){
    Pixmap image = new Pixmap(new Fi(file))
    Pixmap out = image.copy()

    Color color = new Color()
    Color sum = new Color()
    Color suma = new Color()
    int[] p = new int[9]

    for(int x = 0; x < image.width; x++){
        for(int y = 0; y < image.height; y++){
            int A = getRGB(image, x - 1, y + 1),
                B = getRGB(image, x, y + 1),
                C = getRGB(image, x + 1, y + 1),
                D = getRGB(image, x - 1, y),
                E = getRGB(image, x, y),
                F = getRGB(image, x + 1, y),
                G = getRGB(image, x - 1, y - 1),
                H = getRGB(image, x, y - 1),
                I = getRGB(image, x + 1, y - 1)

            Arrays.fill(p, E)

            if(D == B && D != H && B != F) p[0] = D
            if((D == B && D != H && B != F && E != C) || (B == F && B != D && F != H && E != A)) p[1] = B
            if(B == F && B != D && F != H) p[2] = F
            if((H == D && H != F && D != B && E != A) || (D == B && D != H && B != F && E != G)) p[3] = D
            if((B == F && B != D && F != H && E != I) || (F == H && F != B && H != D && E != C)) p[5] = F
            if(H == D && H != F && D != B) p[6] = D
            if((F == H && F != B && H != D && E != G) || (H == D && H != F && D != B && E != I)) p[7] = H
            if(F == H && F != B && H != D) p[8] = F

            suma.set(0)

            for(int val : p){
                color.rgba8888(val)
                color.premultiplyAlpha()
                suma.r(suma.r + color.r)
                suma.g(suma.g + color.g)
                suma.b(suma.b + color.b)
                suma.a(suma.a + color.a)
            }

            float fm = suma.a <= 0.001f ? 0f : (float)(1f / suma.a)
            suma.mul(fm, fm, fm, fm)

            float total = 0
            sum.set(0)

            for(int val : p){
                color.rgba8888(val)
                float a = color.a
                color.lerp(suma, (float) (1f - a))
                sum.r(sum.r + color.r)
                sum.g(sum.g + color.g)
                sum.b(sum.b + color.b)
                sum.a(sum.a + a)
                total += 1f
            }

            fm = (float)(1f / total)
            sum.mul(fm, fm, fm, fm)
            out.setRaw(x, y, sum.rgba8888())
            sum.set(0)
        }
    }

    image.dispose()
    out.dispose()

    new Fi(file).writePng(out)
}

task antialiasImages(){
    doLast{
        for(def img : project.getProperty("images").split(",")){
            println(project.getProperty("startdir") + "/" + img)
            antialias(new File(project.getProperty("startdir") + "/" + img))
        }
    }
}

task tileImages(){
    doLast{
        for(def img : project.getProperty("images").split(",")){
            println(project.getProperty("startdir") + "/" + img)
            tileImage(new File(project.getProperty("startdir") + "/" + img))
        }
    }
}

task pack(dependsOn: [classes, configurations.runtimeClasspath]){

    doLast{
        //cleanup old sprites
        delete{
            delete "../core/assets-raw/sprites_out/"
        }

        //copy in new sprites
        copy{
            from "../core/assets-raw/sprites/"
            into "../core/assets-raw/sprites_out/"
        }

        //run generation task; generate all needed sprites
        file(genFolder).mkdirs()
        javaexec{
            main = "mindustry.tools.ImagePacker"
            classpath = sourceSets.main.runtimeClasspath
            workingDir = genFolder
        }

        copy{
            from "../core/assets-raw/sprites_out/ui/icons"
            into "../core/assets-raw/sprites_out/ui/"
        }

        delete{
            delete "../core/assets-raw/sprites_out/ui/icons"
        }

        if(enableAA){
            ExecutorService executor = Executors.newFixedThreadPool(OS.cores)
            long ms = System.currentTimeMillis()

            //antialias everything except UI elements
            fileTree(dir: new File(rootDir, 'core/assets-raw/sprites_out/').absolutePath, include: "**/*.png").visit{ file ->
                if(file.isDirectory() || (file.toString().replace("\\", "/").contains("/ui/") && file.toString().startsWith("icon-")) || file.toString().contains(".9.png") || file.toString().contains("aaaa")) return

                executor.submit{
                    antialias(file.file)
                }
            }

            Threads.await(executor)

            println "Time taken for AA: ${(System.currentTimeMillis() - ms) / 1000f} seconds"
        }

        println("\n\nPacking normal 4096 sprites...\n\n")

        //pack normal sprites
        TexturePacker.process(new File(rootDir, "core/assets-raw/sprites_out/").absolutePath, new File(rootDir, "core/assets/sprites/").absolutePath, "sprites.aatls")

        println("\n\nPacking fallback 2048 sprites...\n\n")

        //replace config file contents
        fileTree(dir: '../core/assets-raw/sprites_out/', include: "**/*.json").visit{ file ->
            if(!file.isDirectory()) file.file.text = file.file.text.replace("4096", "2048")
        }

        //pack fallback 2048x2048 sprites - disabled when debugging
        if(!project.hasProperty("args")){
            TexturePacker.process(new File(rootDir, "core/assets-raw/sprites_out/").absolutePath, new File(rootDir, "core/assets/sprites/fallback/").absolutePath, "sprites.aatls")
        }
    }
}

task genSprites(dependsOn: classes, type: JavaExec){
    finalizedBy 'antialiasGen'

    mainClass = "mindustry.tools.ImagePacker"
    classpath = sourceSets.main.runtimeClasspath
    standardInput = System.in
    workingDir = genFolder
}

task fontgen(dependsOn: classes, type: JavaExec){
    /* icon font pipeline:
     1. take set of pre-defined icons and SVGs
     2. use Fontello API to get a font with these
     3. combine fontello font and standard font, get output font
     4. use json to generate a file with constants for every icon size+type (during annotation processing)
     */
    doLast{
        Fi folder = Fi.get("core/assets-raw/fontgen/out/")
        folder.mkdirs()

        Log.info("Session...")

        OS.exec("curl", "--fail", "--output", "core/assets-raw/fontgen/out/session", "--form", "config=@core/assets-raw/fontgen/config.json", "https://fontello.com")

        Log.info("Zip...")

        String session = folder.child("session").readString()

        Http.get("https://fontello.com/" + session + "/get").block(result -> {
            Streams.copy(result.getResultAsStream(), folder.child("font.zip").write())
        })

        Log.info("Icon font...")

        ZipFi zip = new ZipFi(folder.child("font.zip"))
        Fi dest = folder.child("font.woff")
        zip.list()[0].child("font").child("fontello.ttf").copyTo(dest)
        dest.copyTo(Fi.get("core/assets/fonts/icon.ttf"))

        Log.info("Merge...")

        //TODO this is broken

        Log.info(OS.exec("fontforge", "-script",
            Fi.get("core/assets-raw/fontgen/merge.pe").absolutePath(),
            Fi.get("core/assets/fonts/font.woff").absolutePath(),
            Fi.get("core/assets-raw/fontgen/out/font.woff").absolutePath())
        )

        Log.info("Done.")
    }
}

task icongen(dependsOn: classes, type: JavaExec){
    mainClass = "mindustry.tools.IconConverter"
    classpath = sourceSets.main.runtimeClasspath
    standardInput = System.in
    workingDir = "../core/assets-raw"
}

task updateScripts(dependsOn: classes, type: JavaExec){
    mainClass = "mindustry.tools.ScriptMainGenerator"
    classpath = sourceSets.main.runtimeClasspath
    standardInput = System.in
    workingDir = "../"
}

task updateBundles{
    doLast{
        def uniEscape = { String string ->
            StringBuilder outBuffer = new StringBuilder()
            int len = string.length()
            for(int i = 0; i < len; i++){
                char ch = string.charAt(i)
                if((ch > 61) && (ch < 127)){
                    outBuffer.append(ch == (char)'\\' ? "\\\\" : ch)
                    continue
                }

                if(ch >= 0xE000 && ch <= 0xF8FF){
                    String hex = Integer.toHexString((int)ch)
                    outBuffer.append("\\u")
                    for(int j = 0; j < 4 - hex.length(); j++){
                        outBuffer.append('0')
                    }
                    outBuffer.append(hex)
                }else{
                    outBuffer.append(ch)
                }
            }

            return outBuffer.toString()
        }

        OrderedMap<String, String> base = new OrderedMap<>()
        PropertiesUtils.load(base, Fi.get("core/assets/bundles/bundle.properties").reader())

        //map of line number to comment (or just empty string for blank line) content
        IntMap<String> commentAndBlankLines = new IntMap<>()
        var lines = Fi.get("core/assets/bundles/bundle.properties").reader().readLines()
        int offset = 0
        for(int i = 0; i < lines.size(); i++){
            var line = lines.get(i)
            if(i > 0 && lines.get(i - 1).endsWith("\\")) offset++ //multiline value escaped with \\ at end of line
            else if(line.isEmpty() || line.startsWith("#")) commentAndBlankLines.put(i - offset, line)
        }

        Log.info("Updating bundles...")

        Fi.get("core/assets/bundles").walk(child -> {
            if(child.name() == "bundle.properties" || child.toString().contains("output")) return
            if(project.hasProperty("bundle") && child.name() != project.property("bundle")) return

            Log.info("| @", child.nameWithoutExtension())

            OrderedMap<String, String> other = new OrderedMap<>()

            ObjectMap<String, String> extras = new OrderedMap<>()
            Seq<String> removals = new Seq<>()

            PropertiesUtils.load(other, child.reader())

            for(String key : other.orderedKeys()){
                if(!base.containsKey(key) && key.contains(".details") && false){
                    extras.put(key, other.get(key))
                }else if(!base.containsKey(key)){
                    removals.add(key)
                    Log.info("&lr- Removing unused key '@'...", key)
                }
            }
            if(removals.size > 0) Log.info("&lr@ keys removed.", removals.size)
            for(String s : removals){
                other.remove(s)
            }

            int added = 0

            for(String key : base.orderedKeys()){
                if(other.get(key) == null || other.get(key).trim().isEmpty()){
                    other.put(key, base.get(key))
                    added++
                    Log.info("&lc- Adding missing key '@'...", key)
                }
            }

            Func2<String, String, String> processor = (key, value) ->
                    (key + " =" + (value.trim().isEmpty() ? "" : " ") + uniEscape(value)).replace("\n", "\\n") + "\n"
            Fi output = child.sibling("output/" + child.name())

            if(added > 0) Log.info("&lc@ keys added.", added)
            if(removals.size + added > 0) Log.info("Writing bundle to @", output)
            StringBuilder result = new StringBuilder()

            int i = 0
            //add everything ordered
            for(String key : base.orderedKeys().copy().add(extras.keys().toSeq())){
                //append any comments or blank lines as needed to match the english bundle
                while(commentAndBlankLines.containsKey(i++)) result.append(commentAndBlankLines.get(i - 1) + "\n")
                if(other.get(key) == null) continue

                result.append(processor.get(key, other.get(key)))
                other.remove(key)
            }

            child.writeString(result.toString())
        })
    }
}
