#!/usr/bin/env python3
"""
护照模板自动填充脚本
- EasyOCR 只扫描坐标框（忽略文字内容）
- 按 Y/X 空间位置排序（行容差分组）
- 将新文本按索引依次写到锚点下方
"""
import json
import sys
import os
from pathlib import Path

try:
    import easyocr
    import numpy as np
    from PIL import Image, ImageDraw, ImageFont
except ImportError as e:
    print(f"缺少依赖: {e}")
    sys.exit(1)

FONT_DIR = Path("/usr/share/fonts/truetype/dejavu")
FONT_TEXT = FONT_DIR / "DejaVuSans.ttf"
FONT_BOLD = FONT_DIR / "DejaVuSans-Bold.ttf"
FONT_MONO = Path("/data/passport/fonts/OCR-B.otf")


def sort_text_regions(results, line_tolerance=20):
    """
    按 Y 轴（从上到下）和 X 轴（从左到右）排序坐标框。
    line_tolerance: 同一行判定容差（像素）
    """
    boxes = []
    for item in results:
        bbox = item[0]
        left_x = bbox[0][0]
        top_y = bbox[0][1]
        bottom_y = bbox[2][1]
        right_x = bbox[2][0]
        boxes.append({
            'bbox': bbox,
            'x': left_x,
            'top_y': top_y,
            'bottom_y': bottom_y,
            'right_x': right_x,
            'w': right_x - left_x,
            'h': bottom_y - top_y,
        })

    boxes.sort(key=lambda b: b['top_y'])

    rows = []
    for box in boxes:
        placed = False
        for row in rows:
            if abs(box['top_y'] - row[0]['top_y']) < line_tolerance:
                row.append(box)
                placed = True
                break
        if not placed:
            rows.append([box])

    sorted_boxes = []
    for row in rows:
        row.sort(key=lambda b: b['x'])
        sorted_boxes.extend(row)

    return sorted_boxes


def generate_passport(
    template_path,
    output_path,
    new_contents,
    font_size=20,
    text_color="#1F1F1F",
    y_offset=15,
    line_tolerance=20,
    debug=False,
):
    print("1. OCR 扫描坐标点...")
    reader = easyocr.Reader(['en'], gpu=False, verbose=False)
    results = reader.readtext(template_path)

    print(f"2. 检测到 {len(results)} 个标签框，按空间位置排序...")
    sorted_boxes = sort_text_regions(results, line_tolerance)

    n_detected = len(sorted_boxes)
    n_content = len(new_contents)
    if n_content != n_detected:
        print(f"   警告: 内容数({n_content}) ≠ 检测数({n_detected})，取较小值")

    img = Image.open(template_path).convert("RGB")
    draw = ImageDraw.Draw(img)

    if font_size >= 14:
        font = ImageFont.truetype(str(FONT_BOLD), font_size)
    else:
        font = ImageFont.truetype(str(FONT_TEXT), font_size)

    print(f"3. 写入新文本（共 {min(n_content, n_detected)} 条）...")
    coords_log = []
    for idx, box_info in enumerate(sorted_boxes):
        if idx >= n_content:
            break

        left_x = box_info['x']
        bottom_y = box_info['bottom_y']
        target_x = left_x
        target_y = bottom_y + y_offset

        w, h = img.size
        target_x = max(0, min(int(target_x), w - 1))
        target_y = max(0, min(int(target_y), h - 1))

        new_text = new_contents[idx]
        draw.text((target_x, target_y), new_text, font=font, fill=text_color)
        print(f"   [# {idx+1:02d}] 锚点({left_x:.0f},{bottom_y:.0f}) -> 写入: '{new_text}'")

        coords_log.append({
            "idx": idx + 1,
            "anchor": [round(left_x), round(bottom_y)],
            "write_at": [target_x, target_y],
            "text": new_text,
        })

        if debug:
            draw.rectangle(
                [box_info['bbox'][0][0], box_info['bbox'][0][1],
                 box_info['bbox'][2][0], box_info['bbox'][2][1]],
                outline="#0000FF", width=1
            )
            draw.line([target_x - 5, target_y, target_x + 5, target_y], fill="#FF0000", width=1)
            draw.line([target_x, target_y - 5, target_x, target_y + 5], fill="#FF0000", width=1)

    img.save(output_path, quality=95)
    print(f"\n完成！已保存至: {output_path}")

    json_path = output_path.rsplit('.', 1)[0] + '_coords.json'
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump({
            "template": template_path,
            "output": output_path,
            "detected_count": n_detected,
            "written_count": min(n_content, n_detected),
            "line_tolerance": line_tolerance,
            "coords": coords_log,
        }, f, ensure_ascii=False, indent=2)
    print(f"坐标日志: {json_path}")

    return True


def main():
    import argparse
    parser = argparse.ArgumentParser(description="护照模板自动填充")
    parser.add_argument("-t", "--template", required=True, help="模板图片路径")
    parser.add_argument("-o", "--output", required=True, help="输出图片路径")
    parser.add_argument("-c", "--contents", required=True, help="新内容 JSON 文件或 JSON 字符串")
    parser.add_argument("-f", "--font-size", type=int, default=20, help="字号 (默认: 20)")
    parser.add_argument("--color", default="#1F1F1F", help="颜色 (默认: #1F1F1F)")
    parser.add_argument("--y-offset", type=int, default=15, help="垂直偏移 (默认: 15px)")
    parser.add_argument("--tolerance", type=int, default=20, help="行容差 (默认: 20px)")
    parser.add_argument("--debug", action="store_true", help="调试模式（画框）")
    args = parser.parse_args()

    if os.path.isfile(args.contents):
        with open(args.contents, 'r', encoding='utf-8') as f:
            new_contents = json.load(f)
    else:
        new_contents = json.loads(args.contents)

    if not isinstance(new_contents, list):
        print("错误: 内容必须是列表")
        sys.exit(1)

    print(f"模板: {args.template}")
    print(f"内容: {len(new_contents)} 条")
    print(f"字号: {args.font_size} | 颜色: {args.color} | 偏移: {args.y_offset}px | 行容差: {args.tolerance}px")
    print("-" * 50)

    ok = generate_passport(
        args.template, args.output, new_contents,
        args.font_size, args.color, args.y_offset,
        args.tolerance, args.debug,
    )
    sys.exit(0 if ok else 1)


if __name__ == "__main__":
    main()
