# -*- coding: utf-8 -*-
"""仮の病院のフロアの形（同じ階・案 h0）を作る。

サービスロボット導入シミュレーション 病院の搬送ロボット（検体・薬剤）用。
実在の病院ではなく、外来・検査部・薬剤部・外来化学療法室・救急室の
典型的な並びをコードで一から起こしたもの（2026-11-09 エイイチと決めた）。

決まり
- 廊下の幅は医療法施行規則 第16条1項11号の一般の廊下に合わせる：中廊下 2.1m 以上
- 外来の大廊下（待合に面する）は 4.0m（仮置き。人が多い所）
- ロボットが通れる最小の幅は W3 の公表値 0.7m。全部の廊下がこれを超える
- 距離は廊下の中心線の上で測る（扉の点 → 廊下 → 扉の点）。試算はこの距離を使う

使い方：python3 make_hospital.py 出力先 [h0|h0b]
出力：hospital_{h0,h0b}.json（形・3ルートの道と距離・点と点の距離 distances）・hospital_h0.png（平面図）
"""
import heapq
import json
import sys
from pathlib import Path

W, D = 120.0, 60.0          # 外寸 m（x 右、y 奥）
HALL_Y0, HALL_Y1 = 27.0, 31.0     # 外来の大廊下 4.0m
STAFF_Y0, STAFF_Y1 = 13.0, 15.1   # 職員の中廊下 2.1m
HALL_C = (HALL_Y0 + HALL_Y1) / 2
STAFF_C = (STAFF_Y0 + STAFF_Y1) / 2
CROWD_X0, CROWD_X1 = 0.0, 70.0   # 外来の大廊下のうち受付・会計・待合の前（人の多い区間）
CONNECT_X = {"西の縦廊下": 24.0, "玄関の通り": 48.0, "東の縦廊下": 99.0}
CONNECT_W = {"西の縦廊下": 2.1, "玄関の通り": 8.0, "東の縦廊下": 2.1}

ROOMS = [  # 名前, x0, y0, x1, y1, 種類
    ("内科外来", 0, 31, 42, 60, "外来"),
    ("ほかの外来", 42, 31, 96, 60, "外来"),
    ("外来化学療法室", 96, 31, 120, 60, "届け先"),
    ("薬剤部", 0, 0, 23, 13, "出発"),
    ("お薬の窓口", 0, 15.1, 23, 27, "その他"),
    ("総合受付・会計・待合", 25.1, 15.1, 70, 27, "混む所"),
    ("倉庫", 25.1, 0, 44, 13, "その他"),
    ("正面玄関", 44, 0, 52, 13, "混む所"),
    ("医事・事務", 52, 0, 70, 13, "その他"),
    ("中央検査室", 70, 0, 98, 13, "届け先"),
    ("生理検査", 70, 15.1, 98, 27, "その他"),
    ("救急室", 100.1, 0, 120, 27, "出発"),
]

POINTS = {  # 扉の前の点（ロボットが積む・受け取る所）。名前: (x, y, つながる廊下の中心の点)
    "内科外来の処置室": (12.0, HALL_Y1, (12.0, HALL_C)),
    "外来化学療法室の受付": (104.0, HALL_Y1, (104.0, HALL_C)),
    "薬剤部の払い出し口": (13.0, STAFF_Y0, (13.0, STAFF_C)),
    "検査部の検体受付": (80.0, STAFF_Y0, (80.0, STAFF_C)),
    "救急室の処置室": (108.0, STAFF_Y0 + 0.0, (108.0, STAFF_C)),
}
# 救急室は x100.1〜 にあり、中廊下の南ではなく東の端。扉は中廊下の東の突き当たりに出す
POINTS["救急室の処置室"] = (100.1, STAFF_C, (99.0, STAFF_C))

ROUTES = {
    "green": ("救急室の処置室", "検査部の検体受付", "検体（救急・病棟）"),
    "blue": ("内科外来の処置室", "検査部の検体受付", "採血管・PCR/抗原・尿"),
    "red": ("薬剤部の払い出し口", "外来化学療法室の受付", "抗がん剤・前投薬"),
}

