import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;

public final class PassiveMobArtGenerator {
    private static final int MASTER_SIZE = 256;
    private static final int CARD_SIZE = 64;
    private static final int PIXEL = 4;

    private static final Path ROOT = Path.of("").toAbsolutePath();
    private static final Path FULL_ART_DIR = ROOT.resolve("Full Art").resolve("passive_mobs");
    private static final Path CARD_ART_DIR = ROOT.resolve("src")
            .resolve("main")
            .resolve("resources")
            .resolve("assets")
            .resolve("minetriad")
            .resolve("textures")
            .resolve("item")
            .resolve("cards");

    private record MobArt(String name, Color base, Color dark, Color light, Color backgroundTop, Color backgroundBottom,
                          Color accent, List<String> features) {
    }

    private static final List<MobArt> MOBS = List.of(
            mob("axolotl", rgb(244, 173, 203), rgb(232, 144, 187), rgb(124, 62, 110), rgb(95, 173, 207), rgb(19, 76, 113), rgb(255, 214, 122), "gills"),
            mob("bee", rgb(255, 213, 62), rgb(84, 57, 29), rgb(255, 246, 196), rgb(131, 200, 95), rgb(50, 112, 49), rgb(226, 248, 255), "wings", "stripe"),
            mob("camel", rgb(193, 154, 101), rgb(128, 94, 57), rgb(232, 203, 155), rgb(241, 194, 120), rgb(189, 110, 49), rgb(77, 148, 192), "ears"),
            mob("cat", rgb(217, 176, 92), rgb(105, 76, 44), rgb(247, 229, 174), rgb(142, 186, 228), rgb(71, 99, 143), rgb(158, 219, 110), "ears", "snout"),
            mob("chicken", rgb(243, 246, 239), rgb(208, 72, 46), rgb(251, 205, 61), rgb(161, 212, 127), rgb(89, 130, 70), rgb(164, 199, 245), "beak", "crest"),
            mob("cod", rgb(145, 117, 88), rgb(95, 76, 52), rgb(211, 185, 149), rgb(57, 136, 170), rgb(15, 62, 97), rgb(230, 120, 74), "fins"),
            mob("cow", rgb(116, 77, 52), rgb(59, 37, 25), rgb(231, 219, 206), rgb(144, 196, 110), rgb(75, 115, 59), rgb(162, 212, 255), "horns", "snout", "spots"),
            mob("donkey", rgb(143, 118, 92), rgb(81, 62, 44), rgb(218, 202, 182), rgb(131, 170, 210), rgb(66, 90, 129), rgb(125, 193, 98), "long_ears", "snout"),
            mob("fox", rgb(222, 110, 57), rgb(108, 55, 34), rgb(250, 239, 221), rgb(145, 204, 130), rgb(68, 109, 71), rgb(247, 214, 119), "ears", "snout"),
            mob("frog", rgb(132, 176, 92), rgb(70, 108, 54), rgb(208, 219, 171), rgb(121, 180, 116), rgb(63, 110, 68), rgb(255, 220, 128), "frog_eyes"),
            mob("glow_squid", rgb(96, 172, 188), rgb(41, 87, 101), rgb(163, 231, 246), rgb(38, 52, 102), rgb(11, 18, 39), rgb(119, 243, 255), "tentacles", "glow"),
            mob("goat", rgb(208, 196, 184), rgb(121, 110, 98), rgb(245, 240, 233), rgb(160, 196, 193), rgb(92, 124, 122), rgb(192, 171, 101), "horns", "beard"),
            mob("horse", rgb(122, 83, 55), rgb(65, 44, 28), rgb(236, 219, 187), rgb(134, 188, 121), rgb(64, 111, 67), rgb(187, 223, 255), "ears", "snout"),
            mob("llama", rgb(220, 197, 162), rgb(140, 112, 80), rgb(248, 236, 219), rgb(170, 197, 122), rgb(88, 118, 72), rgb(197, 122, 67), "long_ears"),
            mob("mooshroom", rgb(187, 54, 44), rgb(98, 26, 24), rgb(239, 226, 208), rgb(114, 162, 94), rgb(61, 103, 52), rgb(237, 247, 215), "horns", "snout", "spots", "mushroom"),
            mob("mule", rgb(133, 104, 80), rgb(76, 57, 42), rgb(220, 205, 186), rgb(150, 188, 132), rgb(76, 112, 74), rgb(113, 166, 211), "long_ears", "snout"),
            mob("ocelot", rgb(220, 185, 82), rgb(112, 85, 33), rgb(249, 228, 158), rgb(117, 178, 113), rgb(56, 104, 59), rgb(236, 191, 94), "ears", "snout", "spots"),
            mob("panda", rgb(235, 238, 242), rgb(42, 46, 52), rgb(255, 255, 255), rgb(137, 198, 124), rgb(70, 111, 64), rgb(198, 227, 141), "ears", "panda_eyes"),
            mob("parrot", rgb(205, 61, 57), rgb(42, 88, 182), rgb(255, 214, 54), rgb(115, 190, 155), rgb(52, 94, 102), rgb(182, 238, 93), "beak", "crest", "wings"),
            mob("pig", rgb(233, 167, 182), rgb(168, 99, 122), rgb(250, 208, 217), rgb(133, 193, 115), rgb(74, 116, 67), rgb(204, 230, 255), "snout"),
            mob("polar_bear", rgb(238, 243, 247), rgb(152, 173, 191), rgb(255, 255, 255), rgb(146, 188, 217), rgb(82, 117, 149), rgb(195, 225, 255), "ears", "snout"),
            mob("rabbit", rgb(187, 173, 162), rgb(117, 98, 86), rgb(240, 231, 222), rgb(166, 203, 111), rgb(87, 118, 63), rgb(255, 215, 145), "long_ears", "snout"),
            mob("salmon", rgb(214, 120, 95), rgb(121, 76, 60), rgb(247, 183, 162), rgb(72, 146, 175), rgb(17, 69, 107), rgb(225, 212, 117), "fins"),
            mob("sheep", rgb(233, 235, 241), rgb(128, 100, 74), rgb(255, 255, 255), rgb(142, 197, 117), rgb(81, 123, 68), rgb(168, 218, 255), "wool", "snout"),
            mob("sniffer", rgb(127, 95, 66), rgb(73, 52, 39), rgb(180, 141, 106), rgb(102, 152, 88), rgb(54, 93, 55), rgb(222, 106, 58), "snout", "moss"),
            mob("squid", rgb(133, 92, 176), rgb(63, 41, 91), rgb(212, 181, 236), rgb(58, 102, 153), rgb(20, 35, 74), rgb(185, 132, 236), "tentacles"),
            mob("strider", rgb(209, 84, 72), rgb(111, 36, 36), rgb(246, 166, 133), rgb(231, 144, 71), rgb(129, 68, 35), rgb(101, 38, 38), "tentacles", "snout"),
            mob("turtle", rgb(107, 150, 77), rgb(57, 92, 45), rgb(194, 176, 88), rgb(92, 166, 143), rgb(44, 100, 100), rgb(223, 232, 176), "shell"),
            mob("wolf", rgb(181, 188, 197), rgb(91, 98, 109), rgb(231, 235, 239), rgb(118, 164, 188), rgb(63, 97, 115), rgb(209, 67, 57), "ears", "snout"));

