import sys, math, os, json, shutil, tempfile, hashlib
try: import bpy, mathutils
except: sys.exit(1)

def clear_scene(): bpy.ops.wm.read_factory_settings(use_empty=True)

def create_dummy_objs(t):
    l = os.path.join(t, "asset_library_full"); os.makedirs(l, exist_ok=True)
    with open(os.path.join(l, "ornate_throne.obj"), "w") as f: f.write("v 1.0 -1.0 0.0\nv 1.0 1.0 0.0\nv -1.0 1.0 0.0\nv -1.0 -1.0 0.0\nv 0.0 0.0 2.0\nf 1 2 3 4\nf 1 2 5\nf 2 3 5\nf 3 4 5\nf 4 1 5\n")
    with open(os.path.join(l, "stone_pillar_gothic.obj"), "w") as f: f.write("v 1 0 0\nv 0 1 0\nv -1 0 0\nv 0 -1 0\nv 0 0 3\nf 1 2 3 4\nf 1 2 5\nf 2 3 5\nf 3 4 5\nf 4 1 5\n")
    with open(os.path.join(l, "palace_archway.obj"), "w") as f: f.write("v 0 0 0\nv 1 0 0\nv 0 1 0\nv 0 0 1\nf 1 2 3\nf 1 3 4\nf 1 4 2\nf 2 4 3\n")

def get_bbox(o):
    c = [o.matrix_world @ mathutils.Vector(v) for v in o.bound_box]
    return min(v[0] for v in c), max(v[0] for v in c), min(v[1] for v in c), max(v[1] for v in c), min(v[2] for v in c), max(v[2] for v in c)

def check_overlap(o1, o2):
    m1x, M1x, m1y, M1y, m1z, M1z = get_bbox(o1)
    m2x, M2x, m2y, M2y, m2z, M2z = get_bbox(o2)
    return (m1x < M2x and M1x > m2x) and (m1y < M2y and M1y > m2y) and (m1z < M2z and M1z > m2z)

def sig(o): 
    return hashlib.md5((",".join(f"{v.co.x:.2f},{v.co.y:.2f},{v.co.z:.2f}" for v in o.data.vertices) + "|" + ",".join("-".join(str(i) for i in p.vertices) for p in o.data.polygons)).encode()).hexdigest()

def get_src_sig(f):
    bpy.ops.import_scene.obj(filepath=os.path.join("asset_library_full", f), axis_forward='Y', axis_up='Z')
    o = bpy.context.selected_objects[0]
    s = sig(o)
    mx, Mx, my, My, mz, Mz = get_bbox(o)
    d = (Mx-mx, My-my, Mz-mz)
    bpy.data.objects.remove(o, do_unlink=True)
    return s, d

