"""
STEP to GLB Converter - Ultimate Edition
Single file output with comprehensive metadata and advanced features
"""

import sys
import json
import logging
import hashlib
from pathlib import Path
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime

# FreeCAD import handling
try:
    import FreeCAD
    import Import
    import Mesh
    import Part
except ImportError as e:
    print(f"ERROR: FreeCAD modules not found: {e}")
    print("This script must be run with FreeCAD Python environment")
    sys.exit(1)

# ============================================================================
# CONFIGURATION - Adjust these settings
# ============================================================================

# Input/Output Settings
STEP_FILE = Path("D:\\__sistem__\\ProgramFiles\\FreeCAD\\bin\\freecad.stp")       # Input STEP file path
OUTPUT_FILE = None                       # Output GLB path (None = auto, same name/location)

# Mesh Quality Settings
MESH_TOLERANCE = 0.05           # Lower = more detail (0.01-0.1 recommended)
ANGULAR_DEFLECTION = 0.5        # Angle deviation in degrees
MIN_EDGE_LENGTH = 0.1           # Minimum edge length to process

# Export Options
EXPORT_EDGES = True             # Include edge wireframes
EXPORT_VERTICES = False         # Include vertex points (can be very dense)
EXPORT_HIDDEN_OBJECTS = False   # Include hidden bodies
MERGE_COPLANAR_FACES = False    # Merge adjacent coplanar faces (experimental)

# Color Settings
DEFAULT_COLOR = (0.8, 0.8, 0.8) # RGB for objects without color
EDGE_COLOR = (0.1, 0.1, 0.1)    # RGB for edges
VERTEX_COLOR = (1.0, 0.0, 0.0)  # RGB for vertices
EDGE_THICKNESS = 1.0            # Visual thickness multiplier

# Metadata Options
INCLUDE_BOUNDING_BOX = True     # Calculate and include bounding boxes
INCLUDE_VOLUME = True           # Calculate solid volumes
INCLUDE_HASH = True             # Add geometry hash for change detection
INCLUDE_TIMESTAMPS = True       # Add processing timestamps
INCLUDE_MATERIAL_INFO = True    # Extract material properties if available

# Performance Settings
MAX_OBJECTS_WARNING = 10000     # Warn if exceeding this count
BATCH_SIZE = 100                # Process in batches (memory optimization)
ENABLE_MULTIPROCESSING = False  # Parallel processing (experimental)

# Output Settings
OUTPUT_SUFFIX = ""              # Add suffix to output files (e.g., "_processed")
CREATE_BACKUP = False           # Backup existing GLB files
COMPRESS_OUTPUT = False         # Attempt to optimize GLB size

# Logging
LOG_FILE = None                 # Log file path (None = auto, same folder as input)
LOG_LEVEL = logging.INFO        # DEBUG, INFO, WARNING, ERROR

# ============================================================================
# END CONFIGURATION
# ============================================================================

# Auto-configure log file if not specified
if LOG_FILE is None and STEP_FILE:
    LOG_FILE = STEP_FILE.parent / "conversion.log"