    public static void main(String[] args) throws IOException {
        Files.createDirectories(FULL_ART_DIR);
        Files.createDirectories(CARD_ART_DIR);

        for (MobArt mob : MOBS) {
            BufferedImage master = generateArt(mob);
            ImageIO.write(master, "png", FULL_ART_DIR.resolve(mob.name() + ".png").toFile());
            ImageIO.write(scale(master, CARD_SIZE, CARD_SIZE), "png", CARD_ART_DIR.resolve(mob.name() + ".png").toFile());
        }

        System.out.println("Generated " + MOBS.size() + " passive mob paintings.");
    }

    private static BufferedImage generateArt(MobArt mob) {
        BufferedImage image = new BufferedImage(MASTER_SIZE, MASTER_SIZE, BufferedImage.TYPE_INT_ARGB);
        Random random = new Random(mob.name().hashCode());
        paintBackground(image, mob, random);

        Graphics2D g = image.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        g.setComposite(AlphaComposite.SrcOver);

        drawHead(g, mob, random);
        addForeground(g, mob, random);
        addAtmosphere(g, mob, random);

        g.dispose();
        return image;
    }

    private static void paintBackground(BufferedImage image, MobArt mob, Random random) {
        for (int y = 0; y < MASTER_SIZE; y++) {
            double blend = (double) y / (MASTER_SIZE - 1);
            double wave = Math.sin((y / (double) MASTER_SIZE) * Math.PI * 3.0) * 0.08;
            Color row = mix(mob.backgroundTop(), mob.backgroundBottom(), clamp01(blend + wave));
            for (int x = 0; x < MASTER_SIZE; x++) {
                double distanceX = x - (MASTER_SIZE / 2.0);
                double distanceY = y - (MASTER_SIZE / 2.0);
                double vignette = (distanceX * distanceX + distanceY * distanceY) / (MASTER_SIZE * (double) MASTER_SIZE);
                Color shaded = shade(row, vignette * 0.65);
                int noise = random.nextInt(17) - 8;
                image.setRGB(x, y, shift(shaded, noise).getRGB());
            }
        }

        Graphics2D g = image.createGraphics();
        for (int i = 0; i < 10; i++) {
            int radius = random.nextInt(35) + 18;
            int x = random.nextInt(MASTER_SIZE + 40) - 20;
            int y = random.nextInt(MASTER_SIZE + 40) - 20;
            g.setColor(alpha(mob.accent(), random.nextInt(56) + 40));
            g.fillOval(x, y, radius, radius);
        }

        for (int i = 0; i < 55; i++) {
            int x = random.nextInt(MASTER_SIZE);
            int y = random.nextInt(MASTER_SIZE);
            int size = random.nextInt(5) + 2;
            g.setColor(alpha(tint(mob.accent(), 0.35), random.nextInt(31) + 18));
            g.fillRect(x, y, size, size);
        }
        g.dispose();
    }