# 案 h0b（2026-11-09 エイイチと決めた「大廊下を通らない道」）：
# 外来の奥（北の壁ぞい）に職員通路 2.1m を足し、内科外来の処置室の裏口から積む。
# 北の職員通路 → 東の縦廊下（x99）で大廊下の人の少ない東の端を横切る → 職員の中廊下 → 検査部。
# h0 の部屋・廊下・点はそのまま（内科外来・ほかの外来は奥行きが2.1m 縮み、外来化学療法室は縦廊下の分だけ右へ縮む）。
NORTH_Y0, NORTH_Y1 = D - 2.1, D
NORTH_C = (NORTH_Y0 + NORTH_Y1) / 2
NORTH_X1 = 100.05
NORTH_CONNECT_X = 99.0
VARIANTS = {
    "h0": {"name": "仮の病院 h0（外来の階）", "north": False},
    "h0b": {"name": "仮の病院 h0b（外来の階・外来の奥に職員通路）", "north": True},
}


def apply_variant(v):
    """h0b のときだけ、部屋・点・ルートを差し替える（h0 は何もしない）"""
    global ROOMS, POINTS, ROUTES
    if not VARIANTS[v]["north"]:
        return
    rooms = []
    for n, x0, y0, x1, y1, k in ROOMS:
        if n in ("内科外来", "ほかの外来"):
            y1 = NORTH_Y0
        if n == "ほかの外来":
            x1 = NORTH_CONNECT_X - 1.05
        if n == "外来化学療法室":
            x0 = NORTH_CONNECT_X + 1.05
        rooms.append((n, x0, y0, x1, y1, k))
    ROOMS = rooms
    POINTS = dict(POINTS)
    POINTS["内科外来の処置室（裏口）"] = (12.0, NORTH_Y0, (12.0, NORTH_C))
    ROUTES = dict(ROUTES)
    ROUTES["blue"] = ("内科外来の処置室（裏口）", "検査部の検体受付", "採血管・PCR/抗原・尿")


def build_graph(variant="h0"):
    nodes = set()
    edges = {}

    def add(a, b):
        d = abs(a[0] - b[0]) + abs(a[1] - b[1])  # 廊下は縦横だけ
        if abs(a[1] - HALL_C) < 1e-6 and abs(b[1] - HALL_C) < 1e-6:
            d += 0.01 * crowded_len(a[0], b[0])  # 同じ長さなら人の多い所を通らない方を選ぶ（距離には入れない）
        edges.setdefault(a, []).append((b, d))
        edges.setdefault(b, []).append((a, d))
        nodes.update([a, b])

    north = VARIANTS[variant]["north"]
    hall_xs = {0.0, W} | set(CONNECT_X.values())
    staff_xs = {0.0, 100.1} | {v for v in CONNECT_X.values() if v <= 100.1}
    north_xs = {0.0, NORTH_CONNECT_X}
    for name, (x, y, c) in POINTS.items():
        if abs(c[1] - HALL_C) < 1e-6:
            hall_xs.add(c[0])
        elif abs(c[1] - NORTH_C) < 1e-6:
            north_xs.add(c[0])
        else:
            staff_xs.add(c[0])
        add((x, y), c)
    if north:
        hall_xs.add(NORTH_CONNECT_X)
        xs = sorted(north_xs)
        for a, b in zip(xs, xs[1:]):
            add((a, NORTH_C), (b, NORTH_C))
        add((NORTH_CONNECT_X, NORTH_C), (NORTH_CONNECT_X, HALL_C))
    xs = sorted(hall_xs)
    for a, b in zip(xs, xs[1:]):
        add((a, HALL_C), (b, HALL_C))
    xs = sorted(staff_xs)
    for a, b in zip(xs, xs[1:]):
        add((a, STAFF_C), (b, STAFF_C))
    for x in CONNECT_X.values():
        add((x, STAFF_C), (x, HALL_C))
    return edges


def crowded_len(xa, xb):
    lo, hi = min(xa, xb), max(xa, xb)
    return max(0.0, min(hi, CROWD_X1) - max(lo, CROWD_X0))


