#!/usr/bin/env python3
"""
Black & white city map poster — minimalist style.
Standardized scale: 25km from center to edge (50km across).
"""

import osmnx as ox
import matplotlib.pyplot as plt
import numpy as np
import geopandas as gpd
from shapely.geometry import box
from PIL import Image, ImageDraw, ImageFont
import tempfile, os
import matplotlib.font_manager as fm


def _find_font(candidates, fallback_family):
    """Return the first font path that exists, else a matplotlib-bundled fallback.

    macOS paths are tried first (for local rendering); Linux paths and the
    always-present DejaVu fallback keep the downloadable source working elsewhere.
    """
    for path in candidates:
        if os.path.exists(path):
            return path
    return fm.findfont(fm.FontProperties(family=fallback_family))

# --- Standardized settings ---
HALF_SPAN_KM = 25
SPAN_LAT = HALF_SPAN_KM / 111.0
RADIUS = 28000
DPI = 300
POSTER_W_IN = 10.5
POSTER_H_IN = 13.5
MAP_FRACTION = 0.84
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output")

CITIES = [
    {
        "city": "Toronto",
        "country": "Canada",
        "lat": 43.70, "lon": -79.40,
        "coords": "43° 42' 00'' N | 79° 24' 00'' W",
    },
    {
        "city": "Gotemba",
        "country": "Shizuoka, Japan",
        "lat": 35.24, "lon": 138.88,
        "coords": "35° 18' 32'' N | 138° 56' 05'' E",
    },
    {
        "city": "Singapore",
        "country": "Singapore",
        "lat": 1.3521, "lon": 103.8198,
        "coords": "1° 21' 07'' N | 103° 49' 11'' E",
    },
    {
        "city": "Chicago",
        "country": "Illinois, USA",
        "lat": 41.8781, "lon": -87.6298,
        "coords": "41° 52' 41'' N | 87° 37' 47'' W",
    },
    {
        "city": "Provo",
        "country": "Utah, USA",
        "lat": 40.2338, "lon": -111.6585,
        "coords": "40° 14' 02'' N | 111° 39' 31'' W",
    },
]


