import sys
import os
import array
import pyperclip
import tkinter as tk
from tkinter import messagebox

GRID_SIZE = 50
CELL_SIZE = 12
PADDING = 4

class TkFontEditor:
    def __init__(self, root):
        self.root = root
        self.root.title("和文私用領域フォントエディタ (TTF出力・アドレス指定完全版)")

        self.grid = [[False for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
        self.strokes = []
        self.current_stroke = []

        self.total_size = GRID_SIZE * CELL_SIZE

        # キャンバスの配置
        self.canvas = tk.Canvas(root, width=self.total_size, height=self.total_size, bg="white", highlightthickness=0)
        self.canvas.pack(padx=15, pady=10)

        # ---------------------------------------------------------
        # コントロールパネルUI領域
        # ---------------------------------------------------------
        # 操作ガイド
        guide_text = "※操作方法: 左ドラッグで1画ずつ描く / 右ドラッグで消去 / Ctrl+Zで1画戻す"
        self.label_guide = tk.Label(root, text=guide_text, fg="#555555", font=("MS Gothic", 9))
        self.label_guide.pack(anchor="w", padx=15, pady=(0, 2))

        # Unicodeアドレス入力フレーム
        unicode_frame = tk.Frame(root)
        unicode_frame.pack(anchor="w", padx=15, pady=5)

        tk.Label(unicode_frame, text="割り当てるUnicodeアドレス (16進数 4桁): U+", font=("MS Gothic", 10)).pack(side="left")

        # 16進数入力欄 (デフォルト: E000)
        self.unicode_entry = tk.Entry(unicode_frame, width=8, font=("Courier New", 10, "bold"), justify="center")
        self.unicode_entry.insert(0, "E000")
        self.unicode_entry.pack(side="left", padx=5)

        # 各種操作ボタン配置フレーム
        button_frame = tk.Frame(root)
        button_frame.pack(fill="x", padx=15, pady=(5, 15))

        # ボタンを均等配置するためのグリッド重み設定
        button_frame.columnconfigure(0, weight=1)
        button_frame.columnconfigure(1, weight=1)
        button_frame.columnconfigure(2, weight=1)

        btn_clear = tk.Button(button_frame, text="全クリア", command=self.clear_canvas, height=1)
        btn_clear.grid(row=0, column=0, padx=4, sticky="ew")

        btn_undo = tk.Button(button_frame, text="1画戻す (Ctrl+Z)", command=self.undo_last_stroke, height=1)
        btn_undo.grid(row=0, column=1, padx=4, sticky="ew")

        btn_submit = tk.Button(button_frame, text="TTFフォントとして出力", command=self.build_ttf, bg="#e1f5fe", fg="#0277bd", font=("MS Gothic", 9, "bold"), height=1)
        btn_submit.grid(row=0, column=2, padx=4, sticky="ew")

        # ---------------------------------------------------------
        # イベントバインディング
        # ---------------------------------------------------------
        self.canvas.bind("<B1-Motion>", self.on_left_drag)
        self.canvas.bind("<Button-1>", self.on_left_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_left_release)
        self.canvas.bind("<B3-Motion>", self.on_right_drag)
        self.canvas.bind("<Button-3>", self.on_right_drag)

        # 各種ショートカットキー設定
        self.root.bind("<Control-z>", lambda e: self.undo_last_stroke())
        self.root.bind("<Control-Z>", lambda e: self.undo_last_stroke())
        self.root.bind("1", lambda e: self.undo_last_stroke())  # 互換キー
        self.root.bind("2", lambda e: self.clear_canvas())     # 互換キー
        self.root.bind("s", lambda e: self.build_ttf())
        self.root.bind("S", lambda e: self.build_ttf())

        self.draw_ui_base()

    # ---------------------------------------------------------
    # UIベース描画とグリフ描画ロジック (提供コードの結合)
    # ---------------------------------------------------------
    def draw_ui_base(self):
        self.canvas.delete("all")
        for y in range(GRID_SIZE):
            for x in range(GRID_SIZE):
                x1, y1 = x * CELL_SIZE, y * CELL_SIZE
                x2, y2 = x1 + CELL_SIZE, y1 + CELL_SIZE
                if x < PADDING or x >= (GRID_SIZE - PADDING) or y < PADDING or y >= (GRID_SIZE - PADDING):
                    bg_color = "#e6f0ff"
                    line_color = "#cbdcfa"
                else:
                    bg_color = "white"
                    line_color = "#e6e6e6"
                if self.grid[y][x]:
                    bg_color = "#282828"
                    line_color = "#3c3c3c"
                self.canvas.create_rectangle(x1, y1, x2, y2, fill=bg_color, outline=line_color, tags=f"cell_{x}_{y}")
        se = PADDING * CELL_SIZE
        ee = (GRID_SIZE - PADDING) * CELL_SIZE
        self.canvas.create_rectangle(se, se, ee, ee, outline="#96b4dc", dash=(4, 4), width=1)
        self.draw_red_lines()

    def draw_red_lines(self):
        self.canvas.delete("red_meta")
        all_strokes = self.strokes + ([self.current_stroke] if self.current_stroke else [])
        for stroke in all_strokes:
            if len(stroke) > 1:
                for i in range(len(stroke) - 1):
                    x1 = (stroke[i][0] + 0.5) * CELL_SIZE
                    y1 = (stroke[i][1] + 0.5) * CELL_SIZE
                    x2 = (stroke[i+1][0] + 0.5) * CELL_SIZE
                    y2 = (stroke[i+1][1] + 0.5) * CELL_SIZE
                    self.canvas.create_line(x1, y1, x2, y2, fill="red", width=2, tags="red_meta")
            for cx, cy in stroke:
                px = (cx + 0.5) * CELL_SIZE
                py = (cy + 0.5) * CELL_SIZE
                self.canvas.create_oval(px-2, py-2, px+2, py+2, fill="red", outline="red", tags="red_meta")

    def get_font_unit_coordinate(self, cell_pos):
        font_x = int((cell_pos[0] + 0.5) * 20)
        font_y = int((GRID_SIZE - (cell_pos[1] + 0.5)) * 20)
        return (font_x, font_y)

    # ---------------------------------------------------------
    # マウス・キーボード操作ハンドラ (提供コードの結合)
    # ---------------------------------------------------------
    def on_left_drag(self, event):
        x, y = event.x // CELL_SIZE, event.y // CELL_SIZE
        if 0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE:
            if self.current_stroke and self.current_stroke[-1] == (x, y): return
            if not self.grid[y][x]:
                self.grid[y][x] = True
                self.current_stroke.append((x, y))
                self.canvas.itemconfig(f"cell_{x}_{y}", fill="#282828", outline="#3c3c3c")
                self.draw_red_lines()

    def on_left_release(self, event):
        if len(self.current_stroke) >= 2:
            self.strokes.append(self.current_stroke)
        self.current_stroke = []
        self.draw_red_lines()

    def on_right_drag(self, event):
        x, y = event.x // CELL_SIZE, event.y // CELL_SIZE
        if 0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE:
            if self.grid[y][x]:
                self.grid[y][x] = False
                for s in self.strokes:
                    if (x, y) in s: s.remove((x, y))
                self.strokes = [s for s in self.strokes if len(s) > 0]
                self.draw_ui_base()

    def undo_last_stroke(self):
        if self.strokes:
            for x, y in self.strokes.pop(): self.grid[y][x] = False
            self.draw_ui_base()

    def clear_canvas(self):
        self.grid = [[False for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
        self.strokes = []
        self.current_stroke = []
        self.draw_ui_base()

    # ---------------------------------------------------------
    # 【完全バグ回避】TTFフォント構築 & HTML書き出し処理
    # ---------------------------------------------------------
    def build_ttf(self):
        from fontTools.fontBuilder import FontBuilder
        from fontTools.ttLib.tables._g_l_y_f import Glyph, GlyphCoordinates
        from fontTools.ttLib.tables import ttProgram

        # 1. ユーザー入力されたUnicodeアドレスの取得と検証
        hex_str = self.unicode_entry.get().strip()
        try:
            target_unicode = int(hex_str, 16)
            if not (0x0000 <= target_unicode <= 0x10FFFF):
                raise ValueError
        except ValueError:
            messagebox.showerror("エラー", "有効な16進数4桁（例: E000）を入力してください。")
            return

        # 2. ストロークデータのチェック
        all_strokes = [s for s in self.strokes if len(s) >= 2]
        if not all_strokes:
            messagebox.showwarning("エラー", "有効な線が描かれていません。")
            return

        # 3. グリフの作成処理 (フォントの座標変換を適用)
        g = Glyph()
        g.numberOfContours = len(all_strokes)
        g.program = ttProgram.Program()
        raw_coords, end_points, current_idx, thickness = [], [], 0, 15

        for stroke in all_strokes:
            font_coords = [self.get_font_unit_coordinate(pt) for pt in stroke]
            contour = []
            for fx, fy in font_coords:
                contour.append((fx + thickness, fy + thickness))
            for fx, fy in reversed(font_coords):
                contour.append((fx - thickness, fy - thickness))
            raw_coords.extend(contour)
            current_idx += len(contour)
            end_points.append(current_idx - 1)

        g.coordinates = GlyphCoordinates(raw_coords)
        g.endPtsOfContours = end_points
        g.flags = array.array('B', [1] * len(raw_coords))

        notdef_g = Glyph()
        notdef_g.program = ttProgram.Program()
        notdef_g.numberOfContours, notdef_g.coordinates = 0, GlyphCoordinates([])
        notdef_g.endPtsOfContours, notdef_g.flags = [], array.array('B', [])

        # 4. FontBuilderによる安全な一括ビルド
        fb = FontBuilder(1000, isTTF=True)
        fb.setupGlyphOrder(['.notdef', 'glyph00001'])
        fb.setupGlyf({'.notdef': notdef_g, 'glyph00001': g})

        # 入力されたアドレスと、Windowsビューアー用にダミーの「A」(0x0041)をマッピング
        fb.setupCharacterMap({0x0041: 'glyph00001', target_unicode: 'glyph00001'})

        fb.setupHorizontalMetrics({'.notdef': (1000, 0), 'glyph00001': (1000, 0)})
        fb.setupHorizontalHeader(ascent=800, descent=-200)

        nameStrings = {
            'familyName': 'MyGridFont',
            'styleName': 'Regular',
            'uniqueFontIdentifier': '1.000;FontTools;MyGridFont',
            'fullName': 'MyGridFont',
            'version': 'Version 1.000',
            'psName': 'MyGridFont-Regular',
        }

        nameStrings = {
            'familyName': 'MyGridFont',
            'styleName': 'Regular',
            'uniqueFontIdentifier': '1.000;FontTools;MyGridFont',
            'fullName': 'MyGridFont',
            'version': 'Version 1.000',
            'psName': 'MyGridFont-Regular',
        }
        fb.setupNameTable(nameStrings)
        fb.setupOS2(sTypoAscender=800, sTypoDescender=-200)
        fb.setupHead(fontRevision=1.0)
        fb.setupMaxp()
        fb.setupPost()

        # 5. デスクトップに保存および整合性チェックの実行
        font_filename = "MyGridFont.ttf"
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop", font_filename)

        fb.font.recalcBBox = True
        fb.font.recalcTimestamp = True

        try:
            fb.save(desktop_path)

            # HTML表示用の実体参照（大文字に揃える）
            html_entity = f"&#x{hex_str.upper()};"

            html_snippet = f"""<!-- 以下のコードをHTMLファイルに貼り付け、生成されたTTFontと同じフォルダに置いてください -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <style>
        @font-face {{
            font-family: 'MyGridFont';
            src: url('{font_filename}') format('truetype');
        }}
        .my-custom-char {{
            font-family: 'MyGridFont';
            font-size: 100px; /* 文字の大きさを調整できます */
            color: #333333;   /* 文字の色を変更できます */
        }}
    </style>
</head>
<body>
    <h1>自作フォントの表示テスト (U+{hex_str.upper()})</h1>
    <div class="my-custom-char">{html_entity}</div>
</body>
</html>"""

            # クリップボードにHTMLスニペットをコピー
            pyperclip.copy(html_snippet)

            messagebox.showinfo(
                "成功",
                f"フォントの書き出しに成功しました！\n"
                f"デスクトップに '{font_filename}' を出力しました。\n"
                f"(U+{hex_str.upper()} にグリフを登録しました)\n\n"
                f"そのままブラウザで表示確認できるHTMLコードがクリップボードにコピーされました。\n"
                f"テキストエディタなどに Ctrl+V で貼り付けてご使用ください。"
            )
        except Exception as e:
            messagebox.showerror("書き込みエラー", f"失敗しました:\n{e}")

if __name__ == "__main__":
    root = tk.Tk()
    app = TkFontEditor(root)
    root.mainloop()