def true_len(path):
    return sum(abs(p[0] - q[0]) + abs(p[1] - q[1]) for p, q in zip(path, path[1:]))


def shortest(edges, s, t):
    dist = {s: 0.0}
    prev = {}
    q = [(0.0, s)]
    while q:
        d, u = heapq.heappop(q)
        if u == t:
            break
        if d > dist[u]:
            continue
        for v, w in edges[u]:
            nd = d + w
            if nd < dist.get(v, 1e18) - 1e-9:
                dist[v] = nd
                prev[v] = u
                heapq.heappush(q, (nd, v))
    path = [t]
    while path[-1] != s:
        path.append(prev[path[-1]])
    return dist[t], path[::-1]


def on_hall(p, q):
    return abs(p[1] - HALL_C) < 1e-6 and abs(q[1] - HALL_C) < 1e-6


def main(out_dir, variant="h0"):
    apply_variant(variant)
    out = Path(out_dir)
    out.mkdir(parents=True, exist_ok=True)
    edges = build_graph(variant)
    routes = {}
    for key, (a, b, carries) in ROUTES.items():
        pa, pb = POINTS[a][:2], POINTS[b][:2]
        d, path = shortest(edges, pa, pb)
        d = true_len(path)
        hall_m = sum(crowded_len(p[0], q[0]) for p, q in zip(path, path[1:]) if on_hall(p, q))
        routes[key] = {"from": a, "to": b, "carries": carries, "distance_m": round(d, 2),
                       "crowded_hall_m": round(hall_m, 2), "path": [list(map(lambda v: round(v, 2), p)) for p in path]}
    names = list(POINTS)
    dist = {}
    for a in names:
        for b in names:
            if a != b:
                dist[f"{a}|{b}"] = round(true_len(shortest(edges, POINTS[a][:2], POINTS[b][:2])[1]), 2)
    data = {
        "meta": {
            "schema_version": "hospital-layout/0.1",
            "name": VARIANTS[variant]["name"],
            "units": "m",
            "coords": "原点は建物の外壁の内側の左下の角。x は右、y は奥（上）。配膳の店の形と同じ向き",
            "rules": {
                "staff_corridor_w": STAFF_Y1 - STAFF_Y0,
                "staff_corridor_w_source": "医療法施行規則 第16条1項11号（一般の中廊下 内法2.1m以上）",
                "hall_w": HALL_Y1 - HALL_Y0,
                "hall_w_basis": "仮置き",
                "robot_min_passage_m": 0.7,
                "distance": "廊下の中心線の上で、扉の点から扉の点まで（縦横の移動だけ）",
                "crowded": "外来の大廊下の x70 より西（受付・会計・待合の前）を、人の多い区間として数える",
            },
        },
        "layout": {
            "outer": {"w": W, "d": D},
            "rooms": [{"name": n, "rect": [x0, y0, x1, y1], "kind": k} for n, x0, y0, x1, y1, k in ROOMS],
            "corridors": [
                {"name": "外来の大廊下", "rect": [0, HALL_Y0, W, HALL_Y1], "w": HALL_Y1 - HALL_Y0},
                {"name": "職員の中廊下", "rect": [0, STAFF_Y0, 100.1, STAFF_Y1], "w": STAFF_Y1 - STAFF_Y0},
            ] + [{"name": n, "rect": [x - CONNECT_W[n] / 2, STAFF_Y1, x + CONNECT_W[n] / 2, HALL_Y0], "w": CONNECT_W[n]}
                 for n, x in CONNECT_X.items()]
              + ([{"name": "外来の奥の職員通路", "rect": [0, NORTH_Y0, NORTH_X1, NORTH_Y1], "w": 2.1},
                  {"name": "東の縦廊下（北）", "rect": [NORTH_CONNECT_X - 1.05, HALL_Y1, NORTH_CONNECT_X + 1.05, NORTH_Y0], "w": 2.1}]
                 if VARIANTS[variant]["north"] else []),
            "points": {n: [x, y] for n, (x, y, _) in POINTS.items()},
        },
        "routes": routes,
        "distances": dist,
    }
    if variant != "h0":  # h0 の JSON は中身を変えない（Unity の依頼が md5 で確かめているため）
        data["meta"]["variant"] = variant
    (out / f"hospital_{variant}.json").write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8")
    draw(data, out / f"hospital_{variant}.png")
    for k, r in routes.items():
        print(k, r["from"], "→", r["to"], r["distance_m"], "m（人の多い区間", r["crowded_hall_m"], "m）")