# Setup logging
log_handlers = [logging.StreamHandler(sys.stdout)]
if LOG_FILE:
    try:
        log_handlers.append(logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8'))
    except Exception as e:
        print(f"WARNING: Could not create log file: {e}")

logging.basicConfig(
    level=LOG_LEVEL,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=log_handlers,
    force=True
)
logger = logging.getLogger(__name__)


def calculate_hash(shape) -> str:
    """Calculate geometry hash for change detection"""
    try:
        # Use vertex coordinates for hash
        vertices = []
        for vertex in shape.Vertexes:
            vertices.extend([vertex.X, vertex.Y, vertex.Z])
        vertex_str = ",".join([f"{v:.6f}" for v in sorted(vertices)])
        return hashlib.md5(vertex_str.encode()).hexdigest()[:16]
    except:
        return "unknown"


def get_bounding_box(shape) -> Dict[str, Any]:
    """Calculate bounding box"""
    try:
        bbox = shape.BoundBox
        return {
            "min": [round(bbox.XMin, 6), round(bbox.YMin, 6), round(bbox.ZMin, 6)],
            "max": [round(bbox.XMax, 6), round(bbox.YMax, 6), round(bbox.ZMax, 6)],
            "center": [round(bbox.Center.x, 6), round(bbox.Center.y, 6), round(bbox.Center.z, 6)],
            "diagonal": round(bbox.DiagonalLength, 6)
        }
    except:
        return {}


def get_volume(shape) -> Optional[float]:
    """Calculate solid volume"""
    try:
        if hasattr(shape, 'Volume'):
            return round(shape.Volume, 6)
    except:
        pass
    return None


def get_material_info(body) -> Dict[str, Any]:
    """Extract material properties"""
    material_info = {}
    try:
        if hasattr(body, 'Material') and body.Material:
            material_info['material'] = body.Material
        if hasattr(body, 'ViewObject'):
            vo = body.ViewObject
            if hasattr(vo, 'ShapeMaterial'):
                material_info['shape_material'] = vo.ShapeMaterial
            if hasattr(vo, 'LineColor'):
                material_info['line_color'] = list(vo.LineColor)[:3]
            if hasattr(vo, 'LineWidth'):
                material_info['line_width'] = vo.LineWidth
    except:
        pass
    return material_info


def get_color_from_body(body, face_index: int) -> Tuple[float, float, float]:
    """Extract color with fallback handling"""
    try:
        if hasattr(body, 'ViewObject') and hasattr(body.ViewObject, 'DiffuseColor'):
            colors = body.ViewObject.DiffuseColor
            if colors:
                return colors[face_index % len(colors)]
    except (AttributeError, IndexError):
        pass
    return DEFAULT_COLOR


def get_transparency(body) -> float:
    """Extract transparency with fallback"""
    try:
        if hasattr(body, 'ViewObject'):
            transparency = getattr(body.ViewObject, 'Transparency', 0)
            return 1.0 - (transparency / 100.0)
    except AttributeError:
        pass
    return 1.0


def get_face_properties(face) -> Dict[str, Any]:
    """Extract comprehensive face properties"""
    props = {}
    
    try:
        # Basic properties
        props['area'] = round(face.Area, 6)
        props['surface_type'] = face.Surface.TypeId if hasattr(face, 'Surface') else "Unknown"
        
        # Geometric center and normal
        try:
            props['center'] = [round(v, 6) for v in face.CenterOfMass]
            props['normal'] = [round(v, 6) for v in face.normalAt(0, 0)]
        except:
            pass
        
        # Surface-specific properties
        surface = face.Surface
        if hasattr(surface, 'Radius'):
            props['radius'] = round(surface.Radius, 6)
        if hasattr(surface, 'Position'):
            props['position'] = [round(v, 6) for v in surface.Position]
        if hasattr(surface, 'Axis'):
            props['axis'] = [round(v, 6) for v in surface.Axis]
        
        # Curvature info
        try:
            u, v = face.ParameterRange
            curvature = face.curvatureAt(u[0], v[0])
            if curvature:
                props['curvature'] = round(abs(curvature), 6)
        except:
            pass
        
        # Topology
        props['edge_count'] = len(face.Edges)
        props['vertex_count'] = len(face.Vertexes)
        props['is_planar'] = face.Surface.isPlanar() if hasattr(face.Surface, 'isPlanar') else None
        
    except Exception as e:
        logger.debug(f"Failed to extract some face properties: {e}")
    
    return props


def get_edge_properties(edge) -> Dict[str, Any]:
    """Extract comprehensive edge properties"""
    props = {}
    
    try:
        # Basic properties
        props['length'] = round(edge.Length, 6)
        props['edge_type'] = edge.Curve.TypeId if hasattr(edge, 'Curve') else "Unknown"
        
        # Curve-specific properties
        curve = edge.Curve
        if hasattr(curve, 'Radius'):
            props['radius'] = round(curve.Radius, 6)
        if hasattr(curve, 'Center'):
            props['center'] = [round(v, 6) for v in curve.Center]
        if hasattr(curve, 'Axis'):
            props['axis'] = [round(v, 6) for v in curve.Axis]
        
        # Start/End points
        try:
            props['start_point'] = [round(v, 6) for v in edge.Vertexes[0].Point]
            props['end_point'] = [round(v, 6) for v in edge.Vertexes[-1].Point]
        except:
            pass
        
        # Continuity
        props['is_closed'] = edge.Closed
        props['is_degenerated'] = edge.isNull()
        
    except Exception as e:
        logger.debug(f"Failed to extract some edge properties: {e}")
    
    return props


def create_face_mesh(face, body, body_index: int, face_index: int, body_label: str) -> Optional[Mesh.Mesh]:
    """Create mesh from face with comprehensive embedded metadata"""
    try:
        mesh = Mesh.Mesh(face.tessellate(MESH_TOLERANCE))
        
        if mesh.CountFacets == 0:
            return None
        
        # Get color and transparency
        color = get_color_from_body(body, face_index)
        alpha = get_transparency(body)
        
        # Apply vertex colors
        colors = [(color[0], color[1], color[2], alpha)] * len(mesh.Facets)
        mesh.VertexColors = colors
        
        # Get comprehensive face properties
        face_props = get_face_properties(face)
        
        # Build metadata
        metadata = {
            "group_id": body_index + 1,
            "face_index": face_index,
            "body_label": body_label,
            "original_label": getattr(body, 'Label', ''),
            "type": "Face",
            "color": [round(c, 3) for c in color],
            "alpha": round(alpha, 3),
            **face_props
        }
        
        # Add bounding box if enabled
        if INCLUDE_BOUNDING_BOX:
            metadata['bbox'] = get_bounding_box(face)
        
        # Add hash if enabled
        if INCLUDE_HASH:
            metadata['hash'] = calculate_hash(face)
        
        # Create compact name for GLB
        surface_short = face_props.get('surface_type', 'Unknown').split('::')[-1]
        mesh.Label = (
            f"B{body_index + 1}_F{face_index}__"
            f"{body_label}__"
            f"T_Face__"
            f"ST_{surface_short}__"
            f"A_{face_props.get('area', 0)}"
        )
        
        # Store full metadata in Label2
        mesh.Label2 = json.dumps(metadata, separators=(',', ':'))
        
        return mesh
        
    except Exception as e:
        logger.warning(f"Failed to create face mesh: {e}")
        return None


def create_edge_mesh(edge, body, body_index: int, face_index: int, edge_index: int, body_label: str) -> Optional[Mesh.Mesh]:
    """Create wireframe mesh from edge with comprehensive metadata"""
    try:
        edge_mesh = Mesh.Mesh(edge.tessellate(MESH_TOLERANCE))
        
        if edge_mesh.CountFacets == 0 or edge.Length < MIN_EDGE_LENGTH:
            return None
        
        # Apply edge color
        edge_color = [(EDGE_COLOR[0], EDGE_COLOR[1], EDGE_COLOR[2], 1.0)] * len(edge_mesh.Facets)
        edge_mesh.VertexColors = edge_color
        
        # Get comprehensive edge properties
        edge_props = get_edge_properties(edge)
        
        # Build metadata
        metadata = {
            "group_id": body_index + 1,
            "face_index": face_index,
            "edge_index": edge_index,
            "body_label": body_label,
            "original_label": getattr(body, 'Label', ''),
            "type": "Edge",
            **edge_props
        }
        
        # Add bounding box if enabled
        if INCLUDE_BOUNDING_BOX:
            metadata['bbox'] = get_bounding_box(edge)
        
        # Create compact name
        edge_type_short = edge_props.get('edge_type', 'Unknown').split('::')[-1]
        radius_str = f"_R_{edge_props['radius']}" if 'radius' in edge_props else ""
        
        edge_mesh.Label = (
            f"B{body_index + 1}_F{face_index}_E{edge_index}__"
            f"{body_label}__"
            f"T_Edge__"
            f"ET_{edge_type_short}__"
            f"L_{edge_props.get('length', 0)}{radius_str}"
        )
        
        edge_mesh.Label2 = json.dumps(metadata, separators=(',', ':'))
        
        return edge_mesh
        
    except Exception as e:
        logger.debug(f"Failed to create edge mesh: {e}")
        return None


def create_vertex_mesh(vertex, body, body_index: int, vertex_index: int, body_label: str) -> Optional[Mesh.Mesh]:
    """Create point mesh from vertex"""
    try:
        # Create a small sphere at vertex position
        point = vertex.Point
        sphere = Part.makeSphere(MESH_TOLERANCE * 2, point)
        vertex_mesh = Mesh.Mesh(sphere.tessellate(MESH_TOLERANCE))
        
        # Apply vertex color
        v_color = [(VERTEX_COLOR[0], VERTEX_COLOR[1], VERTEX_COLOR[2], 1.0)] * len(vertex_mesh.Facets)
        vertex_mesh.VertexColors = v_color
        
        metadata = {
            "group_id": body_index + 1,
            "vertex_index": vertex_index,
            "body_label": body_label,
            "type": "Vertex",
            "position": [round(point.x, 6), round(point.y, 6), round(point.z, 6)]
        }
        
        vertex_mesh.Label = f"B{body_index + 1}_V{vertex_index}__{body_label}__T_Vertex"
        vertex_mesh.Label2 = json.dumps(metadata, separators=(',', ':'))
        
        return vertex_mesh
        
    except Exception as e:
        logger.debug(f"Failed to create vertex mesh: {e}")
        return None


def process_step_file(step_path: Path, output_path: Optional[Path] = None) -> bool:
    """Process single STEP file to GLB with comprehensive metadata"""
    
    # Determine output path
    if output_path is None:
        # Auto: same name, same location
        output_name = step_path.stem + OUTPUT_SUFFIX + ".glb"
        glb_file = step_path.parent / output_name
    else:
        glb_file = output_path
        # Create parent directory if it doesn't exist
        glb_file.parent.mkdir(parents=True, exist_ok=True)
    
    logger.info(f"Target output: {glb_file}")
    
    # Backup existing file if enabled
    if CREATE_BACKUP and glb_file.exists():
        backup_file = glb_file.with_suffix('.glb.backup')
        try:
            glb_file.rename(backup_file)
            logger.info(f"Backed up existing file to {backup_file.name}")
        except Exception as e:
            logger.warning(f"Could not create backup: {e}")
    
    doc = None
    
    try:
        logger.info(f"Step 1/6: Opening STEP file: {step_path}")
        start_time = datetime.now()
        
        # Try to open the file
        try:
            Import.open(str(step_path))
        except Exception as e:
            logger.error(f"Failed to open STEP file: {e}")
            raise
        
        logger.info("Step 2/6: Accessing document...")
        doc = FreeCAD.ActiveDocument
        
        if not doc:
            raise RuntimeError("Failed to open document - ActiveDocument is None")
        
        logger.info(f"Document opened: {doc.Name}")
        logger.info(f"Objects in document: {len(doc.Objects)}")
        
        if len(doc.Objects) == 0:
            raise RuntimeError("Document contains no objects")
        
        objs_to_export = []
        
        # Global metadata summary
        metadata_summary = {
            "source_file": step_path.name,
            "conversion_date": datetime.now().isoformat() if INCLUDE_TIMESTAMPS else None,
            "freecad_version": FreeCAD.Version(),
            "config": {
                "mesh_tolerance": MESH_TOLERANCE,
                "angular_deflection": ANGULAR_DEFLECTION,
                "export_edges": EXPORT_EDGES,
                "export_vertices": EXPORT_VERTICES
            },
            "bodies": [],
            "total_faces": 0,
            "total_edges": 0,
            "total_vertices": 0
        }
        
        logger.info("Step 3/6: Processing bodies...")
        
        # Process each body
        body_count = 0
        for body_index, body in enumerate(doc.Objects):
            logger.debug(f"Checking object {body_index}: {getattr(body, 'Label', 'Unknown')} - Type: {type(body).__name__}")
            
            if not hasattr(body, "Shape"):
                logger.debug(f"  -> No Shape attribute, skipping")
                continue
                
            if not body.Shape:
                logger.debug(f"  -> Shape is None/empty, skipping")
                continue
            
            body_count += 1
            
            # Check visibility
            if not EXPORT_HIDDEN_OBJECTS:
                try:
                    if hasattr(body, 'ViewObject') and not body.ViewObject.Visibility:
                        logger.debug(f"Skipping hidden body: {body.Label}")
                        continue
                except:
                    pass
            
            body_label = getattr(body, 'Label', f'Body{body_index}')
            logger.info(f"Processing body {body_count}/{body_index + 1}: {body_label}")
            
            body_info = {
                "body_id": body_index + 1,
                "label": body_label,
                "face_count": len(body.Shape.Faces),
                "edge_count": len(body.Shape.Edges),
                "vertex_count": len(body.Shape.Vertexes)
            }
            
            logger.info(f"  Faces: {len(body.Shape.Faces)}, Edges: {len(body.Shape.Edges)}, Vertices: {len(body.Shape.Vertexes)}")
            
            # Add volume if enabled
            if INCLUDE_VOLUME:
                volume = get_volume(body.Shape)
                if volume:
                    body_info['volume'] = volume
            
            # Add bounding box if enabled
            if INCLUDE_BOUNDING_BOX:
                body_info['bbox'] = get_bounding_box(body.Shape)
            
            # Add material info if enabled
            if INCLUDE_MATERIAL_INFO:
                material = get_material_info(body)
                if material:
                    body_info['material'] = material
            
            # Add hash if enabled
            if INCLUDE_HASH:
                body_info['hash'] = calculate_hash(body.Shape)
            
            # Process faces
            face_count = 0
            for face_index, face in enumerate(body.Shape.Faces):
                face_mesh = create_face_mesh(face, body, body_index, face_index, body_label)
                if face_mesh:
                    objs_to_export.append(face_mesh)
                    metadata_summary["total_faces"] += 1
                    face_count += 1
                
                # Process edges if enabled
                if EXPORT_EDGES:
                    for edge_index, edge in enumerate(face.Edges):
                        edge_mesh = create_edge_mesh(edge, body, body_index, face_index, edge_index, body_label)
                        if edge_mesh:
                            objs_to_export.append(edge_mesh)
                            metadata_summary["total_edges"] += 1
            
            logger.info(f"  -> Created {face_count} face meshes")
            
            # Process vertices if enabled
            if EXPORT_VERTICES:
                vertex_count = 0
                for vertex_index, vertex in enumerate(body.Shape.Vertexes):
                    vertex_mesh = create_vertex_mesh(vertex, body, body_index, vertex_index, body_label)
                    if vertex_mesh:
                        objs_to_export.append(vertex_mesh)
                        metadata_summary["total_vertices"] += 1
                        vertex_count += 1
                logger.info(f"  -> Created {vertex_count} vertex meshes")
            
            metadata_summary["bodies"].append(body_info)
        
        logger.info(f"Step 4/6: Processed {body_count} bodies with Shape")
        logger.info(f"Total meshes created: {len(objs_to_export)}")
        
        if len(objs_to_export) == 0:
            logger.error("No objects to export - no valid geometry found")
            logger.error("Possible reasons:")
            logger.error("  - STEP file contains no solid geometry")
            logger.error("  - All objects are hidden (set EXPORT_HIDDEN_OBJECTS=True)")
            logger.error("  - Tessellation failed for all faces")
            return False
        
        if len(objs_to_export) > MAX_OBJECTS_WARNING:
            logger.warning(f"Large export: {len(objs_to_export)} objects - may be slow")
        
        # Add processing time
        if INCLUDE_TIMESTAMPS:
            metadata_summary["processing_time_seconds"] = (datetime.now() - start_time).total_seconds()
        
        logger.info("Step 5/6: Creating metadata mesh...")
        
        # Create metadata mesh as first item
        try:
            meta_mesh = Mesh.Mesh()
            meta_mesh.Label = "__METADATA__"
            meta_mesh.Label2 = json.dumps(metadata_summary, separators=(',', ':'))
            objs_to_export.insert(0, meta_mesh)
            logger.info("Metadata mesh created successfully")
        except Exception as e:
            logger.warning(f"Could not create metadata mesh: {e}")
        
        # Export to GLB
        logger.info(f"Step 6/6: Exporting {len(objs_to_export)} objects to GLB...")
        logger.info(f"Output path: {glb_file}")
        
        try:
            Import.export(objs_to_export, str(glb_file))
            logger.info("Export completed successfully")
        except Exception as e:
            logger.error(f"Export failed: {e}")
            raise
        
        # Verify output file exists
        if not glb_file.exists():
            raise RuntimeError(f"GLB file was not created: {glb_file}")
        
        file_size = glb_file.stat().st_size
        logger.info(f"Output file size: {file_size / 1024:.2f} KB")
        
        # Log summary
        logger.info(f"✅ Completed: {glb_file.name}")
        logger.info(f"   ├─ Bodies: {len(metadata_summary['bodies'])}")
        logger.info(f"   ├─ Faces: {metadata_summary['total_faces']}")
        logger.info(f"   ├─ Edges: {metadata_summary['total_edges']}")
        if EXPORT_VERTICES:
            logger.info(f"   ├─ Vertices: {metadata_summary['total_vertices']}")
        if INCLUDE_TIMESTAMPS:
            logger.info(f"   └─ Time: {metadata_summary['processing_time_seconds']:.2f}s")
        
        return True
        
    except Exception as e:
        logger.error(f"❌ Failed: {step_path.name}")
        logger.error(f"Error details: {e}", exc_info=True)
        return False
        
    finally:
        if doc:
            try:
                logger.info("Cleaning up document...")
                FreeCAD.closeDocument(doc.Name)
            except Exception as e:
                logger.warning(f"Could not close document: {e}")


def main():
    """Main execution"""
    print("=" * 70)
    print("STEP to GLB Converter - Ultimate Edition")
    print("=" * 70)
    
    # Ensure logging is working
    logger.info("Logger initialized successfully")
    logger.info("=" * 70)
    logger.info("STEP to GLB Converter - Ultimate Edition")
    logger.info("=" * 70)
    
    # Validate input file
    logger.info(f"Checking input file: {STEP_FILE}")
    
    if not STEP_FILE.exists():
        logger.error(f"Input file not found: {STEP_FILE}")
        logger.error(f"Absolute path checked: {STEP_FILE.absolute()}")
        sys.exit(1)
    
    if STEP_FILE.suffix.lower() not in ['.stp', '.step']:
        logger.error(f"Invalid file type: {STEP_FILE.suffix}. Expected .stp or .step")
        sys.exit(1)
    
    logger.info(f"Input file found: {STEP_FILE.name} ({STEP_FILE.stat().st_size} bytes)")
    
    # Determine output file
    output_file = OUTPUT_FILE
    if output_file:
        logger.info(f"Input: {STEP_FILE}")
        logger.info(f"Output: {output_file}")
    else:
        output_file = STEP_FILE.parent / (STEP_FILE.stem + OUTPUT_SUFFIX + ".glb")
        logger.info(f"File: {STEP_FILE}")
        logger.info(f"Output: {output_file.name} (auto)")
    
    # Log configuration
    logger.info(f"\nConfiguration:")
    logger.info(f"  ├─ Mesh tolerance: {MESH_TOLERANCE}")
    logger.info(f"  ├─ Angular deflection: {ANGULAR_DEFLECTION}")
    logger.info(f"  ├─ Export edges: {EXPORT_EDGES}")
    logger.info(f"  ├─ Export vertices: {EXPORT_VERTICES}")
    logger.info(f"  ├─ Export hidden: {EXPORT_HIDDEN_OBJECTS}")
    logger.info(f"  ├─ Include bounding boxes: {INCLUDE_BOUNDING_BOX}")
    logger.info(f"  ├─ Include volumes: {INCLUDE_VOLUME}")
    logger.info(f"  ├─ Include hashes: {INCLUDE_HASH}")
    logger.info(f"  └─ Min edge length: {MIN_EDGE_LENGTH}")
    
    # Check FreeCAD version
    logger.info(f"\nFreeCAD Version: {FreeCAD.Version()}")
    
    # Process file
    logger.info(f"\n{'='*70}")
    logger.info("Starting conversion...")
    logger.info(f"{'='*70}")
    
    success = process_step_file(STEP_FILE, output_file)
    
    # Final summary
    logger.info("\n" + "=" * 70)
    if success:
        logger.info(f"✅ Conversion Successful")
        logger.info(f"  ├─ Output: {output_file}")
        if LOG_FILE:
            logger.info(f"  └─ Log: {LOG_FILE}")
    else:
        logger.info(f"❌ Conversion Failed")
        if LOG_FILE:
            logger.info(f"  └─ Check log: {LOG_FILE}")
    logger.info("=" * 70)
    
    return 0 if success else 1


if __name__ == "__main__":
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        print("\n\nInterrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"\n\nFATAL ERROR: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)