"""Ground textures for the six biomes after the crater meadow.

Each biome follows its reference in docs/inspiration/fonds/<biome>_0.png. Two
surfaces per biome — a base and the patches that break it — plus, where the
biome needs one, an outer surface or a glow mask. All tileable.

Reuses the helpers of crater_meadow_textures.py and restaurant_textures.py.

Run headless from big-egg-3d/:
  blender --background --factory-startup --python tools/blender/biome_textures.py [-- --only old_port,downtown]
"""
import os, sys
import numpy as np

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import crater_meadow_textures as base  # noqa: E402
import restaurant_textures as tiles  # noqa: E402

hex_rgb = base.hex_rgb
noise = base.periodic_noise


def out(biome):
    base.OUT = os.path.join(base.PROJECT, "assets", "textures", "biomes", biome)
    os.makedirs(base.OUT, exist_ok=True)


def cracks(img, count, steps, colour, seed, width=1):
    """Wandering hairline cracks, wrapped at the edges."""
    r = np.random.default_rng(seed)
    size = img.shape[0]
    for _ in range(count):
        x, y = r.uniform(0, size, 2)
        a = r.uniform(0, 2 * np.pi)
        for _ in range(int(r.integers(steps // 2, steps))):
            a += r.normal(0, 0.35)
            x = (x + np.cos(a) * 1.5) % size
            y = (y + np.sin(a) * 1.5) % size
            for dx in range(width):
                img[int(y) % size, int(x + dx) % size] = colour
            if r.random() < 0.02:
                a += r.choice([-1.2, 1.2])


def planks(size, cols, palette, gap, seed, vertical=True):
    """Boards with staggered end joints and stretched grain."""
    r = np.random.default_rng(seed)
    img = np.zeros((size, size, 3))
    width = size // cols
    grain = noise(size, 1.1, seed + 1)
    grain = np.repeat(grain[:, ::8], 8, axis=1)[:, :size] if vertical else np.repeat(grain[::8, :], 8, axis=0)[:size]
    for k in range(cols):
        start = pos = int(r.integers(0, size))
        while pos - start < size:
            length = int(r.integers(size // 3, size // 2 + size // 4))
            colour = palette[int(r.integers(0, len(palette)))] * r.uniform(0.88, 1.1)
            span = [(pos + d) % size for d in range(length)]
            if vertical:
                img[span, k * width:(k + 1) * width] = colour
                img[(pos + length) % size, k * width:(k + 1) * width] = gap
                # Two nails at each end.
                for nail_y in ((pos + 6) % size, (pos + length - 6) % size):
                    for nx in (k * width + 5, (k + 1) * width - 7):
                        img[nail_y:nail_y + 2, nx:nx + 2] = hex_rgb("#15100c")
            else:
                img[k * width:(k + 1) * width, span] = colour
                img[k * width:(k + 1) * width, (pos + length) % size] = gap
            pos += length + 1
        if vertical:
            img[:, k * width:k * width + 3] = gap
        else:
            img[k * width:k * width + 3, :] = gap
    return img * (0.78 + 0.34 * grain[..., None])


def voronoi(size, count, seed):
    """Periodic Voronoi: distance to nearest seed, to second nearest, cell id."""
    r = np.random.default_rng(seed)
    # Keep seeds apart (wrapped): two close seeds make a thin sliver cell whose
    # crack would glow as a spike.
    pts = []
    spacing = size / np.sqrt(count) * 0.6
    while len(pts) < count:
        p = r.uniform(0, size, 2)
        if all(np.hypot(*((p - q + size / 2) % size - size / 2)) >= spacing for q in pts):
            pts.append(p)
    pts = np.array(pts)
    tiled = np.concatenate([pts + np.array([dx, dy]) * size for dx in (-1, 0, 1) for dy in (-1, 0, 1)])
    ids = np.tile(np.arange(count), 9)
    f1 = np.zeros((size, size))
    f2 = np.zeros((size, size))
    cell = np.zeros((size, size), dtype=int)
    xs = np.arange(size)
    for y in range(0, size, 16):
        yy, xx = np.meshgrid(np.arange(y, min(size, y + 16)), xs, indexing="ij")
        d = np.sqrt((xx[..., None] - tiled[:, 0]) ** 2 + (yy[..., None] - tiled[:, 1]) ** 2)
        order = np.argsort(d, axis=2)[..., :2]
        f1[y:y + 16] = np.take_along_axis(d, order[..., :1], axis=2)[..., 0]
        f2[y:y + 16] = np.take_along_axis(d, order[..., 1:2], axis=2)[..., 0]
        cell[y:y + 16] = ids[order[..., 0]]
    return f1, f2, cell


# ---------------------------------------------------------------- biomes

def old_port():
    out("old_port")
    wood = [hex_rgb(h) for h in ("#4a3526", "#3f2d20", "#553d2b", "#46321f")]
    base.save("dock_planks", planks(1024, 8, wood, hex_rgb("#140e0a"), 201))
    wet = [hex_rgb(h) for h in ("#33302a", "#2b2a24", "#3a352b")]
    img = planks(1024, 8, wet, hex_rgb("#0d0b09"), 202)
    algae = noise(1024, 1.4, 203)[..., None]
    img = img * (1 - 0.3 * algae) + hex_rgb("#1f3a33") * 0.3 * algae
    base.save("dock_planks_wet", img)


def olympic_park():
    out("olympic_park")
    img, joint = tiles.grid_tiles(1024, 4, 4, 4, [hex_rgb(h) for h in ("#7d7e7a", "#74756f", "#83847e")], 211, grime=0.18)
    img[joint] = hex_rgb("#46473f")
    img = img * (0.88 + 0.2 * noise(1024, 1.3, 212)[..., None])
    cracks(img, 40, 220, hex_rgb("#3a3b36"), 213)
    base.save("plaza_concrete", img)
    moss = hex_rgb("#3d4a2a") * (0.7 + 0.5 * noise(1024, 1.2, 214)[..., None])
    grey = img * 0.8
    mask = noise(1024, 1.7, 215)[..., None]
    base.save("plaza_moss", grey * (1 - mask) + moss * mask)


def mount_royal():
    out("mount_royal")
    f1, f2, cell = voronoi(512, 70, 221)
    edge = f2 - f1
    r = np.random.default_rng(222)
    shade = r.uniform(0.8, 1.15, 70)[cell][..., None]
    rock = hex_rgb("#302628") * shade * (0.85 + 0.3 * noise(512, 1.2, 223)[..., None])
    # Rounded stones: lighter toward the middle of each cell.
    rock = rock * (0.75 + 0.35 * np.clip(edge / 14.0, 0, 1)[..., None])
    rock[edge < 3.0] = hex_rgb("#120a08")
    base.save("basalt", rock)
    glow = np.clip(1.0 - edge / 3.5, 0, 1)
    base.save_gray("basalt_cracks", glow)
    ash = hex_rgb("#4d4846") * (0.8 + 0.4 * noise(512, 1.0, 224)[..., None])
    base.stamp_blobs(ash, 120, (1.5, 3.5), hex_rgb("#6b6461"), hex_rgb("#2a2524"), 225)
    base.save("ash", ash)


def downtown():
    out("downtown")
    tar = hex_rgb("#343335") * (0.8 + 0.35 * noise(1024, 1.2, 231)[..., None]) * (0.9 + 0.2 * noise(1024, 0.5, 232)[..., None])
    specks = np.random.default_rng(233).random((1024, 1024)) < 0.03
    tar[specks] = hex_rgb("#5c5a5d")
    cracks(tar, 60, 260, hex_rgb("#161517"), 234, 2)
    base.save("street_asphalt", tar)
    rubble = hex_rgb("#4a4039") * (0.75 + 0.45 * noise(1024, 1.3, 235)[..., None])
    base.stamp_blobs(rubble, 500, (2.0, 6.0), hex_rgb("#6e655c"), hex_rgb("#231d19"), 236, hex_rgb("#948a7e"))
    base.stamp_blobs(rubble, 160, (2.0, 5.0), hex_rgb("#7b3f2f"), hex_rgb("#2a150f"), 237)
    base.save("street_rubble", rubble)
    walk, joint = tiles.grid_tiles(512, 2, 2, 4, [hex_rgb("#7c7a74"), hex_rgb("#75736d")], 238, grime=0.2)
    walk[joint] = hex_rgb("#45433f")
    cracks(walk, 12, 150, hex_rgb("#3e3c38"), 239)
    base.save("street_sidewalk", walk)


def underground_city():
    out("underground_city")
    img, joint = tiles.grid_tiles(1024, 8, 8, 4, [hex_rgb(h) for h in ("#2d3632", "#29302d", "#323b36")], 241, grime=0.45)
    img[joint] = hex_rgb("#141816")
    img = img * (0.8 + 0.3 * noise(1024, 1.4, 242)[..., None])
    cracks(img, 25, 200, hex_rgb("#0d100e"), 243)
    base.save("sewer_tiles", img)
    sludge = hex_rgb("#1b221a") * (0.7 + 0.5 * noise(1024, 1.1, 244)[..., None])
    base.save("sewer_sludge", sludge)


def world_egg_center():
    out("world_egg_center")
    img, joint = tiles.grid_tiles(1024, 4, 4, 3, [hex_rgb(h) for h in ("#e6e8ea", "#e0e3e6", "#eaecee")], 251, bevel=0.05)
    img[joint] = hex_rgb("#b4bac0")
    img = img * (0.96 + 0.05 * noise(1024, 1.0, 252)[..., None])
    base.save("lab_tiles", img)
    smudge = img.copy()
    mask = noise(1024, 1.8, 253)[..., None]
    smudge = smudge * (1 - 0.25 * mask)
    base.stamp_blobs(smudge, 140, (1.5, 4.0), hex_rgb("#8e959c"), hex_rgb("#6f767d"), 254)
    base.save("lab_scuffs", smudge)


MAKERS = {"old_port": old_port, "olympic_park": olympic_park, "mount_royal": mount_royal,
          "downtown": downtown, "underground_city": underground_city, "world_egg_center": world_egg_center}

if __name__ == "__main__":
    args = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
    only = args[args.index("--only") + 1].split(",") if "--only" in args else list(MAKERS)
    for key in only:
        MAKERS[key]()