    private static void drawHead(Graphics2D g, MobArt mob, Random random) {
        Color outline = shade(mob.dark(), 0.18);
        block(g, 16, 15, 32, 26, mob.base(), outline);
        block(g, 18, 17, 28, 9, tint(mob.light(), 0.08), null);
        block(g, 18, 26, 28, 13, mob.base(), null);
        block(g, 22, 41, 20, 8, shade(mob.base(), 0.12), outline);

        if (has(mob, "spots")) {
            spot(g, 20, 20, 4, 4, shade(mob.dark(), 0.08));
            spot(g, 39, 23, 5, 4, shade(mob.dark(), 0.08));
            spot(g, 24, 31, 4, 3, shade(mob.dark(), 0.08));
        }

        if (has(mob, "wool")) {
            puff(g, 16, 14, mob, outline);
            puff(g, 22, 11, mob, outline);
            puff(g, 30, 10, mob, outline);
            puff(g, 38, 12, mob, outline);
            puff(g, 44, 15, mob, outline);
        }

        if (has(mob, "moss")) {
            block(g, 18, 14, 9, 4, rgb(92, 138, 78), null);
            block(g, 37, 15, 7, 3, rgb(92, 138, 78), null);
            block(g, 30, 18, 8, 3, rgb(92, 138, 78), null);
        }

        if (has(mob, "shell")) {
            block(g, 21, 21, 22, 17, mob.light(), outline);
            for (int stripeX : new int[]{25, 32, 39}) {
                block(g, stripeX, 23, 2, 13, tint(mob.base(), 0.1), null);
            }
        }

        if (has(mob, "frog_eyes")) {
            eyeBump(g, 18, mob.light(), outline);
            eyeBump(g, 40, mob.light(), outline);
        }

        if (has(mob, "ears")) {
            ear(g, 15, mob.base(), outline);
            ear(g, 43, mob.base(), outline);
        }

        if (has(mob, "long_ears")) {
            longEar(g, 15, mob.base(), mob.light(), outline);
            longEar(g, 44, mob.base(), mob.light(), outline);
        }

        if (has(mob, "horns")) {
            horn(g, 16, mob.light(), outline);
            horn(g, 45, mob.light(), outline);
        }

        if (has(mob, "gills")) {
            block(g, 9, 22, 4, 12, tint(mob.base(), 0.06), outline);
            block(g, 51, 22, 4, 12, tint(mob.base(), 0.06), outline);
        }

        if (has(mob, "beard")) {
            block(g, 30, 47, 4, 5, tint(mob.light(), 0.12), outline);
        }

        if (has(mob, "mushroom")) {
            mushroom(g, 15, outline);
            mushroom(g, 39, outline);
        }

        if (has(mob, "wings")) {
            wing(g, 2, tint(mob.light(), 0.25), tint(mob.light(), 0.05));
            wing(g, 54, tint(mob.light(), 0.25), tint(mob.light(), 0.05));
        }

        if (has(mob, "tentacles")) {
            int[] tentacles = {19, 24, 29, 34, 39, 44};
            for (int i = 0; i < tentacles.length; i++) {
                block(g, tentacles[i], 41 + (i % 2), 3, 12, shade(mob.base(), 0.12), outline);
            }
        }

        if (has(mob, "fins")) {
            fin(g, 8, tint(mob.light(), 0.18), outline);
            fin(g, 51, tint(mob.light(), 0.18), outline);
            block(g, 29, 11, 6, 7, tint(mob.light(), 0.18), outline);
        }

        if (has(mob, "stripe")) {
            block(g, 18, 22, 28, 3, mob.dark(), null);
            block(g, 18, 28, 28, 3, mob.dark(), null);
            block(g, 18, 34, 28, 3, mob.dark(), null);
        }

        if (has(mob, "glow")) {
            sparkle(g, 20, 18, 4, 4, tint(mob.accent(), 0.25));
            sparkle(g, 40, 21, 3, 3, tint(mob.accent(), 0.25));
            sparkle(g, 24, 31, 3, 3, tint(mob.accent(), 0.25));
            sparkle(g, 37, 34, 4, 4, tint(mob.accent(), 0.25));
        }

        if (has(mob, "panda_eyes")) {
            eyePatch(g, 16, mob.dark(), outline);
            eyePatch(g, 40, mob.dark(), outline);
        }

        if (has(mob, "crest")) {
            block(g, 28, 11, 8, 4, mob.accent(), outline);
        }

        if (has(mob, "snout")) {
            block(g, 24, 34, 16, 9, mob.light(), outline);
            block(g, 27, 37, 3, 3, mob.dark(), null);
            block(g, 34, 37, 3, 3, mob.dark(), null);
        }

        Color eyeWhite = tint(mob.light(), 0.25);
        Color eyeFill = ("panda".equals(mob.name()) || "wolf".equals(mob.name())) ? rgb(212, 61, 56) : mob.accent();
        eye(g, 21, eyeWhite, eyeFill, outline);
        eye(g, 37, eyeWhite, eyeFill, outline);

        if ("cod".equals(mob.name()) || "salmon".equals(mob.name())) {
            block(g, 18, 30, 4, 2, mob.dark(), null);
            block(g, 42, 30, 4, 2, mob.dark(), null);
        }

        switch (mob.name()) {
            case "bee" -> block(g, 24, 35, 16, 6, tint(mob.base(), 0.1), outline);
            case "parrot" -> {
                block(g, 28, 33, 8, 10, mob.accent(), outline);
                block(g, 31, 38, 2, 3, mob.dark(), null);
            }
            case "chicken" -> {
                block(g, 27, 33, 10, 7, mob.accent(), outline);
                block(g, 29, 40, 6, 3, rgb(220, 41, 38), null);
            }
            case "frog" -> block(g, 25, 37, 14, 3, shade(mob.base(), 0.2), null);
            case "glow_squid" -> block(g, 18, 18, 28, 20, shade(mob.base(), 0.05), outline);
            case "strider" -> {
                block(g, 24, 17, 16, 10, shade(mob.base(), 0.08), outline);
                block(g, 20, 24, 24, 4, tint(mob.light(), 0.1), null);
            }
            case "sniffer" -> {
                block(g, 18, 24, 28, 16, shade(mob.base(), 0.04), outline);
                block(g, 23, 35, 18, 6, rgb(229, 120, 67), outline);
            }
            default -> {
            }
        }

        Color shadow = shade(mob.base(), 0.18);
        fill(g, 18, 40, 28, 1, shadow);
        fill(g, 16, 39, 2, 2, shadow);
        fill(g, 46, 39, 2, 2, shadow);

        for (int i = 0; i < 8; i++) {
            int x = random.nextInt(33) + 14;
            int y = random.nextInt(37) + 13;
            if (random.nextDouble() < 0.35) {
                fill(g, x, y, 1, 1, tint(mob.light(), 0.18));
            }
        }
    }

