import os
import argparse
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import traceback

# --- グローバル変数 ---
settings = {
    "linecolor": "black",
    "linewidth": 1,
    "fontcolor": "black",
}
fig = None
ax = None

# --- 描画関数 (★draw_sphere を追加) ---
def draw_cube(ax, x, y, z, size):
    vertices = np.array([[x, y, z], [x + size, y, z], [x + size, y + size, z], [x, y + size, z], [x, y, z + size], [x + size, y, z + size], [x + size, y + size, z + size], [x, y + size, z + size]])
    edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)]
    for start, end in edges:
        points = vertices[[start, end]]
        ax.plot(points[:, 0], points[:, 1], points[:, 2], color=settings["linecolor"], linewidth=settings["linewidth"])

def draw_pyramid(ax, x, y, z, base, height):
    base_pts = np.array([[x, y, z], [x + base, y, z], [x + base, y + base, z], [x, y + base, z]])
    apex = np.array([x + base / 2, y + base / 2, z + height])
    base_edges = [(0, 1), (1, 2), (2, 3), (3, 0)]
    for start, end in base_edges:
        points = base_pts[[start, end]]
        ax.plot(points[:, 0], points[:, 1], points[:, 2], color=settings["linecolor"], linewidth=settings["linewidth"])
    for i in range(4):
        points = np.array([base_pts[i], apex])
        ax.plot(points[:, 0], points[:, 1], points[:, 2], color=settings["linecolor"], linewidth=settings["linewidth"])

# ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
# ここに追加: sphereを描画する関数
def draw_sphere(ax, x, y, z, radius):
    """ワイヤーフレームの球体を描画する"""
    u = np.linspace(0, 2 * np.pi, 20)  # 経度
    v = np.linspace(0, np.pi, 20)    # 緯度
    
    # 球面座標からデカルト座標へ変換
    X = x + radius * np.outer(np.cos(u), np.sin(v))
    Y = y + radius * np.outer(np.sin(u), np.sin(v))
    Z = z + radius * np.outer(np.ones(np.size(u)), np.cos(v))
    
    ax.plot_wireframe(X, Y, Z, color=settings["linecolor"], linewidth=settings["linewidth"])
# ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

def draw_closed_polygon_3d(ax, vertices):
    verts_closed = np.vstack([vertices, vertices[0]])
    ax.plot(verts_closed[:, 0], verts_closed[:, 1], verts_closed[:, 2], color=settings["linecolor"], linewidth=settings["linewidth"])

def draw_line_3d(ax, x1, y1, z1, x2, y2, z2):
    ax.plot([x1, x2], [y1, y2], [z1, z2], color=settings["linecolor"], linewidth=settings["linewidth"])

def draw_circle_3d(ax, x, y, z, r, segments=50):
    theta = np.linspace(0, 2 * np.pi, segments)
    xs = x + r * np.cos(theta); ys = y + r * np.sin(theta); zs = np.full_like(xs, z)
    ax.plot(xs, ys, zs, color=settings["linecolor"], linewidth=settings["linewidth"])

def draw_ellipse_3d(ax, x, y, z, w, h, segments=50):
    theta = np.linspace(0, 2 * np.pi, segments)
    xs = x + (w / 2) * np.cos(theta); ys = y + (h / 2) * np.sin(theta); zs = np.full_like(xs, z)
    ax.plot(xs, ys, zs, color=settings["linecolor"], linewidth=settings["linewidth"])

def draw_rect_3d(ax, x, y, z, w, h):
    vertices = np.array([[x, y, z], [x + w, y, z], [x + w, y + h, z], [x, y + h, z]])
    draw_closed_polygon_3d(ax, vertices)

def draw_square_3d(ax, x, y, z, size):
    draw_rect_3d(ax, x, y, z, size, size)

def draw_triangle_3d(ax, x, y, z, size):
    height = size * np.sqrt(3) / 2
    vertices = np.array([[x, y, z], [x + size, y, z], [x + size / 2, y + height, z]])
    draw_closed_polygon_3d(ax, vertices)

def draw_polygon_3d(ax, x, y, z, sides, radius):
    points = [[x + radius * np.cos(2 * np.pi * i / sides), y + radius * np.sin(2 * np.pi * i / sides), z] for i in range(sides)]
    draw_closed_polygon_3d(ax, np.array(points))

def draw_text_3d(ax, text, x, y, z):
    ax.text(x, y, z, text, color=settings["fontcolor"])

