# -*- coding: utf-8 -*-
"""讲稿收尾：拼装校验 → 统计 → 生成使用说明 → 出打印版 PDF。

用法：python _finalize.py
"""
import os
import re
import subprocess
import sys

BASE = os.path.dirname(os.path.abspath(__file__))
CH = {1: ('第1章-引论-讲稿.md', '第 1 章 引论', '第 1 周'), 2: ('第2章-软件测试基本概念-讲稿.md', '第 2 章 软件测试基本概念', '第 2 周')}
PDF_JOBS = [('第1-2章讲稿-打印版.html', '第1-2章讲稿-打印版.pdf'),
            ('第1-2章讲稿-备课批注版.html', '第1-2章讲稿-备课批注版.pdf'),
            ('第1章-讲稿-打印版.html', '第1章-讲稿-打印版.pdf'),
            ('第2章-讲稿-打印版.html', '第2章-讲稿-打印版.pdf')]


def stats(path):
    txt = open(path, encoding='utf-8').read()
    anchors = re.findall(r'^###\s*【(\d+\.\d+)】', txt, re.M)
    spoken = 0
    for m in re.finditer(r'>\s*\*\*口播\*\*[：:]([\s\S]*?)(?=\n\*\*|\Z)', txt):
        spoken += len(re.sub(r'[\s\*#>]', '', m.group(1)))
    sz = len(re.findall(r'【思政落点\s*\d+】', txt))
    q = len(re.findall(r'\*\*提问\*\*', txt))
    return len(anchors), spoken, sz, q, len(txt)


