import struct
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
import os
import re

# ============ 核心算法 ============
_MULTS = [17, 37, 7, 5, 37, 19, 47, 41, 53, 17]
_ADDS = [11, 3, 11, 31, 13, 23, 31, 43, 59, 19]

# 架构信息表（只包含文件名，Hash根据SN动态计算）
ARCH_INFO = {
    "ARM7": {"filename": "sccarm70.t32"},
    "ARM9": {"filename": "sccarm90.t32"},
    "ARM10": {"filename": "sccarm10.t32"},
    "ARM11": {"filename": "sccarm11.t32"},
    "ARM SA": {"filename": "sccarmsa.t32"},
    "Cortex-M": {"filename": "scccortexm.t32"},
    "Cortex-A/R": {"filename": "scccortex.t32"},
    "ARMv8/v9": {"filename": "sccARMv8.t32"},
    "MCI": {"filename": "sccmci.t32"},
    "JANUS": {"filename": "sccjanus.t32"},
    "ETB Trace": {"filename": "sccetb.t32"},
    "AVR": {"filename": "sccavr.t32"},
    "AVR8": {"filename": "sccavr8.t32"},
    "68K": {"filename": "scc68k.t32"},
    "C166": {"filename": "scc166nb.t32"},
    "C166S": {"filename": "scc166sv2.t32"},
    "C2000": {"filename": "scc322800.t32"},
    "C5400": {"filename": "scc32540.t32"},
    "C5500": {"filename": "scc32550.t32"},
    "MIPS": {"filename": "sccmips.t32"},
    "MSP430": {"filename": "sccmsp430.t32"},
    "PPC4200": {"filename": "sccppc4200.t32"},
    "PPC5500": {"filename": "sccppc5500.t32"},
    "PPC40x": {"filename": "sccppc40.t32"},
    "PPC44x": {"filename": "sccppc44.t32"},
    "PPC50x": {"filename": "sccppc50.t32"},
    "PPC60x": {"filename": "sccppc60.t32"},
    "PPC74x": {"filename": "sccppc74.t32"},
    "PPC82x": {"filename": "sccppc82.t32"},
    "PPC85x": {"filename": "sccppc85.t32"},
    "PPC86x": {"filename": "sccppc86.t32"},
    "HCS08": {"filename": "sccs08.t32"},
    "HCS12": {"filename": "sccs12.t32"},
    "HCS12X": {"filename": "sccs12x.t32"},
    "HCS12Z": {"filename": "sccms12z.t32"},
    "V850": {"filename": "sccRH850.t32"},
    "XTENSA": {"filename": "sccxtensa.t32"},
    "HSDP": {"filename": "scchsdp.t32"},
    "RISCV": {"filename": "sccRISCV.t32"},
    "RISCV32": {"filename": "sccRISCV_32.t32"},
    "RISCV64": {"filename": "sccRISCV_64.t32"},
}

# 提取架构名称和文件名映射
ARCH_NAMES = list(ARCH_INFO.keys())
ARCH_FILES = {name: info["filename"] for name, info in ARCH_INFO.items()}

# EEPROM布局常量
BLOCK_SIZE = 32
CATEGORY_OFFSET = 0x00
SN_OFFSET = 0x04
HASH_OFFSET = 0x10
VERSION_OFFSET = 0xF0
CHECKSUM_OFFSET = 0xFE
MAX_ARCH_PER_BLOCK = 4
MAX_BLOCKS = 3

# License存储位置（按块索引）
LICENSE_OFFSETS = [0xC0, 0x90, 0x60]
LICENSE_SIZE = 0x30

# 校验和计算常量
CHECKSUM_WORD_COUNT = 127  # 前254字节 = 127个WORD


def hash_140151110(value):
    v = value & 0xFFFFFFFF
    for i in range(10):
        v = (v * _MULTS[i] + _ADDS[i]) & 0xFFFFFFFF
    return v


# def parse_serial(serial_str):
#     s = serial_str.strip()
#     if s and not s[0].isdigit():
#         s = s[1:]
#     result = 0
#     for c in s[:11]:
#         if not c.isdigit():
#             break
#         result = result * 10 + int(c)
#     return result
def parse_serial(serial_str):
    """
    从序列号字符串中提取数字部分
    如果序列号以字母开头（如 C17080238053），自动去掉第一个字母
    提取最多11位数字
    """
    s = serial_str.strip()
    # 如果第一个字符不是数字，去掉它（处理如 C17080238053 这样的情况）
    if s and not s[0].isdigit():
        s = s[1:]
    result = 0
    for c in s[:11]:
        if not c.isdigit():
            break
        result = result * 10 + int(c)
    return result

