# -*- coding: utf-8 -*-
"""把《软件测试课程设计》考核方案填进学院模板。

模板：_tpl4/考核方案模板.docx（由 课程考核方案.doc 转换，含"课程考核方案""实验课程考核方案""示例"三部分）
输出：广东金融学院课程考核方案（软件测试课程设计-周宇文）.docx

处理：
  - 表1 课程考核方案：平时（考勤与课堂表现 10%＋实验与阶段任务 40%）＋课程设计考核 50%
    （测试计划与用例 15%＋测试执行与缺陷 15%＋测试报告与答辩 20%），期末为考查、不设闭卷笔试；
  - 表2 实验课程考核方案：按课程实验（√）、考查（√）填写，含评分细则与实验内容；
  - 删除模板自带的"示例"部分与空表。
"""
import copy
import importlib.util
import os
import sys

from docx import Document
from docx.shared import Pt
from docx.text.paragraph import Paragraph


BASE = os.path.dirname(os.path.abspath(__file__))


def _load(name, path):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


G = _load('gen', os.path.join(BASE, '_gen_教学文件.py'))
C, SESSIONS = G.C, G.SESSIONS


def set_text(p, text):
    if not p.runs:
        p.add_run(text)
        return
    p.runs[0].text = text
    for r in p.runs[1:]:
        r.text = ''


def set_cell(cell, text):
    while len(cell.paragraphs) > 1:
        cell.paragraphs[-1]._p.getparent().remove(cell.paragraphs[-1]._p)
    lines = str(text).split('\n')
    set_text(cell.paragraphs[0], lines[0])
    p = cell.paragraphs[0]
    for line in lines[1:]:
        el = copy.deepcopy(p._p)
        p._p.addnext(el)
        p = Paragraph(el, cell)
        set_text(p, line)
    for para in cell.paragraphs:
        for r in para.runs:
            r.font.name = '宋体'
            if r.font.size is None:
                r.font.size = Pt(10.5)


def row_cells(row):
    seen, out = set(), []
    for c in row.cells:
        if id(c._tc) in seen:
            continue
        seen.add(id(c._tc))
        out.append(c)
    return out


def pick_tables(doc):
    """按内容识别三张表，避免依赖顺序。"""
    main = lab = sample = None
    for t in doc.tables:
        if not t.rows:
            continue
        head = [c.text.strip() for c in row_cells(t.rows[0])]
        if head and head[0] == '实验课程名称':
            lab = t
        elif head and head[0] == '课程名称':
            if len(head) > 1 and '大学计算机' in head[1]:
                sample = t
            else:
                main = t
    return main, lab, sample