    private static void addForeground(Graphics2D g, MobArt mob, Random random) {
        for (int i = 0; i < 12; i++) {
            int x = random.nextInt(59);
            int y = random.nextInt(15) + 48;
            int height = random.nextInt(7) + 2;
            fill(g, x, y, 2, height, tint(mob.backgroundTop(), 0.06));
        }

        if (List.of("cod", "salmon", "squid", "glow_squid", "axolotl", "turtle").contains(mob.name())) {
            Color wave = tint(mob.backgroundTop(), 0.12);
            for (int y : new int[]{45, 50, 55}) {
                for (int x = 0; x < 64; x += 6) {
                    fill(g, x, y, 4, 1, wave);
                }
            }
        }

        if (List.of("bee", "parrot").contains(mob.name())) {
            fill(g, 10, 16, 2, 2, tint(mob.light(), 0.35));
            fill(g, 49, 16, 2, 2, tint(mob.light(), 0.35));
        }

        if (List.of("mooshroom", "cow", "sheep", "pig", "horse", "goat").contains(mob.name())) {
            fill(g, 11, 52, 4, 6, shade(mob.dark(), 0.1));
            fill(g, 50, 52, 4, 6, shade(mob.dark(), 0.1));
        }
    }

    private static void addAtmosphere(Graphics2D g, MobArt mob, Random random) {
        for (int i = 0; i < 18; i++) {
            int x = random.nextInt(MASTER_SIZE - 20) + 10;
            int y = random.nextInt(MASTER_SIZE - 16) + 8;
            int size = random.nextInt(5) + 3;
            g.setColor(alpha(tint(mob.accent(), 0.25), random.nextInt(36) + 20));
            g.fillOval(x, y, size, size);
        }
    }