def compute_arch_hash(serial_str, filename):
    serial = parse_serial(serial_str)
    sn_mod = serial % 100000
    csum = sum(ord(c) for c in filename)
    value = sn_mod * csum
    hash_result = hash_140151110(value)
    return {
        'hash': hash_result,
        'hash_hex': f"0x{hash_result:08X}",
        'hash_bytes_le': hash_result.to_bytes(4, 'little').hex().upper()
    }


def compute_activation_code(serial_str, month, year):
    serial = parse_serial(serial_str)
    v29 = 100003 * serial * (month + 12 * year)
    hi = (v29 >> 32) & 0xFFFFFFFF
    lo = v29 & 0xFFFFFFFF
    hash1 = hash_140151110(hi)
    hash2 = hash_140151110(lo)
    code = hash1.to_bytes(4, 'little') + hash2.to_bytes(4, 'little')
    return code.hex().upper()


def compute_checksum(data):
    """
    计算校验和：计算前254字节（0x00-0xFD），最后两字节（0xFE-0xFF）存放校验和不参与计算
    算法：将前254字节按字节累加，得到16位校验和
    校验和 = sum & 0xFFFF
    """
    # 确保数据长度为256字节
    if len(data) < 256:
        data.extend(b'\x00' * (256 - len(data)))
    elif len(data) > 256:
        del data[256:]

    # 先将校验和位置置0
    data[CHECKSUM_OFFSET] = 0
    data[CHECKSUM_OFFSET + 1] = 0

    # 按字节累加前254字节
    total = sum(data[:CHECKSUM_OFFSET])  # 直接对字节求和
    checksum = total & 0xFFFF  # 取低16位

    # 写入校验和（小端序）
    data[CHECKSUM_OFFSET] = checksum & 0xFF
    data[CHECKSUM_OFFSET + 1] = (checksum >> 8) & 0xFF

    return checksum


def verify_checksum(data):
    """验证校验和：计算前254字节的字节累加和，与最后两字节比较"""
    if len(data) < 256:
        return False

    # 读取存储的校验和
    stored_checksum = int.from_bytes(data[CHECKSUM_OFFSET:CHECKSUM_OFFSET + 2], 'little')

    # 按字节累加前254字节
    total = sum(data[:CHECKSUM_OFFSET])
    calculated_checksum = total & 0xFFFF

    return stored_checksum == calculated_checksum


def get_checksum_info(data):
    """获取校验和详细信息，用于显示"""
    if len(data) < 256:
        return None

    # 读取存储的校验和
    stored_checksum = int.from_bytes(data[CHECKSUM_OFFSET:CHECKSUM_OFFSET + 2], 'little')

    # 按字节累加前254字节
    total = sum(data[:CHECKSUM_OFFSET])
    calculated_checksum = total & 0xFFFF

    return {
        'stored': stored_checksum,
        'calculated': calculated_checksum,
        'total': total,
        'valid': stored_checksum == calculated_checksum,
        'stored_hex': f"0x{stored_checksum:04X}",
        'calculated_hex': f"0x{calculated_checksum:04X}",
        'stored_bytes': f"{data[CHECKSUM_OFFSET]:02X} {data[CHECKSUM_OFFSET + 1]:02X}"
    }
