# -*- coding: utf-8 -*-
"""1850–2025 の全長版。1 年 12 コマ（0.4 秒）、うち先頭 4 コマは静止（dwell 0.35）。

止まっているコマは必ず実際の観測年。動いている間だけが表示のための補間。
下の帯は全球平均のストライプで、地図が静止する拍に合わせて 1 本ずつ伸びる。
"""
import os, sys, csv
import numpy as np
sys.path.insert(0, '/home/claude/forest'); sys.path.insert(0, '/home/claude/stripes')
import forest1 as F, temp2 as T
from PIL import Image
from scipy import ndimage
OUT = '/home/claude/stripes/final'; os.makedirs(f'{OUT}/frames', exist_ok=True)
W, Hm, SS, BAR = 1350, 675, 2, 210
LIM, SIG = 2.0, 3.5
Y0, Y1, FPY, HOLD = 1850, 2025, 12, 0.35
HOLD0, HOLD1 = 60, 90          # 最初に 2 秒、最後に 3 秒

rows = list(csv.DictReader(open('/home/claude/stripes/global_annual.csv')))
GLOBAL = {int(r['Time']): float(r['Anomaly (deg C)']) for r in rows}
yrs, YG = T.annual()
sc = F.Scene((-180, -90, 180, 90), (W*SS, Hm*SS), proj='pc', pad=0.0)
sc_out = F.Scene((-180, -90, 180, 90), (W, Hm), proj='pc', pad=0.0)
lon, lat, ok = sc.lonlat_grid()
lm = F.land_mask(sc) > 0.5
coast = lm & ~ndimage.binary_erosion(lm)
gw = [T.grey_out(c) for c in T.WARM_H]; gc = [T.grey_out(c) for c in T.COOL_H]
YEARS = list(range(Y0, Y1 + 1))
fields = {y: T.fill_field(YG[int(np.where(yrs == y)[0][0])], sigma_deg=SIG) for y in YEARS}
print('fields ready', len(fields), flush=True)
h, w = fields[Y0][0].shape
fx = np.clip((lon + 180) / 360 * w - 0.5, 0, w - 1.001); fy = np.clip((lat + 90) / 180 * h - 0.5, 0, h - 1.001)
XI = fx.astype(int); YI = fy.astype(int); TX = fx - XI; TY = fy - YI
def bil(a):
    a = np.nan_to_num(a.astype(np.float32))
    return (a[YI, XI]*(1-TX)*(1-TY) + a[YI, XI+1]*TX*(1-TY) + a[YI+1, XI]*(1-TX)*TY + a[YI+1, XI+1]*TX*TY)
def bar(upto):
    img = np.full((BAR, W, 3), 255, np.uint8); n = len(YEARS)
    for i, y in enumerate(YEARS):
        if y > upto: break
        v = GLOBAL.get(y)
        if v is None: continue
        x0 = int(i * W / n); x1 = max(x0 + 1, int((i + 1) * W / n))
        b = min(7, int(abs(v) / 1.2 * 8))
        img[:, x0:x1] = (T.WARM_H[b] if v >= 0 else T.COOL_H[b]).astype(np.uint8)
    return img
def paint(fld, conf, year):
    v = np.where(ok, bil(fld), np.nan); cf = np.where(ok, bil(conf), 0.0)
    fin = np.isfinite(v)
    k = np.clip((np.abs(np.nan_to_num(v)) / LIM * 8).astype(int), 0, 7)
    full = np.zeros((Hm*SS, W*SS, 3), np.float32); grey = np.zeros_like(full)
    for b in range(8):
        mw = fin & (v >= 0) & (k == b); mc = fin & (v < 0) & (k == b)
        full[mw] = T.WARM_H[b]; grey[mw] = gw[b]
        full[mc] = T.COOL_H[b]; grey[mc] = gc[b]
    img = np.full((Hm*SS, W*SS, 3), 255, np.float32)
    c3 = cf[..., None]; img[fin] = np.clip(grey * (1 - c3) + full * c3, 0, 255)[fin]
    img[coast] = img[coast] * 0.62
    interp = fin & (cf < 0.5)
    if interp.any():
        ring = interp & ~ndimage.binary_erosion(interp, iterations=SS)
        img[ring] = img[ring] * 0.68 + np.array([120, 120, 124], np.float32) * 0.32
    m = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8)).resize((W, Hm), Image.LANCZOS)
    m = T.label_extrema(m, sc_out, fld, T.extrema(fld, nwarm=2, ncool=1))
    canvas = np.full((Hm + BAR, W, 3), 255, np.uint8)
    canvas[:Hm] = np.array(m); canvas[Hm:] = bar(year)
    return Image.fromarray(canvas)
def smooth(t): return t * t * (3 - 2 * t)
k = 0
for idx, y in enumerate(YEARS):
    nxt = min(y + 1, Y1)
    f0, c0 = fields[y]; f1, c1 = fields[nxt]
    still = paint(f0, c0, y)
    n_still = int(np.ceil(HOLD * FPY))
    if idx == 0: n_still += HOLD0
    if idx == len(YEARS) - 1: n_still = FPY + HOLD1
    p = f'{OUT}/frames/{k:04d}.png'; still.save(p); k += 1
    for _ in range(n_still - 1):
        os.link(p, f'{OUT}/frames/{k:04d}.png'); k += 1
    if idx == len(YEARS) - 1: break
    for s in range(n_still if False else int(np.ceil(HOLD*FPY)), FPY):
        u = smooth(min(1.0, (s / FPY - HOLD) / (1 - HOLD)))
        paint(f0 * (1 - u) + f1 * u, c0 * (1 - u) + c1 * u, y).save(f'{OUT}/frames/{k:04d}.png'); k += 1
    if y % 20 == 0: print(y, k, flush=True)
print('frames', k, 'sec', round(k / 30, 1), flush=True)
