# -*- coding: utf-8 -*-
"""
软著代码生成器（规范版）
============================
功能：
1. 选择本地文件夹，递归筛选所有 .cs 后缀的源代码文件
2. 自动去除空行，拼接成连续代码，保证有效代码行数
3. 按软件著作权登记要求生成 Word 文档：
   - 默认提取前 30 页 + 后 30 页（共 60 页），每页强制 50 行
   - 等宽字体 Consolas / 宋体，小四号字，固定行距
   - 【左侧行号】每行代码前内置行号，每页从 1 重新计数（1~50）
   - 【页眉】软件全称 + Vx.x 源代码
   - 【页脚 / 页码】全局连续页码（第 X 页），不重置
4. 导出符合软著申请格式的 .docx 文档

依赖安装：
    pip install python-docx

运行方式：
    python copyright_generator.py
"""

import os
import sys

try:
    import tkinter as tk
    from tkinter import filedialog, messagebox, simpledialog
except ImportError:
    print("错误：未找到 tkinter 模块，请确认 Python 安装时包含 tkinter。")
    sys.exit(1)

try:
    from docx import Document
    from docx.shared import Pt, Cm, RGBColor
    from docx.oxml.ns import qn
    from docx.oxml import OxmlElement
    from docx.enum.text import WD_LINE_SPACING, WD_ALIGN_PARAGRAPH
except ImportError:
    print("错误：未安装 python-docx，请运行：pip install python-docx")
    input("按回车键退出...")
    sys.exit(1)


# ----------------------------- 配置参数 -----------------------------
LINES_PER_PAGE = 50          # 每页行数（软著要求）
PAGES_FRONT = 30             # 前部页数
PAGES_BACK = 30              # 后部页数
FONT_NAME_EN = "Consolas"    # 等宽字体（代码）
FONT_NAME_CN = "宋体"
FONT_SIZE = 10.5             # 小四 = 10.5pt
LINE_SPACING_PT = 13         # 固定行距
LINE_NO_WIDTH = 3            # 行号显示宽度（右对齐）
SKIP_BLANK_LINES = True      # 跳过空行（避免有效代码不足）
SKIP_COMMENT_LINES = False   # 是否跳过单行注释（// 开头）
# 读取源码时尝试的编码顺序（解决中文乱码）
ENCODINGS = ["utf-8-sig", "utf-8", "gb18030", "gbk", "latin-1"]
# -------------------------------------------------------------------


def collect_cs_files(folder):
    """递归收集所有 .cs 文件，按路径排序。"""
    cs_files = []
    for root, _, files in os.walk(folder):
        for f in files:
            if f.lower().endswith(".cs"):
                cs_files.append(os.path.join(root, f))
    cs_files.sort()
    return cs_files


def read_file_text(path):
    """尝试多种编码读取文件，避免中文乱码。"""
    raw = None
    try:
        with open(path, "rb") as fp:
            raw = fp.read()
    except Exception as e:
        print(f"读取失败 {path}: {e}")
        return ""

    for enc in ENCODINGS:
        try:
            return raw.decode(enc)
        except (UnicodeDecodeError, LookupError):
            continue
    # 兜底：忽略无法解码的字节
    return raw.decode("utf-8", errors="ignore")


def read_code_lines(cs_files):
    """读取所有代码行，返回处理后的行列表。"""
    all_lines = []
    for path in cs_files:
        text = read_file_text(path)
        for raw in text.splitlines():
            line = raw.rstrip()

            # 跳过空行（避免空行凑数被驳回）
            if SKIP_BLANK_LINES and line.strip() == "":
                continue
            # 跳过单行注释
            if SKIP_COMMENT_LINES and line.strip().startswith("//"):
                continue

            all_lines.append(line)
    return all_lines


def select_pages(all_lines):
    """根据软著要求选取前30页+后30页代码行。"""
    total_lines = len(all_lines)
    total_pages = (total_lines + LINES_PER_PAGE - 1) // LINES_PER_PAGE

    needed_pages = PAGES_FRONT + PAGES_BACK

    # 代码不足 60 页，全部使用
    if total_pages <= needed_pages:
        return all_lines, total_pages, False

    front_lines = all_lines[: PAGES_FRONT * LINES_PER_PAGE]
    back_lines = all_lines[-(PAGES_BACK * LINES_PER_PAGE):]
    return front_lines + back_lines, needed_pages, True


def set_run_font(run, size=FONT_SIZE):
    """统一设置等宽字体。"""
    run.font.name = FONT_NAME_EN
    run.font.size = Pt(size)
    run._element.rPr.rFonts.set(qn("w:eastAsia"), FONT_NAME_CN)


def add_page_number_field(paragraph):
    """在段落中插入自动页码域（全局连续）。"""
    r1 = paragraph.add_run("第 ")
    set_run_font(r1, 9)

    # 域：PAGE
    fld_begin = OxmlElement("w:fldChar")
    fld_begin.set(qn("w:fldCharType"), "begin")
    instr = OxmlElement("w:instrText")
    instr.set(qn("xml:space"), "preserve")
    instr.text = "PAGE"
    fld_end = OxmlElement("w:fldChar")
    fld_end.set(qn("w:fldCharType"), "end")

    run_fld = paragraph.add_run()
    set_run_font(run_fld, 9)
    run_fld._element.append(fld_begin)
    run_fld._element.append(instr)
    run_fld._element.append(fld_end)

    r2 = paragraph.add_run(" 页")
    set_run_font(r2, 9)