# ============ 可编辑的Hex表格 ============
class HexTableEditor:
    def __init__(self, parent, data_callback):
        self.parent = parent
        self.data_callback = data_callback
        self.entries = []
        self.create_widgets()

    def create_widgets(self):
        main_frame = ttk.Frame(self.parent)
        main_frame.pack(fill=tk.BOTH, expand=True)

        control_frame = ttk.Frame(main_frame)
        control_frame.pack(fill=tk.X, pady=(0, 5))
        ttk.Label(control_frame, text="双击单元格修改数据 (十六进制)").pack(side=tk.LEFT)
        ttk.Button(control_frame, text="刷新", command=self.refresh).pack(side=tk.RIGHT, padx=5)
        ttk.Button(control_frame, text="应用修改", command=self.apply_changes).pack(side=tk.RIGHT, padx=5)

        # 图例
        legend_frame = ttk.Frame(main_frame)
        legend_frame.pack(fill=tk.X, pady=(0, 5))
        ttk.Label(legend_frame, text="图例:", font=('', 8)).pack(side=tk.LEFT)
        ttk.Label(legend_frame, text="红色=校验和", foreground='red', font=('', 8)).pack(side=tk.LEFT, padx=5)
        ttk.Label(legend_frame, text="蓝色=版本", foreground='blue', font=('', 8)).pack(side=tk.LEFT, padx=5)
        ttk.Label(legend_frame, text="绿色=License", foreground='green', font=('', 8)).pack(side=tk.LEFT, padx=5)

        table_frame = ttk.Frame(main_frame)
        table_frame.pack(fill=tk.BOTH, expand=True)

        scrollbar_y = ttk.Scrollbar(table_frame)
        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)

        scrollbar_x = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL)
        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)

        self.canvas = tk.Canvas(table_frame, yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar_y.config(command=self.canvas.yview)
        scrollbar_x.config(command=self.canvas.xview)

        self.inner_frame = ttk.Frame(self.canvas)
        self.canvas.create_window((0, 0), window=self.inner_frame, anchor="nw")
        self.inner_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))

    def refresh(self):
        for widget in self.inner_frame.winfo_children():
            widget.destroy()

        data = self.data_callback()
        if not data:
            return

        self.entries = []

        headers = ['地址'] + [f'{i:02X}' for i in range(16)]
        for col, text in enumerate(headers):
            label = ttk.Label(self.inner_frame, text=text, font=('Consolas', 9, 'bold'),
                              relief=tk.RIDGE, padding=2)
            label.grid(row=0, column=col, sticky='nsew')

        for row in range(16):
            addr = row * 16
            addr_label = ttk.Label(self.inner_frame, text=f'0x{addr:02X}', font=('Consolas', 9),
                                   relief=tk.RIDGE, padding=2)
            addr_label.grid(row=row + 1, column=0, sticky='nsew')

            for col in range(16):
                idx = addr + col
                if idx < len(data):
                    value = data[idx]
                    entry_var = tk.StringVar(value=f'{value:02X}')
                    entry = ttk.Entry(self.inner_frame, textvariable=entry_var,
                                      width=4, font=('Consolas', 9), justify='center')
                    entry.grid(row=row + 1, column=col + 1, sticky='nsew', padx=1, pady=1)

                    entry.bind('<Double-Button-1>', lambda e, r=row, c=col: self.on_cell_click(e, r, c))
                    entry.bind('<KeyRelease>', lambda e, r=row, c=col: self.on_cell_change(e, r, c))

                    # 颜色标记
                    if idx == CHECKSUM_OFFSET or idx == CHECKSUM_OFFSET + 1:
                        entry.config(foreground='red', background='#FFE0E0')
                    elif idx == VERSION_OFFSET:
                        entry.config(foreground='blue', background='#E0E0FF')
                    elif any(offset <= idx < offset + LICENSE_SIZE for offset in LICENSE_OFFSETS):
                        entry.config(foreground='green', background='#E0FFE0')

                    self.entries.append({
                        'row': row,
                        'col': col,
                        'idx': idx,
                        'var': entry_var,
                        'entry': entry,
                        'original': value
                    })

        for col in range(17):
            self.inner_frame.grid_columnconfigure(col, weight=1)
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def on_cell_click(self, event, row, col):
        entry = event.widget
        entry.select_range(0, tk.END)
        entry.focus()

    def on_cell_change(self, event, row, col):
        entry = event.widget
        text = entry.get().strip()
        if len(text) == 0:
            return
        if not re.match(r'^[0-9A-Fa-f]{1,2}$', text):
            entry.config(foreground='red')
        else:
            idx = row * 16 + col
            if idx == CHECKSUM_OFFSET or idx == CHECKSUM_OFFSET + 1:
                entry.config(foreground='red')
            elif idx == VERSION_OFFSET:
                entry.config(foreground='blue')
            elif any(offset <= idx < offset + LICENSE_SIZE for offset in LICENSE_OFFSETS):
                entry.config(foreground='green')
            else:
                entry.config(foreground='black')

    def apply_changes(self):
        data = self.data_callback()
        if not data:
            return

        modified = False
        for entry_info in self.entries:
            text = entry_info['var'].get().strip()
            if text:
                try:
                    new_value = int(text, 16)
                    if new_value != entry_info['original']:
                        data[entry_info['idx']] = new_value
                        entry_info['original'] = new_value
                        modified = True
                except ValueError:
                    pass

        if modified:
            compute_checksum(data)
            self.refresh()
            return True
        return False