def main():
    print('== 1) 拼装与校验 ==')
    rc = subprocess.call([sys.executable, os.path.join(BASE, '_assemble.py')], cwd=BASE)
    rows = []
    for c, (fn, name, week) in CH.items():
        p = os.path.join(BASE, fn)
        if not os.path.exists(p):
            print('✗ 缺少 %s，无法继续' % fn)
            return 1
        n, sp, sz, q, chars = stats(p)
        rows.append((c, name, week, n, sp, sz, q, chars))
        print('   %s：%d 页 ｜ 口播 %d 字 ｜ 均 %d 字/页 ｜ 思政落点 %d ｜ 提问 %d'
              % (name, n, sp, sp // max(1, n), sz, q))
    tot_sp = sum(r[4] for r in rows)

    print('== 2) 生成使用说明 ==')
    L = []
    A = L.append
    A('# 《软件测试课程设计》讲稿（第 1–2 章）· 使用说明')
    A('')
    A('> 目录：`dl-hub/25-软件测试方法和技术第4版OCR整理/讲稿/`')
    A('> 配套课件（云端）：http://111.231.0.226:2345/lecture1/index.html ｜ '
      'http://111.231.0.226:2345/lecture2/index.html')
    A('> 编写依据：逐页还原版课件（第 1 章 61 页、第 2 章 87 页）＋本课程教案（第 1、2 次课）'
      '＋《课程教学大纲》重点难点；格式参照《软件项目管理》讲稿 v2 完整版。')
    A('')
    A('## 一、文件清单')
    A('')
    A('| 文件 | 对应周次 | 课件页数 | 口播字数 | 均字/页 | 思政落点 | 提问设计 |')
    A('|---|---|---|---|---|---|---|')
    for c, name, week, n, sp, sz, q, chars in rows:
        A('| `%s` | %s（2 学时） | %d 页 | **%s 字** | %d | %d 处 | %d 个 |'
          % (CH[c][0], week, n, format(sp, ','), sp // max(1, n), sz, q))
    A('')
    A('- 打印版 PDF（A4）在 `打印版/`：分章打印版、第 1–2 章合订版、备课批注版（每页带"已讲/需回看/课堂记录"栏）。')
    A('- 生成脚本：`_assemble.py`（拼装＋页码校验）、`_make_print.py`（打印版 HTML）、'
      '`_finalize.py`（一键收尾）、`_src/`（逐页源数据）、`_parts/`（分片讲稿）。')
    A('')
    A('## 二、每页固定结构')
    A('')
    A('| 部分 | 用途 |')
    A('|---|---|')
    A('| `> **口播**：…` | **可直接朗读**的完整讲授语（定义 → 为什么重要 → 生活类比＋软件行业实例） |')
    A('| `**板书**：…` | 边讲边写的板书内容与顺序 |')
    A('| `**提问**：…` | 具体可回答的问题（不是"大家懂了吗"） |')
    A('| `**预设回答**：…` | 学生可能的 2–3 种回答（含典型错误）＋点评、追问与纠偏话术 |')
    A('| `**易错点 / 考点**：…` | 混淆点、考试形式、判断三步法、记忆口诀 |')
    A('| `**【思政落点 N】**` | 讲什么故事 → 联系哪个知识点 → 落到什么价值观 |')
    A('| `**过渡**：…` | 承上启下的一句话 |')
    A('')
    A('## 三、容量与配速（重要）')
    A('')
    A('| 讲次 | 课件页数 | 口播总量 | ≈纯朗读时长 | 建议 |')
    A('|---|---|---|---|---|')
    for c, name, week, n, sp, sz, q, chars in rows:
        A('| %s | %d 页 | %s 字 | ≈%d 分钟 | %s |'
          % (name, n, format(sp, ','), round(sp / 240), '素材全量版：按章首"时间分配总表"取用'));
    A('')
    A('口播配速按 **240 字/分钟** 估算；90 分钟课堂（扣除互动、练习、课间约 20 分钟）纯讲授容量约 **70 分钟 ≈ 17,000 字**，'
      '因此**两章讲稿都是素材全量版**，请按每章开头的"时间分配总表"与"压缩优先级"取用：')
    A('')
    A('1. **重点页全文慢讲**（2–3 分钟/页）：章首都列出了页码清单；')
    A('2. **常规页快速带过**（0.5–1 分钟/页）：只念口播第一段（结论）＋板书＋1 个提问；')
    A('3. **★ 页课后阅读**：章首已标注；')
    A('4. **练习/思考题页**：课上交代，答案按讲稿该页口径展开。')
    A('')
    A('## 四、怎么用')
    A('')
    A('1. **备课**：先看章首的"时间分配总表"，在打印版上勾掉不讲的页；重点页通读口播，必要时在"课堂记录"栏写自己的例子。')
    A('2. **上课**：口播段落可照读；学生答错时翻到该页"预设回答"应急；每页"过渡"帮你衔接下一页。')
    A('3. **复盘**：把各页"思政落点"抄入思政教学记录（每章末尾有汇总清单）。')
    A('')
    A('## 五、更新流程')
    A('')
    A('```bash')
    A('cd ~/Downloads/dl-hub/25-软件测试方法和技术第4版OCR整理/讲稿')
    A('# ① 改分片（_parts/ch{1,2}-p*.md）后重新拼装校验（页面锚点必须连续、不漏页）')
    A('python3 _assemble.py')
    A('# ② 重新出打印版 HTML/PDF')
    A('python3 _make_print.py && for f in _print/*.html; do soffice --headless --convert-to pdf --outdir 打印版 "$f"; done')
    A('```')
    A('')
    A('## 六、内容口径')
    A('')
    A('- 所有事实、数字均来自课件逐页文字与教材（朱少民《软件测试方法和技术（第 4 版）》，清华大学出版社 2022）；'
      '课件未给出的数字在文中标注"据课件数据"，**未编造**。')
    A('- 案例线：第 1 章以真实缺陷事故贯穿（Therac-25、Intel 奔腾、火星探测器、波音星际客机、骑士资本、AWS S3、《血狮》）；'
      '第 2 章以"缺陷—质量—测试分类—测试层次"主线贯穿，产品评审部分用需求/设计评审实例。')
    A('- 思政落点：第 1 章与第 2 章各按其教案思政维度展开，逐处清单见每章末尾。')
    A('')
    A('## 七、质量自检')
    A('')
    A('- 页码覆盖：与课件页一一对应，**不漏页、不重复**（`_assemble.py` 自动校验）；')
    A('- 每页必备：口播／板书／提问／预设回答／易错点或考点／过渡（自动检查缺标记页）；')
    A(f'- 口播总量：第 1 章 {format(rows[0][4], ",")} 字、第 2 章 {format(rows[1][4], ",")} 字，'
      f'合计 {format(tot_sp, ",")} 字（≈{round(tot_sp/240)} 分钟纯朗读）。')
    open(os.path.join(BASE, '00-讲稿使用说明.md'), 'w', encoding='utf-8').write('\n'.join(L) + '\n')
    print('   ✓ 00-讲稿使用说明.md')

    print('== 3) 生成打印版 ==')
    subprocess.call([sys.executable, os.path.join(BASE, '_make_print.py')], cwd=BASE)
    outdir = os.path.join(BASE, '打印版')
    os.makedirs(outdir, exist_ok=True)
    for src, dst in PDF_JOBS:
        p = os.path.join(BASE, '_print', src)
        if not os.path.exists(p):
            continue
        subprocess.call(['soffice', '--headless', '--convert-to', 'pdf', '--outdir', outdir, p],
                        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        q = os.path.join(outdir, dst)
        print('   %s %s' % ('✓' if os.path.exists(q) else '✗', dst))
    return 0


if __name__ == '__main__':
    sys.exit(main())