    private static BufferedImage scale(BufferedImage source, int width, int height) {
        BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = scaled.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g.drawImage(source, 0, 0, width, height, null);
        g.dispose();
        return scaled;
    }

    private static MobArt mob(String name, Color base, Color dark, Color light, Color backgroundTop, Color backgroundBottom,
                              Color accent, String... features) {
        return new MobArt(name, base, dark, light, backgroundTop, backgroundBottom, accent, List.of(features));
    }

    private static boolean has(MobArt mob, String feature) {
        return mob.features().contains(feature);
    }

    private static void puff(Graphics2D g, int x, int y, MobArt mob, Color outline) {
        block(g, x, y, 6, 6, tint(mob.light(), 0.08), outline);
    }

    private static void ear(Graphics2D g, int x, Color base, Color outline) {
        block(g, x, 9, 6, 8, base, outline);
    }

    private static void longEar(Graphics2D g, int x, Color base, Color light, Color outline) {
        block(g, x, 4, 5, 14, base, outline);
        block(g, x + 1, 7, 2, 8, tint(light, 0.14), null);
    }

    private static void horn(Graphics2D g, int x, Color light, Color outline) {
        block(g, x, 8, 3, 9, tint(light, 0.2), outline);
    }

    private static void mushroom(Graphics2D g, int x, Color outline) {
        block(g, x, 7, 10, 4, rgb(222, 204, 196), outline);
        block(g, x + 2, 3, 6, 6, rgb(194, 51, 47), outline);
    }