def create_word_document(lines, output_path, software_name, version):
    """生成符合软著要求的 Word 文档。"""
    doc = Document()

    section = doc.sections[0]
    # 页边距
    section.top_margin = Cm(2.0)
    section.bottom_margin = Cm(2.0)
    section.left_margin = Cm(2.5)
    section.right_margin = Cm(2.0)

    # 默认样式：等宽字体、小四
    style = doc.styles["Normal"]
    style.font.name = FONT_NAME_EN
    style.font.size = Pt(FONT_SIZE)
    style.element.rPr.rFonts.set(qn("w:eastAsia"), FONT_NAME_CN)
    style.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
    style.paragraph_format.line_spacing = Pt(LINE_SPACING_PT)
    style.paragraph_format.space_before = Pt(0)
    style.paragraph_format.space_after = Pt(0)

    # ---------- 页眉：软件全称 + Vx.x 源代码 ----------
    header_text = f"{software_name} {version} 源代码".strip()
    header = section.header
    hp = header.paragraphs[0]
    hp.text = header_text
    hp.alignment = WD_ALIGN_PARAGRAPH.CENTER
    for run in hp.runs:
        set_run_font(run, 9)

    # ---------- 页脚：全局连续页码 ----------
    footer = section.footer
    fp = footer.paragraphs[0]
    fp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    add_page_number_field(fp)

    # ---------- 删除默认模板自带的空段落 ----------
    # python-docx 默认文档正文含一个空段落，必须彻底清除，
    # 否则它会占用第一行，导致代码行号从 2 开始。
    body = doc.element.body
    for p in body.findall(qn("w:p")):
        body.remove(p)

    # ---------- 逐行写入代码（内置行号，每页 1~50 重置）----------
    total = len(lines)
    for idx, line in enumerate(lines):
        page_line_no = (idx % LINES_PER_PAGE) + 1  # 每页 1~50 循环

        p = doc.add_paragraph()
        pf = p.paragraph_format
        pf.line_spacing_rule = WD_LINE_SPACING.EXACTLY
        pf.line_spacing = Pt(LINE_SPACING_PT)
        pf.space_before = Pt(0)
        pf.space_after = Pt(0)

        # 每满 50 行强制分页，保证每页正好 50 行，行号与页码对齐
        if idx > 0 and idx % LINES_PER_PAGE == 0:
            pf.page_break_before = True

        # 行号（灰色，等宽，右对齐补空格）
        no_text = str(page_line_no).rjust(LINE_NO_WIDTH)
        run_no = p.add_run(no_text + "  ")
        run_no.font.name = FONT_NAME_EN
        run_no.font.size = Pt(FONT_SIZE)
        run_no.font.color.rgb = RGBColor(0x88, 0x88, 0x88)
        run_no._element.rPr.rFonts.set(qn("w:eastAsia"), FONT_NAME_CN)

        # 代码内容（制表符转空格）
        text = line.replace("\t", "    ")
        run = p.add_run(text if text else " ")
        set_run_font(run)

    doc.save(output_path)


def main():
    root = tk.Tk()
    root.withdraw()

    # 1. 选择源码文件夹
    folder = filedialog.askdirectory(title="请选择包含 .cs 源代码的文件夹")
    if not folder:
        messagebox.showinfo("提示", "未选择文件夹，程序退出。")
        return

    # 2. 收集 .cs 文件
    cs_files = collect_cs_files(folder)
    if not cs_files:
        messagebox.showwarning("警告", "所选文件夹内未找到任何 .cs 文件。")
        return

    # 3. 输入软件名称与版本号（用于页眉，需与申请表一致）
    software_name = simpledialog.askstring(
        "软件全称", "请输入软件全称（需与申请表完全一致）：", parent=root
    ) or "软著源代码"
    version = simpledialog.askstring(
        "版本号", "请输入版本号（如 V1.0）：", parent=root
    ) or "V1.0"

    # 4. 读取并处理代码
    all_lines = read_code_lines(cs_files)
    if not all_lines:
        messagebox.showwarning("警告", "未读取到任何有效代码行。")
        return

    selected_lines, used_pages, truncated = select_pages(all_lines)

    # 5. 选择保存位置
    default_name = f"{software_name}_{version}_源代码.docx"
    output_path = filedialog.asksaveasfilename(
        title="保存 Word 文档",
        defaultextension=".docx",
        initialfile=default_name,
        filetypes=[("Word 文档", "*.docx")],
    )
    if not output_path:
        messagebox.showinfo("提示", "未选择保存位置，程序退出。")
        return

    # 6. 生成文档
    try:
        create_word_document(selected_lines, output_path, software_name, version)
    except Exception as e:
        messagebox.showerror("错误", f"生成文档失败：{e}")
        return

    # 7. 完成提示
    total_lines = len(all_lines)
    info = (
        f"生成成功！\n\n"
        f"扫描到 .cs 文件：{len(cs_files)} 个\n"
        f"有效代码行数：{total_lines} 行\n"
        f"实际写入行数：{len(selected_lines)} 行\n"
        f"约占页数：{used_pages} 页\n"
        f"\n✔ 左侧行号：每页 1~50 重新计数（首行为 1）\n"
        f"✔ 每页强制 50 行，行号与页码精确对齐\n"
        f"✔ 页眉：{software_name} {version} 源代码\n"
        f"✔ 页脚：全局连续页码\n"
        f"✔ 等宽字体：{FONT_NAME_EN}\n"
        f"✔ 多编码读取，避免中文乱码\n"
    )
    if truncated:
        info += f"\n（代码超过 {PAGES_FRONT + PAGES_BACK} 页，已自动提取前 {PAGES_FRONT} 页 + 后 {PAGES_BACK} 页）\n"
    info += f"\n保存路径：\n{output_path}"

    messagebox.showinfo("完成", info)


if __name__ == "__main__":
    main()