import serial
import json
import time
import ezdxf
import os

# --- Configuration ---
PORT = 'COM14' 
BAUD = 115200
TINE_PITCH = 3.0  
MAX_TRAVEL = 50.0 
JSON_FILE = "data.json"
LOG_FILE = "scans.json"
DATA_DIR = "scans"

if not os.path.exists(DATA_DIR):
    os.makedirs(DATA_DIR)

try:
    ser = serial.Serial(PORT, BAUD, timeout=1)
    print(f"--- RESURRECT MASTER BRIDGE ACTIVE ---")
    print(f"Connected: {PORT} | Pitch: {TINE_PITCH}mm | Max Travel: {MAX_TRAVEL}mm")
    print(f"Waiting for 'SEND PC' from micro-controller...")
except Exception as e:
    print(f"CRITICAL ERROR: {e}")
    exit()

def update_master_index(new_filename, timestamp):
    history = []
    if os.path.exists(LOG_FILE):
        try:
            with open(LOG_FILE, "r") as f:
                history = json.load(f)
        except:
            history = []
    
    history.insert(0, {"timestamp": timestamp, "file": new_filename})
    
    with open(LOG_FILE, "w") as f:
        json.dump(history, f, indent=2)

def atomic_write(filename, data):
    temp_name = "temp_write.json"
    with open(temp_name, "w") as f:
        json.dump(data, f, indent=2)
        f.flush()
        os.fsync(f.fileno())
    
    if os.path.exists(filename):
        os.remove(filename)
    os.rename(temp_name, filename)

while True:
    line = ser.readline().decode('utf-8', errors='ignore').strip()
    
    if line == "START_DATA":
        print("\n[!] Incoming Scan Sequence Started...")
        display_ts = time.strftime("%Y-%m-%d %H:%M:%S")
        file_ts = time.strftime("%Y%m%d-%H%M%S")
        
        doc = ezdxf.new('R2010')
        msp = doc.modelspace()
        
        session_scans = []
        seen_profiles = set()  # for duplicate detection
        duplicate_count = 0

        while True:
            data = ser.readline().decode('utf-8', errors='ignore').strip()
            
            if data == "END_DATA": 
                break
            
            if data.startswith("SCAN:"):
                try:
                    # Input: "SCAN:0,0.00,45.2,3.00,12.1,6.00,22.4..."
                    content = data.split(":")[1]
                    parts = content.split(",")
                    
                    scan_index = int(parts[0])   # cast to int for Z math
                    coordinate_data = parts[1:]
                    
                    # Convert remaining tokens into (X, Y, Z) tuples
                    # Z is derived from scan_index * TINE_PITCH so each
                    # scan slice sits at a unique depth in the CAD model
                    points = []
                    z = scan_index * TINE_PITCH
                    for i in range(0, len(coordinate_data), 2):
                        if i + 1 < len(coordinate_data):
                            x = float(coordinate_data[i])
                            y = float(coordinate_data[i+1])
                            points.append((x, y, z))
                    
                    if len(points) > 1:
                        # --- Duplicate detection ---
                        profile_key = tuple((p[0], p[1]) for p in points)  # compare XY only
                        if profile_key in seen_profiles:
                            print(f"  > WARNING: Scan {scan_index} is a duplicate of a previous scan — skipping")
                            duplicate_count += 1
                            continue
                        seen_profiles.add(profile_key)

                        # 1. Generate 3D CAD paths (each scan at its own Z depth)
                        for idx in range(len(points) - 1):
                            msp.add_line(points[idx], points[idx+1])
                        
                        # 2. Format point lists for web interfaces (store Z too)
                        formatted_points = [{"x": p[0], "y": p[1], "z": p[2]} for p in points]
                        session_scans.append({
                            "scan_index": str(scan_index),
                            "points": formatted_points
                        })
                        print(f"  > Processed Scan {scan_index} at Z={z:.2f}mm ({len(points)} channels matched)")
                        
                except Exception as e:
                    print(f"  > Parse Error on line '{data}': {e}")

        # --- FINALIZING PACKETS ---
        if session_scans:
            if duplicate_count:
                print(f"  > {duplicate_count} duplicate scan(s) dropped")

            # Save engineered 3D line DXF output
            dxf_path = f"contour_{file_ts}.dxf"
            doc.saveas(dxf_path)
            
            # Save data packages
            scan_json_path = os.path.join(DATA_DIR, f"scan_{file_ts}.json")
            scan_payload = {"timestamp": display_ts, "scans": session_scans}
            
            atomic_write(scan_json_path, scan_payload)
            update_master_index(scan_json_path, display_ts)
            atomic_write(JSON_FILE, scan_payload)

            print(f"--------------------------------")
            print(f"STATUS: SUCCESS")
            print(f"DXF OUT: {dxf_path}")
            print(f"LOG EXPORT: {scan_json_path}")
            print(f"TOTAL SCANS CAPTURED: {len(session_scans)}")
            print(f"--------------------------------")
        else:
            print("WARNING: Data block processed, but no profiles parsed clean.")