def main():
    doc = Document(os.path.join(BASE, '_tpl4', '考核方案模板.docx'))
    main_t, lab_t, sample_t = pick_tables(doc)

    for p in doc.paragraphs:
        t = p.text.strip()
        if t.startswith('教学单位：'):
            if '专业：' in t:
                set_text(p, '教学单位：%s　　专业：计算机类相关专业　　2026 年 9 月 13 日' % C['college'])
            else:
                set_text(p, '教学单位：%s　　2026 年 9 月 13 日' % C['college'])

    # ---------- 表1 课程考核方案 ----------
    assess = [
        ('平时成绩（50%）', '考勤与课堂表现（10%）',
         '100 分满分：出勤 50 分、课堂讨论与练习 50 分，按次记录、期末折算。'),
        ('平时成绩（50%）', '实验与阶段任务（40%）',
         '100 分满分：按各章实验任务的完成质量、规范性与提交及时性计分，多次取加权平均。'),
        ('课程设计考核（50%）', '测试计划与用例设计（15%）',
         '100 分满分：测试范围清晰、策略合理、用例覆盖充分、步骤与预期结果规范。'),
        ('课程设计考核（50%）', '测试执行与缺陷报告（15%）',
         '100 分满分：执行记录完整、缺陷可复现、缺陷描述规范、跟踪闭环。'),
        ('课程设计考核（50%）', '测试报告与答辩（20%）',
         '100 分满分：报告结构完整、数据准确、结论有据；答辩表达清楚、回答准确。'),
    ]
    # 表头两行
    r0, r1, r2, r3 = (row_cells(main_t.rows[i]) for i in range(4))
    set_cell(r0[1], C['name'])
    set_cell(r0[3], C['teacher'])
    set_cell(r1[1], C['classes'])
    set_cell(r1[3], '专业选修（选修 √）')
    set_cell(r2[1], '统考（　）　随堂考试（　）　考查（ √ ）')
    set_cell(r3[0], '总评成绩构成')
    set_cell(r3[1], '项目')
    set_cell(r3[2], '评分标准')

    # 数据行：模板第一列纵向合并（平时成绩/期末成绩两块），先解除合并才能逐行写标签
    from docx.oxml.ns import qn as _qn
    for row in main_t.rows[4:]:
        tcs = row._tr.findall(_qn('w:tc'))
        if tcs:
            tcPr = tcs[0].find(_qn('w:tcPr'))
            vm = tcPr.find(_qn('w:vMerge')) if tcPr is not None else None
            if vm is not None:
                tcPr.remove(vm)
    # 数据行 = 表头后到"备注"行之前；压缩到与 assess 条数一致
    note_row = main_t.rows[-1]
    while len(main_t.rows) - 1 - 4 > len(assess):
        main_t._tbl.remove(main_t.rows[-2]._tr)
    while len(main_t.rows) - 1 - 4 < len(assess):
        main_t._tbl.append(copy.deepcopy(main_t.rows[-2]._tr))
    for i in range(len(assess)):
        cells = row_cells(main_t.rows[4 + i])
        for j, val in enumerate(assess[i]):
            if j < len(cells):
                set_cell(cells[j], val)

    # 第一列按"成绩类别"纵向合并：平时成绩一块（2 行）、期末成绩一块（3 行），
    # 这样类别与占比只出现一次，不会每行都重复写 50%
    from docx.oxml.ns import qn as _q
    groups = [(4, 5, '平时成绩（50%）'), (6, 8, '期末成绩（50%）\n（课程设计考核）')]
    for start, end, label in groups:
        set_cell(row_cells(main_t.rows[start])[0], label)
        for k in range(start + 1, end + 1):
            cells = row_cells(main_t.rows[k])
            set_cell(cells[0], '')
            tc = cells[0]._tc
            tcPr = tc.get_or_add_tcPr()
            for old in tcPr.findall(_q('w:vMerge')):
                tcPr.remove(old)
            tcPr.append(tcPr.makeelement(_q('w:vMerge'), {}))      # 续行：垂直合并
        tcPr = row_cells(main_t.rows[start])[0]._tc.get_or_add_tcPr()
        for old in tcPr.findall(_q('w:vMerge')):
            tcPr.remove(old)
        el = tcPr.makeelement(_q('w:vMerge'), {})
        el.set(_q('w:val'), 'restart')
        tcPr.append(el)

    # 备注
    last = row_cells(main_t.rows[-1])
    set_cell(last[0], '备注')
    if len(last) > 1:
        set_cell(last[1], '平时成绩 50%＝考勤与课堂表现 10%＋实验与阶段任务 40%；'
                          '期末成绩 50%＝测试计划与用例设计 15%＋测试执行与缺陷报告 15%＋测试报告与答辩 20%，合计 100%。'
                          '本课程为考查类课程，以过程性考核与课程设计成果为主，不组织闭卷笔试；'
                          '比例可由任课教师根据教学实际微调，但需在开课时向学生公布。')

    # ---------- 表2 实验课程考核方案 ----------
    lab_rows = [row_cells(r) for r in lab_t.rows]
    set_cell(lab_rows[0][1], C['name'] + '（课程实验）')
    set_cell(lab_rows[0][3], C['teacher'])
    set_cell(lab_rows[1][1], str(C['credits']))
    set_cell(lab_rows[1][3], C['teacher'])
    set_cell(lab_rows[2][1], '独立实验课程（　）　　课程实验（ √ ）')
    set_cell(lab_rows[3][1], '验证型（ ）流程型（ ）分析型（ ）模拟型（ ）设计型（√）综合型（√）')
    set_cell(lab_rows[4][1], '系（部）统考（　）　随堂考试（　）　考查（ √ ）')
    fill = [C['classes'], '以选课名单为准', '第 17 周（随课程设计答辩）', '实验室 / 多媒体教室', C['teacher'], '']
    for j, val in enumerate(fill):
        if j + 1 < len(lab_rows[6]):
            set_cell(lab_rows[6][j + 1], val)
    # 评分细则分数分布：操作/行为/态度/结果/其它/总分
    dist = ['40', '10', '10', '40', '0', '100']
    for j, val in enumerate(dist):
        if j + 2 < len(lab_rows[12]):
            set_cell(lab_rows[12][j + 2], val)
    labs = ['实验一　测试环境搭建与缺陷发现（第 1 周）',
            '实验二　等价类与边界值用例设计（第 2 周）',
            '实验三　探索式测试会话（第 3 周）',
            '实验四　状态转换图用例设计（第 4 周）',
            '实验五　测试计划与流程设计（第 5 周）',
            '实验六　JUnit 单元测试与持续集成（第 6 周）',
            '实验七　接口功能测试（第 7 周）',
            '实验八　JMeter 性能压测（第 8 周）',
            '实验九　本地化检查（第 9 周）',
            '实验十　自动化用例与 Docker 测试环境（第 10、13 周）',
            '实验十一　测试管理工具与缺陷全流程演练（第 12、13 周）',
            '实验十二　系统测试执行与测试报告（第 14 周）',
            '综合实践　课程设计项目汇报答辩（第 16 周）']
    set_cell(lab_rows[13][1], '\n'.join(labs))

    # ---------- 删除"示例"部分与空表 ----------
    body = doc.element.body
    drop = False
    for child in list(body.iterchildren()):
        tag = child.tag.split('}')[1]
        if tag == 'p':
            txt = ''.join(t.text or '' for t in child.iter(
                __import__('docx.oxml.ns', fromlist=['qn']).qn('w:t')))
            if '课程考核方案（示例）' in txt:
                drop = True
            if drop:
                body.remove(child)
        elif tag == 'tbl':
            if drop or (sample_t is not None and child is sample_t._tbl) or len(child.findall(
                    __import__('docx.oxml.ns', fromlist=['qn']).qn('w:tr'))) == 0:
                body.remove(child)

    out = os.path.join(BASE, '广东金融学院课程考核方案（软件测试课程设计-周宇文）.docx')
    doc.save(out)
    d = Document(out)
    print('已生成：%s' % out)
    print('表格数 %d' % len(d.tables))
    return 0


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