# --- コマンド解析 (★ここを修正) ---
def parse_command(cmd):
    global fig, ax, settings
    tokens = cmd.strip().split()
    if not tokens: return
    command = tokens[0]

    if ax is None and command != "canvas":
        raise RuntimeError("エラー: 最初のコマンドは 'canvas' である必要があります。")

    if command == "canvas":
        width, height = int(tokens[1]), int(tokens[2])
        fig = plt.figure(figsize=(width / 100, height / 100))
        ax = fig.add_subplot(111, projection='3d')
        ax.set_xlabel("X"); ax.set_ylabel("Y"); ax.set_zlabel("Z")
        ax.set_aspect('equal', adjustable='box')
    # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    # ここに 'sphere' を追加
    elif command in ["cube3d", "pyramid3d", "circle", "ellipse", "square", "triangle", "polygon", "rect", "sphere"]:
    # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
        x, y, z = map(int, tokens[1:4])
        params = {key: int(val) for key, val in (token.split('=') for token in tokens[4:])}
        if command == "cube3d": draw_cube(ax, x, y, z, **params)
        elif command == "pyramid3d": draw_pyramid(ax, x, y, z, **params)
        elif command == "circle": draw_circle_3d(ax, x, y, z, **params)
        elif command == "ellipse": draw_ellipse_3d(ax, x, y, z, **params)
        elif command == "square": draw_square_3d(ax, x, y, z, **params)
        elif command == "triangle": draw_triangle_3d(ax, x, y, z, **params)
        elif command == "polygon": draw_polygon_3d(ax, x, y, z, **params)
        elif command == "rect": draw_rect_3d(ax, x, y, z, **params)
        # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
        # ここに 'sphere' の呼び出しを追加
        elif command == "sphere": draw_sphere(ax, x, y, z, **params)
        # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
    elif command == "line":
        x1, y1, z1, x2, y2, z2 = map(int, tokens[1:7]); draw_line_3d(ax, x1, y1, z1, x2, y2, z2)
    elif command == "text":
        text = tokens[1].strip('"'); x, y, z = map(int, tokens[2:5]); draw_text_3d(ax, text, x, y, z)
    elif command in ["linecolor", "linewidth", "fontcolor"]:
        key = command; value = tokens[1]
        settings[key] = int(value) if key == "linewidth" else value
    elif not command.startswith("#"):
        print(f"警告: コマンド '{command}' は不明なコマンドです。")

# --- スクリプト実行 ---
def run_cad_script(script):
    lines = script.strip().split("\n")
    for i, line in enumerate(lines):
        line_num = i + 1
        line_content = line.strip()
        if not line_content: continue
        try:
            parse_command(line_content)
        except Exception:
            print("─" * 60); print(f"⛔ スクリプトの実行中にエラーが発生しました。処理を中断します。"); print(f"  - 行番号: {line_num}"); print(f"  - 内容  : '{line_content}'"); print("\n--- トレースバック情報 ---"); traceback.print_exc(); print("─" * 60)
            return
    if fig:
        print("✅ 描画が正常に完了しました。ウィンドウを閉じるとプログラムが終了します。")
        plt.show()

# --- main関数 (★サンプルファイルを更新) ---
def main():
    parser = argparse.ArgumentParser(description="Simple 3D CAD interpreter using Matplotlib")
    parser.add_argument("filename", nargs="?", default="example3d.ecad", help="CAD script file")
    args = parser.parse_args()
    if not os.path.exists(args.filename):
        print(f"ファイル '{args.filename}' が見つかりません。サンプルファイルを作成します。")
        # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
        # サンプルファイルに 'sphere' を追加
        sample_script = """canvas 1000 800
# 3D Shapes
linecolor blue
linewidth 2
cube3d -100 -50 0 size=50

linecolor red
pyramid3d -120 50 0 base=40 height=60

# A gold sphere
linecolor gold
linewidth 1
sphere 50 -50 40 radius=30

# --- 2D Shapes ported to 3D ---
linecolor purple
line -100 70 60 0 0 80

linecolor green
square 20 20 20 size=40

linecolor orange
rect 20 -60 0 w=50 h=30

linecolor black
circle -50 -50 70 r=30

linecolor cyan
ellipse -75 -25 -10 w=60 h=20

linecolor magenta
triangle 10 -20 0 size=30

linecolor brown
polygon 60 60 0 sides=6 radius=25

# Text placed in 3D space
fontcolor darkred
text "CAD3D" -20 -80 0
text "Top" -100 70 75
"""
        # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
        with open(args.filename, "w", encoding="utf-8") as f: f.write(sample_script)
        print(f"サンプル '{args.filename}' を作成しました。再度コマンドを実行してください。")
        return

    with open(args.filename, "r", encoding="utf-8") as f: script = f.read()
    run_cad_script(script)

if __name__ == "__main__":
    main()