# ============ EEPROM 构建器 ============
class EEPROMBuilder:
    def __init__(self):
        self.data = bytearray(256)
        self.blocks = []
        self.license_cache = {}  # 缓存已生成的License {serial: license_str}

    def init_default(self):
        self.data = bytearray(256)
        self.blocks = []
        self.license_cache = {}

    def get_license(self, serial_str, month, year):
        """获取或生成License，相同SN只生成一次"""
        key = f"{serial_str}_{month}_{year}"
        if key not in self.license_cache:
            code = compute_activation_code(serial_str, month, year)
            license_str = f"{serial_str} {month:02d}/{year} {code}"
            self.license_cache[key] = license_str
        return self.license_cache[key]

    def add_block(self, category, serial_str, month, year, arch_names, version_byte=0x00):
        block_idx = len(self.blocks)
        if block_idx >= MAX_BLOCKS:
            return False

        block_offset = block_idx * BLOCK_SIZE
        block = bytearray(BLOCK_SIZE)

        # 设置分类
        category_bytes = category.encode('ascii')[:4]
        block[CATEGORY_OFFSET:CATEGORY_OFFSET + 4] = category_bytes.ljust(4, b'\x00')

        # 设置SN
        serial_bytes = serial_str.encode('ascii')[:12]
        if serial_bytes[0] > 0x39:
            serial_bytes = serial_bytes[1:]
        block[SN_OFFSET:SN_OFFSET + 12] = serial_bytes.ljust(12, b'\x00')
        # 生成License（使用缓存）
        license_str = self.get_license(serial_str, month, year)
        license_bytes = license_str.encode('ascii')[:LICENSE_SIZE]
        license_offset = LICENSE_OFFSETS[block_idx]
        self.data[license_offset:license_offset + LICENSE_SIZE] = license_bytes.ljust(LICENSE_SIZE, b'\x00')

        # 设置架构Hash
        hash_offset = HASH_OFFSET
        arch_count = 0
        for arch_name in arch_names[:MAX_ARCH_PER_BLOCK]:
            if arch_name in ARCH_FILES:
                result = compute_arch_hash(serial_str, ARCH_FILES[arch_name])
                hash_value = result['hash']
                block[hash_offset:hash_offset + 4] = hash_value.to_bytes(4, 'little')
                hash_offset += 4
                arch_count += 1

        # 设置版本
        self.data[VERSION_OFFSET] = version_byte & 0xFF

        # 写入块
        self.data[block_offset:block_offset + BLOCK_SIZE] = block

        self.blocks.append({
            'offset': block_offset,
            'idx': block_idx,
            'category': category,
            'serial': serial_str,
            'month': month,
            'year': year,
            'arch_names': arch_names[:MAX_ARCH_PER_BLOCK],
            'arch_count': arch_count,
            'license_offset': license_offset,
            'license': license_str
        })
        return True

    def build(self, blocks_data, version_byte=0x00):
        self.init_default()
        for i, block_info in enumerate(blocks_data):
            if i >= MAX_BLOCKS:
                break
            self.add_block(
                block_info.get('category', ''),
                block_info['serial'],
                block_info.get('month', 10),
                block_info.get('year', 2026),
                block_info['arch_names'],
                version_byte
            )
        checksum = compute_checksum(self.data)
        return self.data, checksum, len(self.blocks)

    @classmethod
    def from_file(cls, filepath):
        with open(filepath, 'rb') as f:
            data = bytearray(f.read())
            if len(data) < 256:
                data.extend(b'\x00' * (256 - len(data)))
            elif len(data) > 256:
                data = data[:256]
        builder = cls()
        builder.data = data

        builder.blocks = []
        builder.license_cache = {}

        for block_idx in range(MAX_BLOCKS):
            block_offset = block_idx * BLOCK_SIZE
            block = data[block_offset:block_offset + BLOCK_SIZE]

            serial_bytes = bytes(block[SN_OFFSET:SN_OFFSET + 12]).strip(b'\x00')
            if not serial_bytes:
                continue

            try:
                serial_str = serial_bytes.decode('ascii')
                category_bytes = bytes(block[CATEGORY_OFFSET:CATEGORY_OFFSET + 4]).strip(b'\x00')
                category = category_bytes.decode('ascii') if category_bytes else ''

                license_offset = LICENSE_OFFSETS[block_idx]
                license_bytes = bytes(data[license_offset:license_offset + LICENSE_SIZE]).strip(b'\x00')
                month, year = 10, 2026
                license_str = ''
                if license_bytes:
                    try:
                        license_str = license_bytes.decode('ascii')
                        parts = license_str.split()
                        if len(parts) >= 2:
                            date_parts = parts[1].split('/')
                            if len(date_parts) == 2:
                                month = int(date_parts[0])
                                year = int(date_parts[1])
                    except:
                        pass

                # 缓存License
                if license_str:
                    key = f"{serial_str}_{month}_{year}"
                    builder.license_cache[key] = license_str

                arch_names = []
                hash_offset = HASH_OFFSET
                for _ in range(MAX_ARCH_PER_BLOCK):
                    hash_value = int.from_bytes(block[hash_offset:hash_offset + 4], 'little')
                    if hash_value != 0:
                        matched = False
                        for name, filename in ARCH_FILES.items():
                            result = compute_arch_hash(serial_str, filename)
                            if result['hash'] == hash_value:
                                arch_names.append(name)
                                matched = True
                                break
                        if not matched:
                            arch_names.append(f"0x{hash_value:08X}")
                    hash_offset += 4

                builder.blocks.append({
                    'offset': block_offset,
                    'idx': block_idx,
                    'category': category,
                    'serial': serial_str,
                    'month': month,
                    'year': year,
                    'arch_names': arch_names[:MAX_ARCH_PER_BLOCK],
                    'arch_count': len([a for a in arch_names if not a.startswith('0x')]),
                    'license_offset': license_offset,
                    'license': license_str
                })
            except:
                pass

        return builder

    def save(self, filepath):
        if len(self.data) < 256:
            self.data.extend(b'\x00' * (256 - len(self.data)))
        with open(filepath, 'wb') as f:
            f.write(self.data[:256])