def generate_poster(cfg):
    city = cfg["city"]
    country = cfg["country"]
    lat, lon = cfg["lat"], cfg["lon"]
    coord_label = cfg["coords"]
    output_file = os.path.join(OUTPUT_DIR, f"{city.lower().replace(' ', '_')}_poster.png")

    span_lat = SPAN_LAT
    span_lon = span_lat / np.cos(np.radians(lat))
    xmin, xmax = lon - span_lon, lon + span_lon
    ymin, ymax = lat - span_lat, lat + span_lat
    clip_box = box(xmin, ymin, xmax, ymax)

    print(f"\n{'='*50}")
    print(f"  {city.upper()} — {HALF_SPAN_KM*2}km x {HALF_SPAN_KM*2}km")
    print(f"{'='*50}")

    ox.settings.use_cache = False

    print("  Downloading streets...")
    G = ox.graph_from_point((lat, lon), dist=RADIUS, network_type="all")

    print("  Downloading buildings...")
    try:
        buildings = ox.features_from_point((lat, lon), dist=RADIUS, tags={"building": True})
    except Exception:
        buildings = None

    print("  Downloading water...")
    try:
        water = ox.features_from_point((lat, lon), dist=RADIUS, tags={"natural": "water"})
    except Exception:
        water = None

    print("  Downloading parks...")
    try:
        parks = ox.features_from_point((lat, lon), dist=RADIUS, tags={"leisure": "park"})
    except Exception:
        parks = None

    print("  Downloading coastline...")
    try:
        coastline = ox.features_from_point((lat, lon), dist=RADIUS, tags={"natural": "coastline"})
    except Exception:
        coastline = None

    print("  Clipping...")
    pad = span_lat * 0.1
    water_clip_box = box(xmin - pad, ymin - pad, xmax + pad, ymax + pad)
    edges = ox.graph_to_gdfs(G, nodes=False).clip(clip_box)
    if buildings is not None and len(buildings) > 0:
        buildings = buildings.clip(clip_box)
    if water is not None and len(water) > 0:
        water = water.clip(water_clip_box)
    if parks is not None and len(parks) > 0:
        parks = parks.clip(clip_box)
    if coastline is not None and len(coastline) > 0:
        coastline = coastline.clip(water_clip_box)

    highway_types = ["motorway", "trunk", "motorway_link", "trunk_link"]
    major_types = ["primary", "secondary", "primary_link", "secondary_link"]
    medium_types = ["tertiary", "tertiary_link", "residential", "unclassified"]

    if "highway" in edges.columns:
        def classify(hw):
            if isinstance(hw, list):
                if any(h in highway_types for h in hw): return "highway"
                if any(h in major_types for h in hw): return "major"
                if any(h in medium_types for h in hw): return "medium"
                return "minor"
            if hw in highway_types: return "highway"
            if hw in major_types: return "major"
            if hw in medium_types: return "medium"
            return "minor"
        edges["road_class"] = edges["highway"].apply(classify)
    else:
        edges["road_class"] = "medium"

    minor = edges[edges["road_class"] == "minor"]
    medium = edges[edges["road_class"] == "medium"]
    majors = edges[edges["road_class"] == "major"]
    highways = edges[edges["road_class"] == "highway"]

    # --- Render map ---
    print("  Rendering map...")
    target_px = int(POSTER_W_IN * MAP_FRACTION * DPI)
    fig_inches = target_px / DPI
    fig = plt.figure(figsize=(fig_inches, fig_inches), dpi=DPI, facecolor="white")
    ax = fig.add_axes([0, 0, 1, 1])
    ax.set_facecolor("white")

    if buildings is not None and len(buildings) > 0:
        buildings.plot(ax=ax, color="#999999", edgecolor="#666666",
                      linewidth=0.1, alpha=0.85, zorder=1)
    if parks is not None and len(parks) > 0:
        parks.plot(ax=ax, color="#b0b0b0", edgecolor="#888888",
                   linewidth=0.15, alpha=0.8, zorder=1)

    minor.plot(ax=ax, color="#333333", linewidth=0.15, alpha=0.8, zorder=2)
    medium.plot(ax=ax, color="#111111", linewidth=0.35, alpha=0.9, zorder=2)
    majors.plot(ax=ax, color="#000000", linewidth=0.8, alpha=1.0, zorder=3)
    highways.plot(ax=ax, color="#000000", linewidth=2.0, alpha=1.0, zorder=3)

    if water is not None and len(water) > 0:
        water.plot(ax=ax, color="white", edgecolor="black", linewidth=0.6, zorder=4)

    if coastline is not None and len(coastline) > 0:
        coastline.plot(ax=ax, facecolor="none", edgecolor="black", linewidth=0.8, zorder=5)

    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    ax.set_aspect("auto")
    ax.set_xticks([])
    ax.set_yticks([])
    for spine in ax.spines.values():
        spine.set_visible(False)

    tmp_map = os.path.join(tempfile.gettempdir(), f"{city.lower()}_map.png")
    fig.savefig(tmp_map, dpi=DPI, facecolor="white", edgecolor="none", pad_inches=0)
    plt.close(fig)
    map_img = Image.open(tmp_map).convert("RGB")
    os.remove(tmp_map)

    # --- Assemble poster ---
    print("  Assembling poster...")
    poster_w = int(POSTER_W_IN * DPI)
    poster_h = int(POSTER_H_IN * DPI)
    poster = Image.new("RGB", (poster_w, poster_h), "white")

    map_img = map_img.resize((target_px, target_px), Image.LANCZOS)
    map_x = (poster_w - target_px) // 2
    map_y = int(poster_h * 0.04)
    poster.paste(map_img, (map_x, map_y))

    # Text
    draw = ImageDraw.Draw(poster)
    text_area_top = map_y + target_px
    text_area_height = poster_h - text_area_top
    cx = poster_w // 2

    city_font_path = _find_font([
        "/System/Library/Fonts/Supplemental/Georgia Bold.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf",
    ], "serif")
    country_font_path = _find_font([
        "/System/Library/Fonts/Supplemental/Georgia.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf",
    ], "serif")
    coords_font_path = _find_font([
        "/System/Library/Fonts/SFNSMono.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
    ], "monospace")

    font_city = ImageFont.truetype(city_font_path, size=int(DPI * 0.58))
    font_country = ImageFont.truetype(country_font_path, size=int(DPI * 0.25))
    font_coords = ImageFont.truetype(coords_font_path, size=int(DPI * 0.14))

    city_y = text_area_top + int(text_area_height * 0.33)
    draw.text((cx, city_y), city.upper(), fill="black", font=font_city, anchor="mm")

    country_y = text_area_top + int(text_area_height * 0.58)
    draw.text((cx, country_y), country.upper(), fill="black", font=font_country, anchor="mm")

    coords_y = text_area_top + int(text_area_height * 0.80)
    draw.text((cx, coords_y), coord_label, fill="black", font=font_coords, anchor="mm")

    poster.save(output_file, dpi=(DPI, DPI))
    print(f"  Saved: {output_file}")

    pdf_file = output_file.replace(".png", ".pdf")
    poster.save(pdf_file, dpi=(DPI, DPI))
    print(f"  Saved: {pdf_file}")


if __name__ == "__main__":
    for c in CITIES:
        generate_poster(c)
    print(f"\nAll done! Posters in {OUTPUT_DIR}/")
