"""
Prepare voxel textures from FreeStylized.com ZIPs.
Outputs per material:
  - *_albedo.png  : RGBA (RGB=albedo, A=heightmap)
  - *_normal.png  : RGB normal map (OpenGL convention, Y-up)
"""
import io
import os
import zipfile
from PIL import Image, ImageEnhance

# (zip_name, color_pattern, height_pattern, normal_pattern, brightness_factor)
# brightness_factor: <1 = darken, >1 = brighten, 1.0 = unchanged
MATERIALS = [
    ("grass_01_1k",         "color",     "height", "normal_gl", 1.0),
    ("ground_02_1k",        "color",     "height", "normal_gl", 0.75),  # dirt: darkened
    ("ground_stones_01_1k", "baseColor", "height", "normal_gl", 1.0),
    ("sand_01_1k",          "color",     "height", "normal_gl", 1.0),
    ("snow_01_1k",          "color",     "height", "normal_gl", 1.0),
    ("rock_01_1k",          "color",     "height", "normal_gl", 1.0),
]

OUTPUT_NAMES = [
    "grass",
    "dirt",
    "stone",
    "sand",
    "snow",
    "smoothstone",
]

TARGET_SIZE = 512
RAW_DIR = os.path.join(os.path.dirname(__file__), "..", "assets", "raw")
OUT_DIR = os.path.join(os.path.dirname(__file__), "..", "assets", "voxel")


def find_file_in_zip(zf, pattern):
    """Find a file in the zip matching a pattern substring."""
    for name in zf.namelist():
        basename = os.path.basename(name).lower()
        if pattern.lower() in basename and basename.endswith(".png"):
            return name
    return None


def load_image_from_zip(zf, filename, mode="RGB"):
    data = zf.read(filename)
    img = Image.open(io.BytesIO(data))
    # Handle 16-bit heightmaps: Pillow's .convert("L") on I;16 images
    # doesn't scale properly. We must manually scale 0-65535 → 0-255.
    if img.mode in ("I;16", "I") and mode == "L":
        # Convert to 32-bit int first, then scale down
        img = img.convert("I")
        img = img.point(lambda v: v / 256)
        return img.convert("L")
    return img.convert(mode)


def process_material(zip_path, color_pat, height_pat, normal_pat, brightness, out_name):
    with zipfile.ZipFile(zip_path, "r") as zf:
        color_file = find_file_in_zip(zf, color_pat)
        height_file = find_file_in_zip(zf, height_pat)
        normal_file = find_file_in_zip(zf, normal_pat)

        if not color_file:
            print(f"  ERROR: no color file matching '{color_pat}' in {zip_path}")
            return False

        # ── Albedo + Heightmap → RGBA ──
        color_img = load_image_from_zip(zf, color_file, "RGB")

        if brightness != 1.0:
            color_img = ImageEnhance.Brightness(color_img).enhance(brightness)

        if height_file:
            height_img = load_image_from_zip(zf, height_file, "L")
        else:
            print(f"  WARNING: no height map, deriving from luminance")
            height_img = color_img.convert("L")

        color_img = color_img.resize((TARGET_SIZE, TARGET_SIZE), Image.LANCZOS)
        height_img = height_img.resize((TARGET_SIZE, TARGET_SIZE), Image.LANCZOS)

        r, g, b = color_img.split()
        rgba = Image.merge("RGBA", (r, g, b, height_img))

        albedo_path = os.path.join(OUT_DIR, f"{out_name}_albedo.png")
        rgba.save(albedo_path, "PNG")
        print(f"  OK: {out_name}_albedo.png ({TARGET_SIZE}x{TARGET_SIZE})")

        # ── Normal map → RGB ──
        if normal_file:
            normal_img = load_image_from_zip(zf, normal_file, "RGB")
            normal_img = normal_img.resize((TARGET_SIZE, TARGET_SIZE), Image.LANCZOS)
            normal_path = os.path.join(OUT_DIR, f"{out_name}_normal.png")
            normal_img.save(normal_path, "PNG")
            print(f"  OK: {out_name}_normal.png ({TARGET_SIZE}x{TARGET_SIZE})")
        else:
            print(f"  WARNING: no normal map found")

        return True


def main():
    os.makedirs(OUT_DIR, exist_ok=True)
    print(f"Output directory: {os.path.abspath(OUT_DIR)}")
    print()

    success = 0
    for i, (zip_name, color_pat, height_pat, normal_pat, brightness) in enumerate(MATERIALS):
        zip_path = os.path.join(RAW_DIR, zip_name + ".zip")
        print(f"[{i+1}/6] {OUTPUT_NAMES[i]} <- {zip_name}.zip")

        if not os.path.exists(zip_path):
            print(f"  ERROR: {zip_path} not found")
            continue

        if process_material(zip_path, color_pat, height_pat, normal_pat, brightness, OUTPUT_NAMES[i]):
            success += 1

    print(f"\nDone: {success}/6 materials generated in {os.path.abspath(OUT_DIR)}")


if __name__ == "__main__":
    main()