# ============ 块编辑对话框 ============
class BlockEditDialog:
    def __init__(self, parent, block_data=None):
        self.parent = parent
        self.block_data = block_data or {'category': '', 'serial': '', 'arch_names': [], 'month': 10, 'year': 2026}
        self.result = None

    def show(self):
        dialog = tk.Toplevel(self.parent)
        dialog.title("编辑块")
        dialog.geometry("600x520")
        dialog.transient(self.parent)
        dialog.grab_set()

        main_frame = ttk.Frame(dialog, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        ttk.Label(main_frame, text="分类 (4字节):").grid(row=0, column=0, sticky=tk.W, pady=2)
        category_var = tk.StringVar(value=self.block_data.get('category', ''))
        category_entry = ttk.Entry(main_frame, textvariable=category_var, width=30)
        category_entry.grid(row=0, column=1, sticky=tk.W, pady=2)
        ttk.Label(main_frame, text="(最多4个字符)", font=('', 8), foreground='gray').grid(row=0, column=2, sticky=tk.W,
                                                                                          pady=2, padx=5)

        ttk.Label(main_frame, text="序列号 (SN):").grid(row=1, column=0, sticky=tk.W, pady=2)
        serial_var = tk.StringVar(value=self.block_data.get('serial', ''))
        serial_entry = ttk.Entry(main_frame, textvariable=serial_var, width=30)
        serial_entry.grid(row=1, column=1, sticky=tk.W, pady=2)

        ttk.Label(main_frame, text="有效期 (月/年):").grid(row=2, column=0, sticky=tk.W, pady=2)
        month_year_frame = ttk.Frame(main_frame)
        month_year_frame.grid(row=2, column=1, sticky=tk.W, pady=2)

        month_var = tk.StringVar(value=str(self.block_data.get('month', 10)))
        month_spin = ttk.Spinbox(month_year_frame, from_=1, to=12, width=5, textvariable=month_var)
        month_spin.pack(side=tk.LEFT)
        ttk.Label(month_year_frame, text=" / ").pack(side=tk.LEFT)
        year_var = tk.StringVar(value=str(self.block_data.get('year', 2026)))
        year_spin = ttk.Spinbox(month_year_frame, from_=2000, to=2099, width=7, textvariable=year_var)
        year_spin.pack(side=tk.LEFT)

        ttk.Label(main_frame, text="架构授权 (最多4个):").grid(row=3, column=0, sticky=tk.W, pady=2)
        arch_frame = ttk.Frame(main_frame)
        arch_frame.grid(row=3, column=1, sticky=tk.W, pady=2)

        arch_display_var = tk.StringVar()
        arch_display_var.set(
            ', '.join(self.block_data.get('arch_names', [])) if self.block_data.get('arch_names') else '(未选择)')
        arch_entry = ttk.Entry(arch_frame, textvariable=arch_display_var, width=40, state='readonly')
        arch_entry.pack(side=tk.LEFT)

        selected_archs = list(self.block_data.get('arch_names', []))

        def show_arch_selector():
            nonlocal selected_archs
            arch_dialog = tk.Toplevel(dialog)
            arch_dialog.title("选择架构")
            arch_dialog.geometry("400x400")
            arch_dialog.transient(dialog)
            arch_dialog.grab_set()

            arch_frame2 = ttk.Frame(arch_dialog, padding="10")
            arch_frame2.pack(fill=tk.BOTH, expand=True)

            ttk.Label(arch_frame2, text="选择架构 (最多4个):").pack(anchor=tk.W)

            list_frame = ttk.Frame(arch_frame2)
            list_frame.pack(fill=tk.BOTH, expand=True, pady=10)

            scrollbar = ttk.Scrollbar(list_frame)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

            listbox = tk.Listbox(list_frame, selectmode=tk.MULTIPLE, yscrollcommand=scrollbar.set)
            listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
            scrollbar.config(command=listbox.yview)

            sorted_archs = sorted(ARCH_FILES.keys())
            for arch_name in sorted_archs:
                listbox.insert(tk.END, arch_name)
                if arch_name in selected_archs:
                    listbox.selection_set(tk.END)

            btn_frame = ttk.Frame(arch_frame2)
            btn_frame.pack(fill=tk.X, pady=10)

            def on_ok():
                nonlocal selected_archs
                selected_indices = listbox.curselection()
                selected_archs = [listbox.get(i) for i in selected_indices]
                if len(selected_archs) > 4:
                    messagebox.showerror("错误", "最多只能选择4个架构")
                    return
                arch_display_var.set(', '.join(selected_archs) if selected_archs else '(未选择)')
                arch_dialog.destroy()

            def on_cancel():
                arch_dialog.destroy()

            ttk.Button(btn_frame, text="确定", command=on_ok).pack(side=tk.LEFT, padx=5)
            ttk.Button(btn_frame, text="取消", command=on_cancel).pack(side=tk.LEFT, padx=5)
            arch_dialog.wait_window(arch_dialog)

        ttk.Button(arch_frame, text="选择...", command=show_arch_selector).pack(side=tk.LEFT, padx=5)
        ttk.Label(arch_frame, text=f"(最多4个)", font=('', 8), foreground='gray').pack(side=tk.LEFT)

        ttk.Label(main_frame, text="注意: 相同SN的有效期激活码只保存一份",
                  font=('', 9), foreground='red').grid(row=4, column=0, columnspan=3, sticky=tk.W, pady=10)

        btn_frame2 = ttk.Frame(main_frame)
        btn_frame2.grid(row=5, column=0, columnspan=3, pady=20)

        def on_ok():
            category = category_var.get().strip()[:4]
            serial = serial_var.get().strip()

            if not serial:
                messagebox.showerror("错误", "请填写序列号")
                return

            try:
                month = int(month_var.get())
                year = int(year_var.get())
                if not (1 <= month <= 12):
                    messagebox.showerror("错误", "月份必须在1-12之间")
                    return
                if not (2000 <= year <= 2099):
                    messagebox.showerror("错误", "年份必须在2000-2099之间")
                    return
            except ValueError:
                messagebox.showerror("错误", "请输入有效的数字")
                return

            if not selected_archs:
                messagebox.showerror("错误", "请至少选择一个架构")
                return

            self.result = {
                'category': category,
                'serial': serial,
                'month': month,
                'year': year,
                'arch_names': selected_archs[:4]
            }
            dialog.destroy()

        def on_cancel():
            self.result = None
            dialog.destroy()

        ttk.Button(btn_frame2, text="确定", command=on_ok, width=10).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame2, text="取消", command=on_cancel, width=10).pack(side=tk.LEFT, padx=5)

        dialog.wait_window(dialog)
        return self.result


