#!/usr/bin/env python3
"""
Aggressive pixel art wall weathering with zoomed comparison output.
"""

import colorsys
import random

from noise import pnoise2
from PIL import Image, ImageDraw, ImageFont


def make_noise_map(w, h, seed=0, scale=0.08, octaves=3):
    """Generate a 2D Perlin noise map normalized to 0.0-1.0"""
    nmap = []
    for y in range(h):
        row = []
        for x in range(w):
            # pnoise2 returns roughly -1 to 1, normalize to 0-1
            val = pnoise2(
                x * scale + seed * 100,
                y * scale + seed * 100,
                octaves=octaves,
                persistence=0.5,
                lacunarity=2.0,
            )
            row.append((val + 1.0) / 2.0)  # normalize to 0-1
        nmap.append(row)
    return nmap


def rgb_to_hsv(r, g, b):
    h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)
    return h * 360, s * 100, v * 100


def hsv_to_rgb(h, s, v):
    r, g, b = colorsys.hsv_to_rgb(h / 360, s / 100, v / 100)
    return (int(r * 255), int(g * 255), int(b * 255))


def shift_color(rgb, hue_shift=0, sat_shift=0, val_shift=0):
    h, s, v = rgb_to_hsv(*rgb)
    h = (h + hue_shift) % 360
    s = max(0, min(100, s + sat_shift))
    v = max(0, min(100, v + val_shift))
    return hsv_to_rgb(h, s, v)


def is_mortar(pixel, threshold=120):
    return pixel[1] < threshold


def is_bright_brick(pixel, threshold=180):
    return pixel[1] > threshold


