package mindustry.tools;

import arc.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.tools.ImagePacker.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.legacy.*;

import static mindustry.Vars.*;

public class Generators{
    //used for changing colors in the UI - testing only
    static final IntIntMap paletteMap = IntIntMap.with(
    //empty for now
    0x454545ff, 0x00000000,//0x32394bff,
    0x00000099, 0x00000000//0x000000ff
    );

    public static void generate(){
        ObjectMap<Block, Image> gens = new ObjectMap<>();

        if(!paletteMap.isEmpty()){
            ImagePacker.generate("uipalette", () -> {
                Fi.get("../ui").walk(fi -> {
                    if(!fi.extEquals("png")) return;

                    Pixmap pix = new Pixmap(fi);
                    pix.setBlending(Pixmap.Blending.sourceOver);
                    pix.each((x, y) -> {
                        int value = pix.getPixel(x, y);
                        pix.draw(x, y, paletteMap.get(value, value));
                    });

                    fi.writePNG(pix);
                });
            });
        }

        ImagePacker.generate("splashes", () -> {
            ArcNativesLoader.load();

            int frames = 12;
            int size = 32;
            for(int i = 0; i < frames; i++){
                float fin = (float)i / (frames);
                float fout = 1f - fin;
                float stroke = 3.5f * fout;
                float radius = (size/2f) * fin;

                Pixmap pixmap = new Pixmap(size, size);

                pixmap.each((x, y) -> {
                    float dst = Mathf.dst(x, y, size/2f, size/2f);
                    if(Math.abs(dst - radius) <= stroke){
                        pixmap.draw(x, y, Color.white);
                    }
                });

                Fi.get("splash-" + i + ".png").writePNG(pixmap);

                pixmap.dispose();
            }
        });

        ImagePacker.generate("cliffs", () -> {
            int size = 64;
            Color dark = new Color(0.5f, 0.5f, 0.6f, 1f).mul(0.98f);
            Color mid = Color.lightGray;

            Image[] images = new Image[8];
            for(int i = 0; i < 8; i++){
                images[i] = ImagePacker.get("cliff" + i);
            }

            for(int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++){
                Image result = new Image(size, size);
                byte[][] mask = new byte[size][size];

                byte val = (byte)i;
                //check each bit/direction
                for(int j = 0; j < 8; j++){
                    if((val & (1 << j)) != 0){
                        if(j % 2 == 1 && (((val & (1 << (j + 1))) != 0) != ((val & (1 << (j - 1))) != 0))){
                            continue;
                        }

                        Image image = images[j];
                        image.each((x, y) -> {
                            Color color = image.getColor(x, y);
                            if(color.a > 0.1){
                                //white -> bit 1 -> top
                                //black -> bit 2 -> bottom
                                mask[x][y] |= (color.r > 0.5f ? 1 : 2);
                            }
                        });
                    }
                }

                result.each((x, y) -> {
                    byte m = mask[x][y];
                    if(m != 0){
                        //mid
                        if(m == 3){
                            //find nearest non-mid color
                            byte best = 0;
                            float bestDst = 0;
                            boolean found = false;
                            //expand search range until found
                            for(int rad = 9; rad < 64; rad += 7){
                                for(int cx = Math.max(x - rad, 0); cx <= Math.min(x + rad, size - 1); cx++){
                                    for(int cy = Math.max(y - rad, 0); cy <= Math.min(y + rad, size - 1); cy++){
                                        byte nval = mask[cx][cy];
                                        if(nval == 1 || nval == 2){
                                            float dst2 = Mathf.dst2(cx, cy, x, y);
                                            if(dst2 <= rad * rad && (!found || dst2 < bestDst)){
                                                best = nval;
                                                bestDst = dst2;
                                                found = true;
                                            }
                                        }
                                    }
                                }
                            }

                            if(found){
                                m = best;
                            }
                        }

                        result.draw(x, y, m == 1 ? Color.white : m == 2 ? dark : mid);
                    }
                });

                result.save("../blocks/environment/cliffmask" + (val & 0xff));
            }
        });

        ImagePacker.generate("cracks", () -> {
            RidgedPerlin r = new RidgedPerlin(1, 3);
            for(int size = 1; size <= Block.maxCrackSize; size++){
                int dim = size * 32;
                int steps = Block.crackRegions;
                for(int i = 0; i < steps; i++){
                    float fract = i / (float)steps;

                    Image image = new Image(dim, dim);
                    for(int x = 0; x < dim; x++){
                        for(int y = 0; y < dim; y++){
                            float dst = Mathf.dst((float)x/dim, (float)y/dim, 0.5f, 0.5f) * 2f;
                            if(dst < 1.2f && r.getValue(x, y, 1f / 40f) - dst*(1f-fract) > 0.16f){
                                image.draw(x, y, Color.white);
                            }
                        }
                    }

                    Image output = new Image(image.width, image.height);
                    int rad = 3;

                    //median filter
                    for(int x = 0; x < output.width; x++){
                        for(int y = 0; y < output.height; y++){
                            int whites = 0, clears = 0;
                            for(int cx = -rad; cx < rad; cx++){
                                for(int cy = -rad; cy < rad; cy++){
                                    int wx = Mathf.clamp(cx + x, 0, output.width - 1), wy = Mathf.clamp(cy + y, 0, output.height - 1);
                                    Color color = image.getColor(wx, wy);
                                    if(color.a > 0.5f){
                                        whites ++;
                                    }else{
                                        clears ++;
                                    }
                                }
                            }
                            output.draw(x, y, whites >= clears ? Color.white : Color.clear);
                        }
                    }

                    output.save("cracks-" + size + "-" + i);
                }
            }
        });

        ImagePacker.generate("block-icons", () -> {
            Image colors = new Image(content.blocks().size, 1);

            for(Block block : content.blocks()){
                if(block.isAir() || block instanceof ConstructBlock || block instanceof OreBlock || block instanceof LegacyBlock) continue;

                block.load();

                TextureRegion[] regions = block.getGeneratedIcons();

                if(block instanceof Floor){
                    for(TextureRegion region : block.variantRegions()){
                        GenRegion gen = (GenRegion)region;
                        if(gen.path == null) continue;
                        gen.path.copyTo(Fi.get("../editor/editor-" + gen.path.name()));
                    }
                }

                Image shardTeamTop = null;

                if(block.teamRegion.found()){
                    Image teamr = ImagePacker.get(block.teamRegion);

                    for(Team team : Team.all){
                        if(team.hasPalette){
                            Image out = new Image(teamr.width, teamr.height);
                            teamr.each((x, y) -> {
                                int color = teamr.getColor(x, y).rgba8888();
                                int index = color == 0xffffffff ? 0 : color == 0xdcc6c6ff ? 1 : color == 0x9d7f7fff ? 2 : -1;
                                out.draw(x, y, index == -1 ? teamr.getColor(x, y) : team.palette[index]);
                            });
                            out.save(block.name + "-team-" + team.name);

                            if(team == Team.sharded){
                                shardTeamTop = out;
                            }
                        }
                    }
                }

                if(regions.length == 0){
                    continue;
                }

                try{
                    Image last = null;
                    if(block.outlineIcon){
                        int radius = 4;
                        GenRegion region = (GenRegion)regions[regions.length - 1];
                        Image base = ImagePacker.get(region);
                        Image out = last = new Image(region.width, region.height);
                        for(int x = 0; x < out.width; x++){
                            for(int y = 0; y < out.height; y++){

                                Color color = base.getColor(x, y);
                                out.draw(x, y, color);
                                if(color.a < 1f){
                                    boolean found = false;
                                    outer:
                                    for(int rx = -radius; rx <= radius; rx++){
                                        for(int ry = -radius; ry <= radius; ry++){
                                            if(Mathf.dst(rx, ry) <= radius && base.getColor(rx + x, ry + y).a > 0.01f){
                                                found = true;
                                                break outer;
                                            }
                                        }
                                    }
                                    if(found){
                                        out.draw(x, y, block.outlineColor);
                                    }
                                }
                            }
                        }

                        region.path.delete();

                        out.save(block.name);
                    }

                    Image image = ImagePacker.get(regions[0]);

                    int i = 0;
                    for(TextureRegion region : regions){
                        i++;
                        if(i != regions.length || last == null){
                            image.draw(region);
                        }else{
                            image.draw(last);
                        }

                        //draw shard (default team top) on top of first sprite
                        if(region == block.teamRegions[Team.sharded.id] && shardTeamTop != null){
                            image.draw(shardTeamTop);
                        }
                    }

                    if(!(regions.length == 1 && regions[0] == Core.atlas.find(block.name) && shardTeamTop == null)){
                        image.save("block-" + block.name + "-full");
                    }

                    image.save("../editor/" + block.name + "-icon-editor");

                    for(Cicon icon : Cicon.scaled){
                        Image scaled = new Image(icon.size, icon.size);
                        scaled.drawScaled(image);
                        scaled.save("../ui/block-" + block.name + "-" + icon.name());
                    }

                    boolean hasEmpty = false;
                    Color average = new Color();
                    float asum = 0f;
                    for(int x = 0; x < image.width; x++){
                        for(int y = 0; y < image.height; y++){
                            Color color = image.getColor(x, y);
                            average.r += color.r*color.a;
                            average.g += color.g*color.a;
                            average.b += color.b*color.a;
                            asum += color.a;
                            if(color.a < 0.9f){
                                hasEmpty = true;
                            }
                        }
                    }

                    average.mul(1f / asum);

                    if(block instanceof Floor){
                        average.mul(0.8f);
                    }else{
                        average.mul(1.1f);
                    }
                    //encode square sprite in alpha channel
                    average.a = hasEmpty ? 0.1f : 1f;
                    colors.draw(block.id, 0, average);
                }catch(NullPointerException e){
                    Log.err("Block &ly'@'&lr has an null region!", block);
                }
            }

            colors.save("../../../assets/sprites/block_colors");
        });

        ImagePacker.generate("shallows", () -> {
            content.blocks().<ShallowLiquid>each(b -> b instanceof ShallowLiquid, floor -> {
                Image overlay = ImagePacker.get(floor.liquidBase.region);
                int index = 0;
                for(TextureRegion region : floor.floorBase.variantRegions()){
                    Image res = new Image(32, 32);
                    res.draw(ImagePacker.get(region));
                    for(int x = 0; x < res.width; x++){
                        for(int y = 0; y < res.height; y++){
                            Color color = overlay.getColor(x, y).a(floor.liquidOpacity);
                            res.draw(x, y, color);
                        }
                    }

                    String name = floor.name + "" + (++index);
                    res.save("../blocks/environment/" + name);
                    res.save("../editor/editor-" + name);

                    gens.put(floor, res);
                }
            });
        });

        ImagePacker.generate("item-icons", () -> {
            for(UnlockableContent item : Seq.<UnlockableContent>withArrays(content.items(), content.liquids())){
                Image base = ImagePacker.get(item.getContentType().name() + "-" + item.name);
                for(Cicon icon : Cicon.scaled){
                    //if(icon.size == base.width) continue;
                    Image image = new Image(icon.size, icon.size);
                    image.drawScaled(base);
                    image.save(item.getContentType().name() + "-" + item.name + "-" + icon.name(), false);

                    if(icon == Cicon.medium){
                        image.save("../ui/" + item.getContentType() + "-" + item.name + "-icon");
                    }
                }
            }
        });

        ImagePacker.generate("unit-icons", () -> content.units().each(type -> {
            if(type.isHidden()) return; //hidden units don't generate

            ObjectSet<String> outlined = new ObjectSet<>();

            try{
                type.load();
                type.init();

                Color outc = Pal.darkerMetal;
                Func<Image, Image> outline = i -> i.outline(3, outc);
                Cons<TextureRegion> outliner = t -> {
                    if(t != null && t.found()){
                        ImagePacker.replace(t, outline.get(ImagePacker.get(t)));
                    }
                };

                for(Weapon weapon : type.weapons){
                    if(outlined.add(weapon.name) && ImagePacker.has(weapon.name)){
                        outline.get(ImagePacker.get(weapon.name)).save(weapon.name + "-outline");
                    }
                }

                outliner.get(type.jointRegion);
                outliner.get(type.footRegion);
                outliner.get(type.legBaseRegion);
                outliner.get(type.baseJointRegion);
                if(type.constructor.get() instanceof Legsc) outliner.get(type.legRegion);

                Image image = outline.get(ImagePacker.get(type.region));

                image.save(type.name + "-outline");

                //draw mech parts
                if(type.constructor.get() instanceof Mechc){
                    image.drawCenter(type.baseRegion);
                    image.drawCenter(type.legRegion);
                    image.drawCenter(type.legRegion, true, false);
                    image.draw(type.region);
                }

                //draw outlines
                for(Weapon weapon : type.weapons){
                    weapon.load();

                    image.draw(outline.get(ImagePacker.get(weapon.region)),
                    (int)(weapon.x / Draw.scl + image.width / 2f - weapon.region.width / 2f),
                    (int)(-weapon.y / Draw.scl + image.height / 2f - weapon.region.height / 2f),
                    weapon.flipSprite, false);
                }

                //draw base region on top to mask weapons
                image.draw(type.region);

                Image baseCell = ImagePacker.get(type.cellRegion);
                Image cell = new Image(type.cellRegion.width, type.cellRegion.height);
                cell.each((x, y) -> cell.draw(x, y, baseCell.getColor(x, y).mul(Color.valueOf("ffa665"))));

                image.draw(cell, image.width / 2 - cell.width / 2, image.height / 2 - cell.height / 2);

                for(Weapon weapon : type.weapons){
                    weapon.load();

                    image.draw(weapon.top ? outline.get(ImagePacker.get(weapon.region)) : ImagePacker.get(weapon.region),
                    (int)(weapon.x / Draw.scl + image.width / 2f - weapon.region.width / 2f),
                    (int)(-weapon.y / Draw.scl + image.height / 2f - weapon.region.height / 2f),
                    weapon.flipSprite, false);
                }

                image.save("unit-" + type.name + "-full");

                Rand rand = new Rand();
                rand.setSeed(type.name.hashCode());

                //generate random wrecks

                int splits = 3;
                float degrees = rand.random(360f);
                float offsetRange = Math.max(image.width, image.height) * 0.15f;
                Vec2 offset = new Vec2(1, 1).rotate(rand.random(360f)).setLength(rand.random(0, offsetRange)).add(image.width/2f, image.height/2f);

                Image[] wrecks = new Image[splits];
                for(int i = 0; i < wrecks.length; i++){
                    wrecks[i] = new Image(image.width, image.height);
                }

                RidgedPerlin r = new RidgedPerlin(1, 3);
                VoronoiNoise vn = new VoronoiNoise(type.id, true);

                image.each((x, y) -> {
                    //add darker cracks on top
                    boolean rValue = Math.max(r.getValue(x, y, 1f / (20f + image.width/8f)), 0) > 0.16f;
                    //cut out random chunks with voronoi
                    boolean vval = vn.noise(x, y, 1f / (14f + image.width/40f)) > 0.47;

                    float dst =  offset.dst(x, y);
                    //distort edges with random noise
                    float noise = (float)Noise.rawNoise(dst / (9f + image.width/70f)) * (60 + image.width/30f);
                    int section = (int)Mathf.clamp(Mathf.mod(offset.angleTo(x, y) + noise + degrees, 360f) / 360f * splits, 0, splits - 1);
                    if(!vval) wrecks[section].draw(x, y, image.getColor(x, y).mul(rValue ? 0.7f : 1f));
                });

                for(int i = 0; i < wrecks.length; i++){
                    wrecks[i].save(type.name + "-wreck" + i);
                }

                for(Cicon icon : Cicon.scaled){
                    Vec2 size = Scaling.fit.apply(image.width, image.height, icon.size, icon.size);
                    Image scaled = new Image((int)size.x, (int)size.y);

                    scaled.drawScaled(image);
                    scaled.save("../ui/unit-" + type.name + "-" + icon.name());
                }

            }catch(IllegalArgumentException e){
                Log.err("WARNING: Skipping unit @: @", type.name, e.getMessage());
            }

        }));

        ImagePacker.generate("ore-icons", () -> {
            content.blocks().<OreBlock>each(b -> b instanceof OreBlock, ore -> {
                Item item = ore.itemDrop;

                for(int i = 0; i < 3; i++){
                    //get base image to draw on
                    Image image = new Image(32, 32);
                    Image shadow = ImagePacker.get(item.name + (i + 1));

                    int offset = image.width / tilesize - 1;

                    for(int x = 0; x < image.width; x++){
                        for(int y = offset; y < image.height; y++){
                            Color color = shadow.getColor(x, y - offset);

                            //draw semi transparent background
                            if(color.a > 0.001f){
                                color.set(0, 0, 0, 0.3f);
                                image.draw(x, y, color);
                            }
                        }
                    }

                    image.draw(ImagePacker.get(item.name + (i + 1)));
                    image.save("../blocks/environment/ore-" + item.name + (i + 1));
                    image.save("../editor/editor-ore-" + item.name + (i + 1));

                    image.save("block-" + ore.name + "-full");
                    for(Cicon icon : Cicon.scaled){
                        Image scaled = new Image(icon.size, icon.size);
                        scaled.drawScaled(image);
                        scaled.save("../ui/block-" + ore.name + "-" + icon.name());
                    }
                }
            });
        });

        ImagePacker.generate("edges", () -> {
            content.blocks().<Floor>each(b -> b instanceof Floor && !(b instanceof OverlayFloor), floor -> {

                if(ImagePacker.has(floor.name + "-edge") || floor.blendGroup != floor){
                    return;
                }

                try{
                    Image image = gens.get(floor, ImagePacker.get(floor.getGeneratedIcons()[0]));
                    Image edge = ImagePacker.get("edge-stencil");
                    Image result = new Image(edge.width, edge.height);

                    for(int x = 0; x < edge.width; x++){
                        for(int y = 0; y < edge.height; y++){
                            result.draw(x, y, edge.getColor(x, y).mul(image.getColor(x % image.width, y % image.height)));
                        }
                    }

                    result.save("../blocks/environment/" + floor.name + "-edge");

                }catch(Exception ignored){}
            });
        });

        ImagePacker.generate("scorches", () -> {
            for(int size = 0; size < 10; size++){
                for(int i = 0; i < 3; i++){
                    ScorchGenerator gen = new ScorchGenerator();
                    double multiplier = 30;
                    double ss = size * multiplier / 20.0;

                    gen.seed = Mathf.random(100000);
                    gen.size += size*multiplier;
                    gen.scale = gen.size / 80f * 18f;
                    //gen.nscl -= size * 0.2f;
                    gen.octaves += ss/3.0;
                    gen.pers += ss/10.0/5.0;

                    gen.scale += Mathf.range(3f);
                    gen.scale -= ss*2f;
                    gen.nscl -= Mathf.random(1f);

                    Pixmap out = gen.generate();
                    Pixmap median = Pixmaps.median(out, 2, 0.75);
                    Fi.get("../rubble/scorch-" + size + "-" + i + ".png").writePNG(median);
                    out.dispose();
                    median.dispose();
                }
            }
        });
    }

}