# ============ GUI 程序 ============
class EEPROMGeneratorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("TRACE32 EEPROM 生成器")
        self.root.geometry("1200x950")
        self.root.resizable(True, True)

        self.current_file = None
        self.current_data = bytearray(256)
        self.current_checksum = 0
        self.blocks = []
        self.hex_editor = None

        self.create_widgets()
        self.load_default()

    def create_widgets(self):
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        paned = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.BOTH, expand=True)

        left_frame = ttk.Frame(paned)
        paned.add(left_frame, weight=1)

        input_frame = ttk.LabelFrame(left_frame, text="输入信息", padding="10")
        input_frame.pack(fill=tk.X, pady=(0, 10))

        ttk.Label(input_frame, text="版本 (0xF0):").grid(row=0, column=0, sticky=tk.W, pady=2)
        version_frame = ttk.Frame(input_frame)
        version_frame.grid(row=0, column=1, sticky=tk.W, pady=2)
        self.version_var = tk.StringVar()
        self.version_entry = ttk.Entry(version_frame, textvariable=self.version_var, width=10)
        self.version_entry.pack(side=tk.LEFT)
        ttk.Label(version_frame, text="(1字节十六进制)", font=('', 8), foreground='gray').pack(side=tk.LEFT, padx=5)
        self.version_entry.bind('<KeyRelease>', self.on_change)

        self.block_count_label = ttk.Label(input_frame, text="块数量: 0/3")
        self.block_count_label.grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=2)

        btn_frame = ttk.Frame(left_frame)
        btn_frame.pack(fill=tk.X, pady=(0, 5))
        ttk.Button(btn_frame, text="添加块", command=self.add_block).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(btn_frame, text="编辑块", command=self.edit_block).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="删除块", command=self.delete_block).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="清空块", command=self.clear_blocks).pack(side=tk.LEFT, padx=5)

        list_frame = ttk.LabelFrame(left_frame, text="块列表", padding="5")
        list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 5))

        columns = ('序号', '偏移', '分类', 'SN', '有效期', '架构数', '架构列表')
        self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=4)
        self.tree.pack(fill=tk.BOTH, expand=True)

        self.tree.heading('序号', text='序号')
        self.tree.heading('偏移', text='偏移')
        self.tree.heading('分类', text='分类')
        self.tree.heading('SN', text='SN')
        self.tree.heading('有效期', text='有效期')
        self.tree.heading('架构数', text='架构数')
        self.tree.heading('架构列表', text='架构列表')
        self.tree.column('序号', width=50, anchor='center')
        self.tree.column('偏移', width=60, anchor='center')
        self.tree.column('分类', width=70, anchor='center')
        self.tree.column('SN', width=130)
        self.tree.column('有效期', width=70, anchor='center')
        self.tree.column('架构数', width=50, anchor='center')
        self.tree.column('架构列表', width=200)
        self.tree.bind('<Double-Button-1>', lambda e: self.edit_block())

        bottom_btn_frame = ttk.Frame(left_frame)
        bottom_btn_frame.pack(fill=tk.X, pady=(5, 0))
        ttk.Button(bottom_btn_frame, text="生成 EEPROM", command=self.generate).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(bottom_btn_frame, text="打开BIN", command=self.open_file).pack(side=tk.LEFT, padx=5)
        ttk.Button(bottom_btn_frame, text="导入BIN", command=self.import_bin).pack(side=tk.LEFT, padx=5)
        ttk.Button(bottom_btn_frame, text="保存BIN", command=self.save).pack(side=tk.LEFT, padx=5)
        ttk.Button(bottom_btn_frame, text="另存为", command=self.save_as).pack(side=tk.LEFT, padx=5)

        self.checksum_label = ttk.Label(left_frame, text="校验和: 未计算", font=('', 9, 'bold'))
        self.checksum_label.pack(anchor=tk.W, pady=5)

        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(left_frame, textvariable=self.status_var, relief=tk.SUNKEN)
        status_bar.pack(fill=tk.X, pady=(5, 0))

        right_frame = ttk.Frame(paned)
        paned.add(right_frame, weight=1)

        editor_frame = ttk.LabelFrame(right_frame, text="Hex编辑器 (双击修改)", padding="5")
        editor_frame.pack(fill=tk.BOTH, expand=True)

        self.hex_editor = HexTableEditor(editor_frame, self.get_data)
        self.hex_editor.refresh()

    def get_data(self):
        return self.current_data

    def parse_version(self, version_str):
        if not version_str.strip():
            return 0x00
        value = version_str.strip()
        try:
            if value.startswith('0x'):
                return int(value, 16) & 0xFF
            else:
                return int(value, 16) & 0xFF
        except ValueError:
            return None

    def update_block_list(self):
        self.tree.delete(*self.tree.get_children())
        for i, block in enumerate(self.blocks):
            arch_list = ', '.join(block['arch_names']) if block['arch_names'] else '(无)'
            self.tree.insert('', 'end', values=(
                i + 1,
                f"0x{i * BLOCK_SIZE:02X}",
                block.get('category', ''),
                block['serial'],
                f"{block.get('month', 10)}/{block.get('year', 2026)}",
                len(block['arch_names']),
                arch_list
            ))
        self.block_count_label.config(text=f"块数量: {len(self.blocks)}/{MAX_BLOCKS}")

    def add_block(self):
        if len(self.blocks) >= MAX_BLOCKS:
            messagebox.showerror("错误", f"最多只能添加{MAX_BLOCKS}个块")
            return
        dialog = BlockEditDialog(self.root)
        result = dialog.show()
        if result:
            self.blocks.append(result)
            self.update_block_list()
            self.generate()

    def edit_block(self):
        selection = self.tree.selection()
        if not selection:
            messagebox.showerror("错误", "请先选择一个块")
            return
        index = self.tree.index(selection[0])
        if index >= len(self.blocks):
            return
        dialog = BlockEditDialog(self.root, self.blocks[index])
        result = dialog.show()
        if result:
            self.blocks[index] = result
            self.update_block_list()
            self.generate()

    def delete_block(self):
        selection = self.tree.selection()
        if not selection:
            messagebox.showerror("错误", "请先选择一个块")
            return
        index = self.tree.index(selection[0])
        if index >= len(self.blocks):
            return
        if messagebox.askyesno("确认", f"确定要删除块 {index + 1} 吗？"):
            del self.blocks[index]
            self.update_block_list()
            self.generate()

    def clear_blocks(self):
        if not self.blocks:
            return
        if messagebox.askyesno("确认", "确定要清空所有块吗？"):
            self.blocks = []
            self.update_block_list()
            self.generate()

    def load_default(self):
        self.version_var.set("0x01")
        self.blocks = [{
            'category': 'arm',
            'serial': 'C17080238053',
            'month': 10,
            'year': 2026,
            'arch_names': ['Cortex-M']
        }]
        self.update_block_list()
        self.current_file = None
        self.generate()

    def clear_all(self):
        self.version_var.set("")
        self.blocks = []
        self.update_block_list()
        self.current_file = None
        self.current_data = bytearray(256)
        self.current_checksum = 0
        self.checksum_label.config(text="校验和: 未计算")
        self.status_var.set("已清空")
        self.hex_editor.refresh()

    def generate(self):
        try:
            if not self.blocks:
                self.status_var.set("请添加至少一个块")
                return

            version_byte = self.parse_version(self.version_var.get())
            if version_byte is None:
                messagebox.showerror("错误", "版本数据格式错误")
                return

            builder = EEPROMBuilder()
            blocks_data = []
            for block in self.blocks[:MAX_BLOCKS]:
                blocks_data.append({
                    'category': block.get('category', ''),
                    'serial': block['serial'],
                    'month': block.get('month', 10),
                    'year': block.get('year', 2026),
                    'arch_names': block['arch_names'][:MAX_ARCH_PER_BLOCK]
                })

            data, checksum, block_count = builder.build(blocks_data, version_byte)
            self.current_data = data
            self.current_checksum = checksum

            is_valid = verify_checksum(data)
            status_text = "✓ 有效" if is_valid else "✗ 无效"
            self.checksum_label.config(text=f"校验和: 0x{checksum:04X} ({status_text})")
            self.hex_editor.refresh()
            self.status_var.set(f"✓ 生成完成 (校验和: 0x{checksum:04X}, 块数: {block_count})")

        except Exception as e:
            messagebox.showerror("错误", f"生成失败: {e}")

    def on_change(self, event=None):
        if hasattr(self, '_change_after'):
            self.root.after_cancel(self._change_after)
        self._change_after = self.root.after(300, self.generate)

    def open_file(self):
        filepath = filedialog.askopenfilename(
            title="打开BIN文件",
            filetypes=[("BIN文件", "*.bin *.eep"), ("所有文件", "*.*")]
        )
        if not filepath:
            return
        self.load_from_path(filepath)

    def import_bin(self):
        filepath = filedialog.askopenfilename(
            title="导入BIN文件",
            filetypes=[("BIN文件", "*.bin *.eep"), ("所有文件", "*.*")]
        )
        if not filepath:
            return
        self.load_from_path(filepath)

    def load_from_path(self, filepath):
        try:
            builder = EEPROMBuilder.from_file(filepath)
            self.current_data = builder.data

            is_valid = verify_checksum(builder.data)

            # 获取校验和信息
            checksum_info = get_checksum_info(builder.data)
            if checksum_info:
                self.current_checksum = checksum_info['stored']
            else:
                self.current_checksum = 0

            self.current_file = filepath

            version_byte = builder.data[VERSION_OFFSET]
            self.version_var.set(f"0x{version_byte:02X}")

            self.blocks = builder.blocks
            self.update_block_list()

            self.hex_editor.refresh()

            # 显示详细的校验和信息
            if checksum_info:
                status_text = "✓ 有效" if checksum_info['valid'] else "✗ 无效 (文件可能已损坏)"
                self.checksum_label.config(
                    text=f"校验和: 0x{checksum_info['stored']:04X} ({status_text})"
                )
                self.status_var.set(
                    f"✓ 已加载: {os.path.basename(filepath)} | "
                    f"存储: 0x{checksum_info['stored']:04X}, "
                    f"计算: 0x{checksum_info['calculated']:04X}, "
                    f"状态: {'有效' if checksum_info['valid'] else '无效'}"
                )
            else:
                self.checksum_label.config(text="校验和: 无法计算")
                self.status_var.set(f"已加载: {os.path.basename(filepath)}")

        except Exception as e:
            messagebox.showerror("错误", f"加载失败: {e}")

    def save(self):
        if len(self.current_data) < 256:
            self.current_data.extend(b'\x00' * (256 - len(self.current_data)))
        if not self.current_file:
            self.save_as()
            return
        try:
            temp_data = bytearray(self.current_data)
            compute_checksum(temp_data)
            with open(self.current_file, 'wb') as f:
                f.write(temp_data[:256])
            self.current_data = temp_data
            self.current_checksum = compute_checksum(bytearray(temp_data))
            self.status_var.set(f"✓ 已保存: {os.path.basename(self.current_file)}")
            self.hex_editor.refresh()
        except Exception as e:
            messagebox.showerror("错误", f"保存失败: {e}")

    def save_as(self):
        if len(self.current_data) < 256:
            self.current_data.extend(b'\x00' * (256 - len(self.current_data)))
        filepath = filedialog.asksaveasfilename(
            title="保存BIN文件",
            defaultextension=".bin",
            filetypes=[("BIN文件", "*.bin *.eep"), ("所有文件", "*.*")]
        )
        if not filepath:
            return
        try:
            temp_data = bytearray(self.current_data)
            compute_checksum(temp_data)
            with open(filepath, 'wb') as f:
                f.write(temp_data[:256])
            self.current_data = temp_data
            self.current_checksum = compute_checksum(bytearray(temp_data))
            self.current_file = filepath
            self.status_var.set(f"✓ 已保存: {os.path.basename(filepath)}")
            self.hex_editor.refresh()
        except Exception as e:
            messagebox.showerror("错误", f"保存失败: {e}")


def main():
    root = tk.Tk()
    app = EEPROMGeneratorApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()