    private static void wing(Graphics2D g, int x, Color fill, Color outline) {
        block(g, x, 20, 8, 14, fill, outline);
    }

    private static void fin(Graphics2D g, int x, Color fill, Color outline) {
        block(g, x, 25, 5, 10, fill, outline);
    }

    private static void eyeBump(Graphics2D g, int x, Color fill, Color outline) {
        block(g, x, 10, 6, 6, fill, outline);
    }

    private static void eyePatch(Graphics2D g, int x, Color fill, Color outline) {
        block(g, x, 24, 8, 8, fill, outline);
    }

    private static void eye(Graphics2D g, int x, Color white, Color iris, Color outline) {
        block(g, x, 24, 5, 5, white, outline);
        block(g, x + 1, 25, 2, 2, iris, null);
    }

    private static void sparkle(Graphics2D g, int x, int y, int w, int h, Color fill) {
        block(g, x, y, w, h, fill, null);
    }

    private static void spot(Graphics2D g, int x, int y, int w, int h, Color fill) {
        block(g, x, y, w, h, fill, null);
    }

    private static void block(Graphics2D g, int x, int y, int w, int h, Color fill, Color outline) {
        fill(g, x, y, w, h, fill);
        if (outline != null) {
            fill(g, x, y, w, 1, outline);
            fill(g, x, y + h - 1, w, 1, outline);
            fill(g, x, y, 1, h, outline);
            fill(g, x + w - 1, y, 1, h, outline);
        }
    }

    private static void fill(Graphics2D g, int x, int y, int w, int h, Color color) {
        g.setColor(color);
        g.fillRect(x * PIXEL, y * PIXEL, w * PIXEL, h * PIXEL);
    }

    private static Color tint(Color color, double amount) {
        return new Color(
                clamp(color.getRed() + ((255 - color.getRed()) * amount)),
                clamp(color.getGreen() + ((255 - color.getGreen()) * amount)),
                clamp(color.getBlue() + ((255 - color.getBlue()) * amount))
        );
    }

    private static Color shade(Color color, double amount) {
        return new Color(
                clamp(color.getRed() * (1.0 - amount)),
                clamp(color.getGreen() * (1.0 - amount)),
                clamp(color.getBlue() * (1.0 - amount))
        );
    }

    private static Color shift(Color color, int amount) {
        return new Color(
                clamp(color.getRed() + amount),
                clamp(color.getGreen() + amount),
                clamp(color.getBlue() + amount)
        );
    }

    private static Color mix(Color a, Color b, double amount) {
        return new Color(
                clamp(a.getRed() + ((b.getRed() - a.getRed()) * amount)),
                clamp(a.getGreen() + ((b.getGreen() - a.getGreen()) * amount)),
                clamp(a.getBlue() + ((b.getBlue() - a.getBlue()) * amount))
        );
    }

    private static Color alpha(Color color, int alpha) {
        return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
    }

    private static Color rgb(int red, int green, int blue) {
        return new Color(red, green, blue);
    }

    private static int clamp(double value) {
        return Math.max(0, Math.min(255, (int) Math.round(value)));
    }

    private static double clamp01(double value) {
        return Math.max(0.0, Math.min(1.0, value));
    }
}