def apply_weathering(img, noise_seed=0):
    result = img.copy()
    w, h = img.size

    random.seed(42 + noise_seed)

    # Generate noise maps at different scales for different effects
    # Coarse noise: large blotchy weathering patterns
    noise_coarse = make_noise_map(w, h, seed=noise_seed, scale=0.06, octaves=2)
    # Fine noise: small pixel-level variation
    noise_fine = make_noise_map(w, h, seed=noise_seed + 50, scale=0.15, octaves=3)

    # PASS 1: Deepen mortar intersections - MORE AGGRESSIVE
    for y in range(h):
        for x in range(w):
            px = img.getpixel((x, y))
            if not is_mortar(px):
                continue

            mortar_neighbors = 0
            for dx, dy in [
                (-1, 0),
                (1, 0),
                (0, -1),
                (0, 1),
                (-1, -1),
                (1, -1),
                (-1, 1),
                (1, 1),
            ]:
                nx, ny = (x + dx) % w, (y + dy) % h
                if is_mortar(img.getpixel((nx, ny))):
                    mortar_neighbors += 1

            if mortar_neighbors >= 6:
                # Deep blue-shifted shadow
                new_px = shift_color(px, hue_shift=25, sat_shift=15, val_shift=-20)
                result.putpixel((x, y), new_px)
            elif mortar_neighbors >= 4:
                new_px = shift_color(px, hue_shift=12, sat_shift=8, val_shift=-10)
                result.putpixel((x, y), new_px)

    # PASS 2: Warm highlights on brick edges
    for y in range(h):
        for x in range(w):
            px = img.getpixel((x, y))
            if is_mortar(px):
                continue

            above = img.getpixel((x, (y - 1) % h))
            left = img.getpixel(((x - 1) % w, y))

            is_top_edge = is_mortar(above)
            is_left_edge = is_mortar(left)

            if is_top_edge or is_left_edge:
                random.seed(hash((x // 6, y // 4)) + 99)
                if random.random() < 0.4:
                    # Warm yellow highlight
                    new_px = shift_color(px, hue_shift=-15, sat_shift=5, val_shift=10)
                    result.putpixel((x, y), new_px)

    random.seed(42)

    # PASS 3: Vary whole brick colors - MORE VISIBLE
    for y in range(h):
        for x in range(w):
            px = img.getpixel((x, y))
            if is_mortar(px):
                continue

            brick_id = (x // 12, y // 8)
            random.seed(hash(brick_id) + 42)
            roll = random.random()

            if roll < 0.2:
                # Darker, warmer brick
                current = result.getpixel((x, y))
                new_px = shift_color(current, hue_shift=8, sat_shift=6, val_shift=-12)
                result.putpixel((x, y), new_px)
            elif roll < 0.35:
                # Slightly cooler brick
                current = result.getpixel((x, y))
                new_px = shift_color(current, hue_shift=-8, sat_shift=4, val_shift=-5)
                result.putpixel((x, y), new_px)

    random.seed(42 + noise_seed)

    # PASS 4: Full-height darkening gradient (rising damp / ground grime)
    # Modulated by Perlin noise so the damp line isn't perfectly horizontal
    GRADIENT_EXPONENT = 3.5
    GRADIENT_MAX_DARKEN = -30
    GRADIENT_MAX_HUE_COOL = 25
    GRADIENT_MAX_SAT_BUMP = 12

    for y in range(h):
        progress = y / (h - 1)
        curve = progress**GRADIENT_EXPONENT
        for x in range(w):
            # Noise pushes the gradient up or down per-pixel
            # coarse noise warps the damp line by +/- 30% of its intensity
            noise_mod = 0.7 + noise_coarse[y][x] * 0.6  # range: 0.7 to 1.3
            modulated = min(1.0, curve * noise_mod)

            px = result.getpixel((x, y))
            val_drop = GRADIENT_MAX_DARKEN * modulated
            hue_cool = GRADIENT_MAX_HUE_COOL * modulated
            sat_bump = GRADIENT_MAX_SAT_BUMP * modulated
            new_px = shift_color(
                px, hue_shift=hue_cool, sat_shift=sat_bump, val_shift=val_drop
            )
            result.putpixel((x, y), new_px)

    # PASS 5: Stain patches - BIGGER AND MORE VISIBLE
    num_stains = 6
    for _ in range(num_stains):
        cx = random.randint(0, w - 1)
        cy = random.randint(0, h - 1)
        radius = random.randint(3, 7)

        for dy in range(-radius, radius + 1):
            for dx in range(-radius, radius + 1):
                dist = (dx * dx + dy * dy) ** 0.5
                if dist > radius:
                    continue

                nx, ny = (cx + dx) % w, (cy + dy) % h
                if is_mortar(img.getpixel((nx, ny))):
                    continue

                intensity = 1.0 - (dist / radius)
                current = result.getpixel((nx, ny))
                new_px = shift_color(
                    current, hue_shift=8, sat_shift=6, val_shift=-10 * intensity
                )
                result.putpixel((nx, ny), new_px)

    # PASS 6: Crack line (one diagonal crack)
    crack_x = random.randint(w // 4, 3 * w // 4)
    crack_y = random.randint(0, h // 3)
    for i in range(12):
        cx = crack_x + random.choice([-1, 0, 0, 1])
        cy = crack_y + i
        crack_x = cx
        if 0 <= cx < w and 0 <= cy < h:
            px = result.getpixel((cx, cy))
            if not is_mortar(img.getpixel((cx, cy))):
                # Dark cool crack pixel
                crack_color = shift_color(px, hue_shift=20, sat_shift=10, val_shift=-25)
                result.putpixel((cx, cy), crack_color)

    # PASS 7: Moss specks in mortar - MORE OF THEM
    random.seed(77 + noise_seed)
    for y in range(h // 3, h):
        for x in range(w):
            px = img.getpixel((x, y))
            if not is_mortar(px):
                continue
            depth = (y - h // 3) / (2 * h // 3)
            # Noise modulates where moss appears
            moss_chance = 0.06 * depth * (0.5 + noise_fine[y][x])
            if random.random() < moss_chance:
                current = result.getpixel((x, y))
                moss = shift_color(current, hue_shift=-80, sat_shift=20, val_shift=-10)
                result.putpixel((x, y), moss)

    # PASS 8: Perlin noise surface grime overlay
    # Fine noise adds subtle per-pixel value/hue variation across brick faces
    # This is the pass that makes each seed look distinctly different
    for y in range(h):
        for x in range(w):
            px = img.getpixel((x, y))
            if is_mortar(px):
                continue  # Only affect brick faces

            current = result.getpixel((x, y))
            n = noise_fine[y][x]  # 0.0 to 1.0

            # Map noise to subtle shifts: center around 0
            # n=0.5 means no change, n=0 means darken, n=1 means lighten slightly
            centered = n - 0.5  # -0.5 to 0.5

            val_shift = centered * 10  # -5 to +5 value
            hue_shift = centered * 8  # -4 to +4 hue
            sat_shift = abs(centered) * 4  # darker areas get slightly more saturated

            new_px = shift_color(
                current, hue_shift=hue_shift, sat_shift=sat_shift, val_shift=val_shift
            )
            result.putpixel((x, y), new_px)

    return result


def main():
    img = Image.open(
        "/home/saarsena/godot_projects/trench/textures/td_sliced/BRICK_1A.png"
    ).convert("RGB")
    w, h = img.size

    # Generate 4 variants with different noise seeds
    variants = []
    for seed in range(4):
        variant = apply_weathering(img, noise_seed=seed)
        variant.save(f"./weathered_wall_v{seed}.png")
        variants.append(variant)
        print(f"Generated variant {seed}")

    # Create a 2x2 grid comparison of all 4 variants, zoomed 6x
    zoom = 6
    zw, zh = w * zoom, h * zoom
    grid = Image.new("RGB", (zw * 2 + 4, zh * 2 + 4), (30, 30, 30))
    for i, v in enumerate(variants):
        zoomed = v.resize((zw, zh), Image.NEAREST)
        col = i % 2
        row = i // 2
        grid.paste(zoomed, (col * (zw + 4), row * (zh + 4)))
    grid.save("./wall_variants_grid.png")

    # Also make a side-by-side original vs variant 0
    orig_zoomed = img.resize((zw, zh), Image.NEAREST)
    weath_zoomed = variants[0].resize((zw, zh), Image.NEAREST)
    gap = 20
    comparison = Image.new("RGB", (zw * 2 + gap, zh + 40), (30, 30, 30))
    comparison.paste(orig_zoomed, (0, 40))
    comparison.paste(weath_zoomed, (zw + gap, 40))
    draw = ImageDraw.Draw(comparison)
    draw.text((zw // 2 - 30, 10), "ORIGINAL", fill=(200, 200, 200))
    draw.text((zw + gap + zw // 2 - 35, 10), "WEATHERED", fill=(200, 200, 200))
    comparison.save("./wall_comparison.png")

    print("Saved: 4 variants, grid comparison, and side-by-side")


if __name__ == "__main__":
    main()