def draw(data, path):
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    from matplotlib import font_manager
    from matplotlib.patches import Rectangle
    for f in font_manager.findSystemFonts():
        if "NotoSansCJK" in f or "NotoSansJP" in f:
            font_manager.fontManager.addfont(f)
            plt.rcParams["font.family"] = font_manager.FontProperties(fname=f).get_name()
            break
    col = {"外来": "#eef1f5", "届け先": "#dce9fb", "出発": "#e3f3e6", "混む所": "#fff1dc", "その他": "#f4f4f4"}
    rc = {"green": "#2e9d4f", "blue": "#2f6fd6", "red": "#d24b3e"}
    fig, ax = plt.subplots(figsize=(16, 8.6), dpi=150)
    for r in data["layout"]["rooms"]:
        x0, y0, x1, y1 = r["rect"]
        ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc=col[r["kind"]], ec="#9aa3ad", lw=1))
        ax.text((x0 + x1) / 2, (y0 + y1) / 2, r["name"], ha="center", va="center", fontsize=11, color="#333")
    for c in data["layout"]["corridors"]:
        x0, y0, x1, y1 = c["rect"]
        ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="#ffffff", ec="none"))
    ax.add_patch(Rectangle((CROWD_X0, HALL_Y0), CROWD_X1 - CROWD_X0, HALL_Y1 - HALL_Y0, fc="#fff1dc", ec="none"))
    ax.text(45, HALL_C, "外来の大廊下 4.0m（受付・待合の前は人が多い）", va="center", ha="center", fontsize=9, color="#8a5a00")
    ax.text(0.6, STAFF_C, "職員の中廊下 2.1m", va="center", fontsize=8, color="#777")
    offs = {"green": 0.45, "blue": 0.0, "red": -0.45}
    for k, r in data["routes"].items():
        xs = [p[0] for p in r["path"]]
        ys = [p[1] + offs[k] for p in r["path"]]
        ax.plot(xs, ys, color=rc[k], lw=2.6, solid_capstyle="round")
        ax.plot(xs[0], ys[0], "o", color=rc[k], ms=9)
        ax.plot(xs[-1], ys[-1], "s", color=rc[k], ms=9)
    for n, (x, y) in data["layout"]["points"].items():
        dy = -1.6 if y > D - 3 else (1.6 if y > 20 else -1.6)
        ax.text(x, y + dy, n, ha="center", va="center", fontsize=9, color="#222",
                bbox=dict(fc="white", ec="#bbb", lw=0.6, pad=1.5))
    legend = [f"{ {'green':'緑','blue':'青','red':'赤'}[k]}  {r['from']} → {r['to']}  {r['distance_m']:.0f}m"
              for k, r in data["routes"].items()]
    for i, (k, t) in enumerate(zip(data["routes"], legend)):
        ax.text(1, -3 - i * 2.2, "●", color=rc[k], fontsize=12, va="center")
        ax.text(3, -3 - i * 2.2, t, fontsize=11, va="center")
    ax.text(60, -3, "● 出発（積む所）　■ 届け先　オレンジ＝人の多い所", fontsize=10, color="#555", va="center")
    ax.set_xlim(-1, W + 1)
    ax.set_ylim(-9.5, D + 1)
    ax.set_aspect("equal")
    ax.axis("off")
    ax.add_patch(Rectangle((0, 0), W, D, fc="none", ec="#555", lw=1.6))
    ax.set_title(f"{data['meta']['name']}（120m×60m）と搬送の3ルート", fontsize=15, loc="left")
    fig.savefig(path, bbox_inches="tight", facecolor="white")


if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else ".", sys.argv[2] if len(sys.argv) > 2 else "h0")