def run():
    print("=== ALE Task Evaluator: Procedural Asset Assembly ===")
    ts = 0; ag = "generate_kitbash_scene.py"; ob = "pericles_kitbash_scene.blend"
    epsilon = 0.001
    
    if not os.path.exists(ag):
        print(f"[-] CRITICAL FAIL: {ag} missing.")
        sys.exit(1)

    print("\n[*] Pass/Fail Gate: Artifact Generation, Centering & Forbidden Assets")
    clear_scene()
    if not os.path.exists("asset_library_full"): shutil.copytree("../Input_Materials/asset_library_full", "asset_library_full")
    if os.path.exists(ob): os.remove(ob)
    
    st, dt = get_src_sig("ornate_throne.obj")
    sa, da = get_src_sig("palace_archway.obj")
    sp, dp = get_src_sig("stone_pillar_gothic.obj")

    try: exec(compile(open(ag).read(), ag, 'exec'), {'__name__': '__main__', '__file__': os.path.abspath(ag)})
    except Exception as e: print(f"  [-] Error: {e}"); sys.exit(1)
    if not os.path.exists(ob): sys.exit(1)
    
    bpy.ops.wm.open_mainfile(filepath=ob)
    floor = next((o for o in bpy.data.objects if o.type == 'MESH' and o.name == "Floor"), None)
    if not floor: sys.exit(1)
    
    fx, Fx, fy, Fy, fz, Fz = get_bbox(floor)
    if not (math.isclose((fx+Fx)/2, 0, abs_tol=epsilon) and math.isclose((fy+Fy)/2, 0, abs_tol=epsilon) and math.isclose((fz+Fz)/2, 0, abs_tol=epsilon)): 
        print(f"  [-] CRITICAL FAIL: Floor is not perfectly centered on all 3 axes."); sys.exit(1)

    allowed = ["ornate_throne", "palace_archway", "Floor"]
    for o in bpy.data.objects:
        if o.type == 'MESH':
            if o.name not in allowed and not o.name.startswith("stone_pillar_gothic"):
                print(f"  [-] CRITICAL FAIL: Forbidden fabricated or unlisted geometry imported: {o.name}")
                sys.exit(1)

    print("  [+] Pass/Fail Gate passed. Artifact generated, floor centered, no forbidden assets.")

    print("\n[*] Phase 1: Genuine Import, Topology Validation & Exact Naming (20 pts)")
    p1 = 20; g = True
    
    thrones = [o for o in bpy.data.objects if o.name == 'ornate_throne']
    archways = [o for o in bpy.data.objects if o.name == 'palace_archway']
    pillars = [o for o in bpy.data.objects if o.name.startswith('stone_pillar_gothic')]
    
    if len(thrones) != 1: g = False
    elif sig(thrones[0]) != st or not all(math.isclose(thrones[0].dimensions[i], dt[i], abs_tol=epsilon) for i in range(3)): 
        print(f"  [-] Topology/Bounds mismatch for ornate_throne. Genuine import failed."); g = False

    if len(archways) != 1: g = False
    elif sig(archways[0]) != sa or not all(math.isclose(archways[0].dimensions[i], da[i], abs_tol=epsilon) for i in range(3)): 
        print(f"  [-] Topology/Bounds mismatch for palace_archway. Genuine import failed."); g = False

    if len(pillars) != 4: g = False
    else:
        for p in pillars:
            if sig(p) != sp or not all(math.isclose(p.dimensions[i], dp[i], abs_tol=epsilon) for i in range(3)):
                print(f"  [-] Topology/Bounds mismatch for {p.name}. Genuine import failed."); g = False

    if not g: p1 = 0
    else: print("  [+] Genuine OBJ imports, exact names, and strict topology signatures verified.")
    print(f"Phase 1 Score: {p1}/20"); ts += p1

    print("\n[*] Phase 2: Behavioral Data-Parsing Validation (20 pts)")
    p2 = 0
    with tempfile.TemporaryDirectory() as td:
        c = os.getcwd(); os.chdir(td); shutil.copy(os.path.join(c, ag), ag); create_dummy_objs(".")
        with open("design_document.json", "w") as f: json.dump({"required_assets": [{"filename":"ornate_throne.obj","count":1}, {"filename":"stone_pillar_gothic.obj","count":5}]}, f)
        try:
            exec(compile(open(ag).read(), ag, 'exec'), {'__name__': '__main__', '__file__': os.path.abspath(ag)})
            bpy.ops.wm.open_mainfile(filepath=ob)
            if sum(1 for o in bpy.data.objects if o.name.startswith("stone_pillar_gothic")) == 5 and sum(1 for o in bpy.data.objects if o.name == "palace_archway") == 0:
                print("  [+] Negative Test 1 (Fabricated geometries/mutations): Script successfully adapted to Mutation 1."); p2 += 10
        except: pass
        with open("design_document.json", "w") as f: json.dump({"required_assets": [{"filename":"ornate_throne.obj","count":1}, {"filename":"stone_pillar_gothic.obj","count":3}, {"filename":"palace_archway.obj","count":2}]}, f)
        try:
            exec(compile(open(ag).read(), ag, 'exec'), {'__name__': '__main__', '__file__': os.path.abspath(ag)})
            bpy.ops.wm.open_mainfile(filepath=ob)
            if sum(1 for o in bpy.data.objects if o.name.startswith("stone_pillar_gothic")) == 3 and sum(1 for o in bpy.data.objects if o.name.startswith("palace_archway")) == 2:
                print("  [+] Negative Test 2 (Y-offset/counts): Script successfully adapted to Mutation 2."); p2 += 10
        except: pass
        os.chdir(c)
    print(f"Phase 2 Score: {p2}/20"); ts += p2

    print("\n[*] Phase 3: Spatial Symmetry, Containment & Geometry Check (60 pts)")
    p3 = 0; bpy.ops.wm.open_mainfile(filepath=ob)
    floor = next((o for o in bpy.data.objects if o.name == "Floor"), None)
    if floor:
        fx, Fx, fy, Fy, fz, Fz = get_bbox(floor)
        if math.isclose(Fx-fx, 25, abs_tol=epsilon) and math.isclose(Fy-fy, 25, abs_tol=epsilon): print("  [+] Floor bounds are precisely 25x25. (+10)"); p3 += 10
    
    cv = True; ct = True; a = [o for o in bpy.data.objects if o.type == 'MESH' and o != floor]
    for o in a:
        mx, Mx, my, My, mz, Mz = get_bbox(o)
        if not math.isclose(mz, 0, abs_tol=epsilon): cv = False
        if mx < -12.5 or Mx > 12.5 or my < -12.5 or My > 12.5: ct = False
    if cv: print("  [+] All assets have perfect Z=0 floor contact. (+10)"); p3 += 10
    if ct: print("  [+] All assets are strictly contained within the 25x25 floor XY boundary. (+10)"); p3 += 10
    
    ov = False
    for i in range(len(a)):
        for j in range(i+1, len(a)):
            if check_overlap(a[i], a[j]): ov = True
    if not ov and len(a) > 1: print("  [+] No overlapping structures detected among imported assets. (+10)"); p3 += 10
    
    p = [o for o in a if o.name.startswith("stone_pillar_gothic")]
    if len(p) == 4:
        xs, ys = [abs(x.location[0]) for x in p], [abs(y.location[1]) for y in p]
        
        # Test BOTH X and Y coordinates for a rectangle centered at the origin
        x_sym = all(math.isclose(x, max(xs), abs_tol=epsilon) for x in xs) and math.isclose(sum(x.location[0] for x in p), 0, abs_tol=epsilon)
        y_sym = all(math.isclose(y, max(ys), abs_tol=epsilon) for y in ys) and math.isclose(sum(x.location[1] for x in p), 0, abs_tol=epsilon)
        
        if x_sym and y_sym:
            print("  [+] Pillars form a true symmetric 4-corner geometric rectangle on both X and Y axes. (+20)"); p3 += 20
        else:
            print("  [-] Pillar symmetry check failed. (+0)")
    print(f"Phase 3 Score: {p3}/60"); ts += p3

    print("\n========================================")
    print(f"FINAL TOTAL SCORE: {ts}/100")
    sys.exit(0 if ts == 100 else 1)

if __name__ == "__main__": run()
