import os
import math
import struct
import numpy as np
import argparse
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import sys
import traceback
import ezdxf
try:
    from ezdxf.math import Vec3
except ImportError:
    # Older versions may not have this
    Vec3 = None
import csv

VERSION = "1.5.1"

class TrackPoint:
    def __init__(self, x=0, y=0, z=0, dx=0, dy=0, dz=0, turn_brake=0, placeholder=0):
        self.x = x
        self.y = y  
        self.z = z
        self.dx = dx
        self.dy = dy
        self.dz = dz
        self.turn_brake = turn_brake
        self.placeholder = placeholder
        
    def calculate_speed_and_turn(self):
        """Calculate speed and turn values from the encoded turn_brake parameter"""
        # Handle special case of gentle turn at full speed
        if -2.0 <= self.turn_brake <= 1.0:
            return 1.0, self.turn_brake
        
        # Regular calculation
        turn_strength = self.turn_brake % 1.0
        if abs(turn_strength) < 0.001:  # Handle small floating point errors
            turn_strength = 0.0
            
        max_speed = 1.0 - (self.turn_brake - turn_strength) / 40.0
        
        # Validate and clamp
        max_speed = max(0.2, min(1.0, max_speed))
        turn_strength = max(-2.0, min(1.0, turn_strength))
        
        return max_speed, turn_strength
    
    def set_speed_and_turn(self, max_speed, turn_strength):
        """Set the turn_brake parameter based on speed and turn values"""
        # Validate inputs
        max_speed = max(0.2, min(1.0, max_speed))
        turn_strength = max(-2.0, min(1.0, turn_strength))
            
        # Calculate combined value
        self.turn_brake = 40.0 * (1.0 - max_speed) + turn_strength
        
    def __str__(self):
        max_speed, turn_strength = self.calculate_speed_and_turn()
        return (f"Point({self.x:.2f}, {self.y:.2f}, {self.z:.2f}) - "
                f"Direction({self.dx:.2f}, {self.dy:.2f}, {self.dz:.2f}) - "
                f"Speed: {max_speed:.2f}, Turn: {turn_strength:.2f}")


class TrackData:
    def __init__(self):
        self.inner_line = []  # List of TrackPoint objects for inner racing line
        self.outer_line = []  # List of TrackPoint objects for outer racing line
        self.points_per_line = 0
        self.debug = False
        
    def set_debug(self, debug):
        self.debug = debug
        
    def log_debug(self, message):
        if self.debug:
            print(f"[DEBUG] {message}")
            
    def load_from_csv(self, filename, debug=False):
        """Load track from CSV file with dual racing lines"""
        self.set_debug(debug)
        
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                lines = f.readlines()
                
            if not lines:
                self.log_debug("Empty CSV file")
                return False
            
            # First line should contain the number of points per racing line
            try:
                self.points_per_line = int(lines[0].strip())
                self.log_debug(f"Points per line: {self.points_per_line}")
            except ValueError:
                self.log_debug("First line is not a valid point count, trying to parse as data")
                self.points_per_line = 0
            
            # Clear existing data
            self.inner_line = []
            self.outer_line = []
            
            # If first line was the point count, start from second line
            start_idx = 1 if self.points_per_line > 0 else 0
            
            # If no point count was provided, try to determine from data
            if self.points_per_line == 0:
                self.points_per_line = len(lines) // 2
                self.log_debug(f"Estimated points per line: {self.points_per_line}")
            
            # Parse inner line
            for i in range(start_idx, start_idx + self.points_per_line):
                if i >= len(lines):
                    break
                    
                line = lines[i].strip()
                if not line:
                    continue
                    
                values = [float(v.strip()) for v in line.split(',')]
                if len(values) >= 7:
                    point = TrackPoint(
                        x=values[0], y=values[1], z=values[2],
                        dx=values[3], dy=values[4], dz=values[5],
                        turn_brake=values[6],
                        placeholder=values[7] if len(values) > 7 else 0
                    )
                    self.inner_line.append(point)
            
            # Parse outer line
            outer_start = start_idx + self.points_per_line
            for i in range(outer_start, outer_start + self.points_per_line):
                if i >= len(lines):
                    break
                    
                line = lines[i].strip()
                if not line:
                    continue
                    
                values = [float(v.strip()) for v in line.split(',')]
                if len(values) >= 7:
                    point = TrackPoint(
                        x=values[0], y=values[1], z=values[2],
                        dx=values[3], dy=values[4], dz=values[5],
                        turn_brake=values[6],
                        placeholder=values[7] if len(values) > 7 else 0
                    )
                    self.outer_line.append(point)
            
            self.log_debug(f"Loaded inner line: {len(self.inner_line)} points")
            self.log_debug(f"Loaded outer line: {len(self.outer_line)} points")
            
            # Update points_per_line if it was estimated incorrectly
            if len(self.inner_line) != self.points_per_line and len(self.inner_line) > 0:
                self.points_per_line = len(self.inner_line)
                self.log_debug(f"Updated points per line to: {self.points_per_line}")
            
            return True
            
        except Exception as e:
            print(f"Error loading CSV file: {str(e)}")
            traceback.print_exc()
            return False
            
    def save_to_csv(self, filename):
        """Save track to CSV file with dual racing lines"""
        try:
            with open(filename, 'w', encoding='utf-8', newline='') as f:
                # Write the number of points per line
                f.write(f"{self.points_per_line}\n")
                
                # Write inner line points
                for point in self.inner_line:
                    line = f"{point.x}, {point.y}, {point.z}, {point.dx}, {point.dy}, {point.dz}, {point.turn_brake}, {point.placeholder}\n"
                    f.write(line)
                
                # Write outer line points
                for point in self.outer_line:
                    line = f"{point.x}, {point.y}, {point.z}, {point.dx}, {point.dy}, {point.dz}, {point.turn_brake}, {point.placeholder}\n"
                    f.write(line)
            
            print(f"Saved track to {filename} ({self.points_per_line} points per line)")
            return True
        except Exception as e:
            print(f"Error saving CSV file: {str(e)}")
            traceback.print_exc()
            return False
    
    def export_to_dxf(self, filename):
        """Export track to DXF with both racing lines and metadata"""
        try:
            # Create a new DXF document
            doc = ezdxf.new('R2010')  # Using R2010 format for good compatibility
            msp = doc.modelspace()
            
            # Create layers for each racing line - FIXED VERSION to handle older ezdxf versions
            # First create layers without color
            inner_layer_name = 'INNER_LINE'
            outer_layer_name = 'OUTER_LINE'
            
            inner_layer = doc.layers.new(name=inner_layer_name)
            outer_layer = doc.layers.new(name=outer_layer_name)
            
            # Then set colors separately
            try:
                inner_layer.color = 5  # Blue
                outer_layer.color = 1  # Red
            except:
                self.log_debug("Could not set layer colors (older ezdxf version)")
            
            # Create a custom extended data registry to store racing parameters
            try:
                doc.appids.new('RACING_PARAMS')
            except:
                self.log_debug("Could not create appid - parameters may not be preserved")
            
            # Add inner racing line
            if self.inner_line:
                # Create polyline for inner line
                # Using only 2D coordinates to ensure compatibility
                vertices = [(p.x, p.z) for p in self.inner_line]  # Using Z as Y for visualization
                
                try:
                    polyline = msp.add_lwpolyline(vertices, dxfattribs={'layer': inner_layer_name, 'closed': True})
                except TypeError:
                    # Older versions may not support 'closed' parameter
                    polyline = msp.add_lwpolyline(vertices, dxfattribs={'layer': inner_layer_name})
                    # Try to close the polyline after creation
                    try:
                        polyline.close(True)
                    except:
                        self.log_debug("Could not close polyline (older ezdxf version)")
                
                # Add individual point entities with metadata
                for i, point in enumerate(self.inner_line):
                    try:
                        # Create a point entity
                        point_entity = msp.add_point((point.x, point.z), dxfattribs={'layer': inner_layer_name})
                        
                        # Store point data as plain text since xdata might not be supported
                        point_text = f"INNER_POINT_{i}:{point.x},{point.y},{point.z},{point.dx},{point.dy},{point.dz},{point.turn_brake},{point.placeholder}"
                        msp.add_text(
                            point_text,
                            dxfattribs={
                                'layer': inner_layer_name,
                                'height': 0.1,  # Very small text, not meant to be read visually
                                'color': 5
                            }
                        ).set_pos((point.x, point.z), align='LEFT')
                        
                        # Add point number as visible text
                        if i % 5 == 0:
                            msp.add_text(
                                f"{i+1}",
                                dxfattribs={
                                    'layer': inner_layer_name,
                                    'height': 5
                                }
                            ).set_pos((point.x, point.z), align='CENTER')
                    except Exception as e:
                        self.log_debug(f"Error adding inner point {i}: {str(e)}")
            
            # Add outer racing line
            if self.outer_line:
                # Create polyline for outer line (2D for compatibility)
                vertices = [(p.x, p.z) for p in self.outer_line]
                
                try:
                    polyline = msp.add_lwpolyline(vertices, dxfattribs={'layer': outer_layer_name, 'closed': True})
                except TypeError:
                    # Older versions may not support 'closed' parameter
                    polyline = msp.add_lwpolyline(vertices, dxfattribs={'layer': outer_layer_name})
                    # Try to close the polyline after creation
                    try:
                        polyline.close(True)
                    except:
                        self.log_debug("Could not close polyline (older ezdxf version)")
                
                # Add individual point entities with metadata
                for i, point in enumerate(self.outer_line):
                    try:
                        # Create a point entity
                        point_entity = msp.add_point((point.x, point.z), dxfattribs={'layer': outer_layer_name})
                        
                        # Store point data as plain text
                        point_text = f"OUTER_POINT_{i}:{point.x},{point.y},{point.z},{point.dx},{point.dy},{point.dz},{point.turn_brake},{point.placeholder}"
                        msp.add_text(
                            point_text,
                            dxfattribs={
                                'layer': outer_layer_name,
                                'height': 0.1,  # Very small text
                                'color': 1
                            }
                        ).set_pos((point.x, point.z), align='LEFT')
                        
                        # Add point number as visible text
                        if i % 5 == 0:
                            msp.add_text(
                                f"{i+1}",
                                dxfattribs={
                                    'layer': outer_layer_name,
                                    'height': 5
                                }
                            ).set_pos((point.x, point.z), align='CENTER')
                    except Exception as e:
                        self.log_debug(f"Error adding outer point {i}: {str(e)}")
            
            # Add metadata about the points per line
            msp.add_text(
                f"POINTS_PER_LINE:{self.points_per_line}",
                dxfattribs={
                    'layer': '0',
                    'height': 1
                }
            ).set_pos((0, -10), align='LEFT')
            
            # Save the DXF file
            doc.saveas(filename)
            print(f"Exported track to {filename}")
            return True
        except Exception as e:
            print(f"Error exporting DXF file: {str(e)}")
            traceback.print_exc()
            return False
    
    def export_separate_dxf(self, inner_filename, outer_filename):
        """Export inner and outer racing lines to separate DXF files"""
        success_inner = self._export_line_to_dxf(inner_filename, self.inner_line, "INNER_LINE", True)
        success_outer = self._export_line_to_dxf(outer_filename, self.outer_line, "OUTER_LINE", False)
        
        return success_inner and success_outer
    
    def _export_line_to_dxf(self, filename, points, layer_name, is_inner):
        """Export a single racing line to DXF file"""
        try:
            # Create a new DXF document
            doc = ezdxf.new('R2010')
            msp = doc.modelspace()
            
            # Create layer - compatible with older ezdxf versions
            layer = doc.layers.new(name=layer_name)
            
            # Set color if supported
            try:
                layer.color = 5 if is_inner else 1  # Blue for inner, Red for outer
            except:
                self.log_debug("Could not set layer color (older ezdxf version)")
            
            # Add racing line if points exist
            if points:
                # Create polyline (2D for compatibility)
                vertices = [(p.x, p.z) for p in points]
                
                try:
                    polyline = msp.add_lwpolyline(vertices, dxfattribs={'layer': layer_name, 'closed': True})
                except TypeError:
                    # Older versions may not support 'closed' parameter
                    polyline = msp.add_lwpolyline(vertices, dxfattribs={'layer': layer_name})
                    # Try to close the polyline after creation
                    try:
                        polyline.close(True)
                    except:
                        self.log_debug("Could not close polyline (older ezdxf version)")
                
                # Add individual point entities with metadata
                for i, point in enumerate(points):
                    try:
                        # Create a point entity
                        point_entity = msp.add_point((point.x, point.z), dxfattribs={'layer': layer_name})
                        
                        # Store point data as text
                        point_prefix = "INNER_POINT_" if is_inner else "OUTER_POINT_"
                        point_text = f"{point_prefix}{i}:{point.x},{point.y},{point.z},{point.dx},{point.dy},{point.dz},{point.turn_brake},{point.placeholder}"
                        msp.add_text(
                            point_text,
                            dxfattribs={
                                'layer': layer_name,
                                'height': 0.1,  # Very small text
                                'color': 5 if is_inner else 1
                            }
                        ).set_pos((point.x, point.z), align='LEFT')
                        
                        # Add point number as visible text
                        if i % 5 == 0:
                            msp.add_text(
                                f"{i+1}",
                                dxfattribs={
                                    'layer': layer_name,
                                    'height': 5
                                }
                            ).set_pos((point.x, point.z), align='CENTER')
                    except Exception as e:
                        self.log_debug(f"Error adding point {i}: {str(e)}")
            
            # Add metadata about the line type
            msp.add_text(
                f"{'INNER' if is_inner else 'OUTER'}_LINE:{len(points)}",
                dxfattribs={
                    'layer': '0',
                    'height': 1
                }
            ).set_pos((0, -10), align='LEFT')
            
            # Save the DXF file
            doc.saveas(filename)
            print(f"Exported {'inner' if is_inner else 'outer'} line to {filename}")
            return True
        except Exception as e:
            print(f"Error exporting line to DXF file: {str(e)}")
            traceback.print_exc()
            return False
    
    def import_from_dxf(self, filename):
        """Import track from DXF file with both racing lines"""
        try:
            # Clear existing data
            self.inner_line = []
            self.outer_line = []
            
            # Load the DXF file
            doc = ezdxf.readfile(filename)
            msp = doc.modelspace()
            
            # Check for points_per_line metadata text
            for text in msp.query('TEXT'):
                text_value = text.dxf.text
                if 'POINTS_PER_LINE:' in text_value:
                    try:
                        self.points_per_line = int(text_value.split(':')[1].strip())
                        self.log_debug(f"Found points_per_line in DXF: {self.points_per_line}")
                    except:
                        self.log_debug("Could not parse points_per_line from DXF")
            
            # Read metadata from text entities
            inner_points_data = {}
            outer_points_data = {}
            
            for text in msp.query('TEXT'):
                text_value = text.dxf.text
                
                if text_value.startswith('INNER_POINT_'):
                    try:
                        # Parse index and data
                        parts = text_value.split(':')
                        index = int(parts[0].replace('INNER_POINT_', ''))
                        data_values = parts[1].split(',')
                        
                        # Create point
                        if len(data_values) >= 8:
                            point = TrackPoint(
                                x=float(data_values[0]), 
                                y=float(data_values[1]), 
                                z=float(data_values[2]),
                                dx=float(data_values[3]), 
                                dy=float(data_values[4]), 
                                dz=float(data_values[5]),
                                turn_brake=float(data_values[6]), 
                                placeholder=float(data_values[7])
                            )
                            inner_points_data[index] = point
                    except Exception as e:
                        self.log_debug(f"Error parsing inner point data: {str(e)}")
                
                elif text_value.startswith('OUTER_POINT_'):
                    try:
                        # Parse index and data
                        parts = text_value.split(':')
                        index = int(parts[0].replace('OUTER_POINT_', ''))
                        data_values = parts[1].split(',')
                        
                        # Create point
                        if len(data_values) >= 8:
                            point = TrackPoint(
                                x=float(data_values[0]), 
                                y=float(data_values[1]), 
                                z=float(data_values[2]),
                                dx=float(data_values[3]), 
                                dy=float(data_values[4]), 
                                dz=float(data_values[5]),
                                turn_brake=float(data_values[6]), 
                                placeholder=float(data_values[7])
                            )
                            outer_points_data[index] = point
                    except Exception as e:
                        self.log_debug(f"Error parsing outer point data: {str(e)}")
            
            # Sort points by index and add to the racing lines
            self.inner_line = [point for _, point in sorted(inner_points_data.items())]
            self.outer_line = [point for _, point in sorted(outer_points_data.items())]
            
            # If metadata points not found, try to extract from polylines
            if not self.inner_line and not self.outer_line:
                self.log_debug("No points with metadata found, trying to import from polylines")
                self._import_from_polylines(msp)
            
            # Update points_per_line if needed
            if self.points_per_line == 0:
                self.points_per_line = max(len(self.inner_line), len(self.outer_line))
            
            self.log_debug(f"Imported inner line: {len(self.inner_line)} points")
            self.log_debug(f"Imported outer line: {len(self.outer_line)} points")
            
            return len(self.inner_line) > 0 or len(self.outer_line) > 0
        except Exception as e:
            print(f"Error importing DXF file: {str(e)}")
            traceback.print_exc()
            return False
    
    def _import_from_polylines(self, modelspace):
        """Import racing lines from polylines when no metadata is available"""
        try:
            inner_vertices = []
            outer_vertices = []
            
            # Find polylines by layer name
            for entity in modelspace.query('LWPOLYLINE'):
                try:
                    layer_name = entity.dxf.layer.upper()
                    
                    if 'INNER' in layer_name:
                        # This is an inner line polyline
                        for vertex in entity.vertices():
                            # Handle both 2D and 3D vertices
                            x = vertex[0]
                            z = vertex[1]  # Y is Z in our visualization
                            y = 0  # Default Y value
                            inner_vertices.append((x, y, z))
                            
                    elif 'OUTER' in layer_name:
                        # This is an outer line polyline
                        for vertex in entity.vertices():
                            # Handle both 2D and 3D vertices
                            x = vertex[0]
                            z = vertex[1]  # Y is Z in our visualization
                            y = 0  # Default Y value
                            outer_vertices.append((x, y, z))
                except Exception as e:
                    self.log_debug(f"Error processing polyline: {str(e)}")
            
            # Create points from vertices
            # Note: No racing parameters are available from polylines, so we'll use defaults
            self.inner_line = [
                TrackPoint(x=v[0], y=v[1], z=v[2], dx=0, dy=0, dz=0, turn_brake=0, placeholder=0)
                for v in inner_vertices
            ]
            
            self.outer_line = [
                TrackPoint(x=v[0], y=v[1], z=v[2], dx=0, dy=0, dz=0, turn_brake=0, placeholder=0)
                for v in outer_vertices
            ]
            
            # Update points_per_line
            self.points_per_line = max(len(self.inner_line), len(self.outer_line))
            
            self.log_debug(f"Imported from polylines - inner: {len(self.inner_line)}, outer: {len(self.outer_line)}")
            
            return True
        except Exception as e:
            self.log_debug(f"Error importing from polylines: {str(e)}")
            return False
    
    def import_separate_dxf(self, inner_filename, outer_filename):
        """Import inner and outer racing lines from separate DXF files"""
        success_inner = self._import_line_from_dxf(inner_filename, True)
        success_outer = self._import_line_from_dxf(outer_filename, False)
        
        # Update points_per_line
        self.points_per_line = max(len(self.inner_line), len(self.outer_line))
        
        return success_inner or success_outer
    
    def _import_line_from_dxf(self, filename, is_inner):
        """Import a single racing line from DXF file"""
        try:
            # Load the DXF file
            doc = ezdxf.readfile(filename)
            msp = doc.modelspace()
            
            # Read data from text entities
            points_data = {}
            point_prefix = "INNER_POINT_" if is_inner else "OUTER_POINT_"
            
            for text in msp.query('TEXT'):
                text_value = text.dxf.text
                
                if text_value.startswith(point_prefix):
                    try:
                        # Parse index and data
                        parts = text_value.split(':')
                        index = int(parts[0].replace(point_prefix, ''))
                        data_values = parts[1].split(',')
                        
                        # Create point
                        if len(data_values) >= 8:
                            point = TrackPoint(
                                x=float(data_values[0]), 
                                y=float(data_values[1]), 
                                z=float(data_values[2]),
                                dx=float(data_values[3]), 
                                dy=float(data_values[4]), 
                                dz=float(data_values[5]),
                                turn_brake=float(data_values[6]), 
                                placeholder=float(data_values[7])
                            )
                            points_data[index] = point
                    except Exception as e:
                        self.log_debug(f"Error parsing point data: {str(e)}")
            
            # Sort points by index
            sorted_points = [point for _, point in sorted(points_data.items())]
            
            # If no points with metadata found, try from polylines
            if not sorted_points:
                self.log_debug(f"No points with metadata found in {filename}, trying polylines")
                vertices = []
                layer_keyword = 'INNER' if is_inner else 'OUTER'
                
                # Find polylines by layer name
                for entity in msp.query('LWPOLYLINE'):
                    try:
                        layer_name = entity.dxf.layer.upper()
                        
                        if layer_keyword in layer_name:
                            for vertex in entity.vertices():
                                # Handle both 2D and 3D vertices
                                x = vertex[0]
                                z = vertex[1]  # Y is Z in our visualization
                                y = 0  # Default Y value
                                vertices.append((x, y, z))
                    except Exception as e:
                        self.log_debug(f"Error processing polyline: {str(e)}")
                
                # Create points from vertices with default racing parameters
                sorted_points = [
                    TrackPoint(x=v[0], y=v[1], z=v[2], dx=0, dy=0, dz=0, turn_brake=0, placeholder=0)
                    for v in vertices
                ]
            
            # Set the appropriate racing line
            if is_inner:
                self.inner_line = sorted_points
                self.log_debug(f"Imported inner line from {filename}: {len(self.inner_line)} points")
            else:
                self.outer_line = sorted_points
                self.log_debug(f"Imported outer line from {filename}: {len(self.outer_line)} points")
            
            return len(sorted_points) > 0
        except Exception as e:
            print(f"Error importing line from DXF file: {str(e)}")
            traceback.print_exc()
            return False
    
    def get_line_name(self, is_inner):
        """Get name of line for display"""
        return "Inner Racing Line" if is_inner else "Outer Racing Line"
    
    def get_points(self, is_inner):
        """Get points for selected line"""
        return self.inner_line if is_inner else self.outer_line


class DXFConverterGUI:
    def __init__(self, root, initial_file=None):
        self.root = root
        self.root.title(f"DXF Racing Line Converter v{VERSION}")
        self.root.geometry("1200x800")
        self.root.minsize(900, 600)
        
        # Apply a nice theme if available
        style = ttk.Style()
        available_themes = style.theme_names()
        if 'clam' in available_themes:
            style.theme_use('clam')
        
        self.track = TrackData()
        self.selected_line = True  # True for inner, False for outer
        self.filename = None
        self.debug_mode = tk.BooleanVar(value=True)  # Enable debug by default
        
        self.setup_ui()
        
        # Load initial file if provided
        if initial_file:
            self.load_track_file(initial_file)
        
    def setup_ui(self):
        # Main frame
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # Left panel
        left_panel = ttk.Frame(main_frame, width=350)
        left_panel.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
        left_panel.pack_propagate(False)
        
        # Right panel for visualization
        right_panel = ttk.Frame(main_frame)
        right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        # === Left Panel ===
        # CSV Operations
        csv_frame = ttk.LabelFrame(left_panel, text="CSV Operations")
        csv_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Button(csv_frame, text="Open CSV", command=self.open_csv).pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(csv_frame, text="Save CSV", command=self.save_csv).pack(fill=tk.X, padx=10, pady=5)
        
        # DXF Operations
        dxf_frame = ttk.LabelFrame(left_panel, text="DXF Operations")
        dxf_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Button(dxf_frame, text="Export to DXF (Both Lines)", command=self.export_dxf).pack(fill=tk.X, padx=10, pady=5)
        
        sep_export_frame = ttk.Frame(dxf_frame)
        sep_export_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(sep_export_frame, text="Export Inner", command=lambda: self.export_separate_dxf(True)).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        ttk.Button(sep_export_frame, text="Export Outer", command=lambda: self.export_separate_dxf(False)).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        
        ttk.Button(dxf_frame, text="Import from DXF", command=self.import_dxf).pack(fill=tk.X, padx=10, pady=5)
        
        sep_import_frame = ttk.Frame(dxf_frame)
        sep_import_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(sep_import_frame, text="Import Inner", command=lambda: self.import_separate_dxf(True)).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        ttk.Button(sep_import_frame, text="Import Outer", command=lambda: self.import_separate_dxf(False)).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        
        # Track Info
        info_frame = ttk.LabelFrame(left_panel, text="Track Information")
        info_frame.pack(fill=tk.X, pady=(0, 10))
        
        # Points per line
        points_frame = ttk.Frame(info_frame)
        points_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Label(points_frame, text="Points Per Line:").pack(side=tk.LEFT)
        self.points_var = tk.StringVar(value="0")
        ttk.Label(points_frame, textvariable=self.points_var).pack(side=tk.LEFT, padx=5)
        
        # Inner line count
        inner_frame = ttk.Frame(info_frame)
        inner_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Label(inner_frame, text="Inner Line Points:").pack(side=tk.LEFT)
        self.inner_count_var = tk.StringVar(value="0")
        ttk.Label(inner_frame, textvariable=self.inner_count_var).pack(side=tk.LEFT, padx=5)
        
        # Outer line count
        outer_frame = ttk.Frame(info_frame)
        outer_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Label(outer_frame, text="Outer Line Points:").pack(side=tk.LEFT)
        self.outer_count_var = tk.StringVar(value="0")
        ttk.Label(outer_frame, textvariable=self.outer_count_var).pack(side=tk.LEFT, padx=5)
        
        # Line Selection
        line_frame = ttk.LabelFrame(left_panel, text="Racing Line Selection")
        line_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.line_var = tk.BooleanVar(value=True)  # True for inner, False for outer
        inner_radio = ttk.Radiobutton(line_frame, text="Inner Racing Line", variable=self.line_var, value=True, command=self.on_line_changed)
        inner_radio.pack(fill=tk.X, padx=10, pady=2)
        outer_radio = ttk.Radiobutton(line_frame, text="Outer Racing Line", variable=self.line_var, value=False, command=self.on_line_changed)
        outer_radio.pack(fill=tk.X, padx=10, pady=2)
        
        # Options
        options_frame = ttk.Frame(left_panel)
        options_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Checkbutton(options_frame, text="Debug Mode", variable=self.debug_mode).pack(side=tk.LEFT, padx=10)
        
        # Status bar
        self.status_var = tk.StringVar(value="No track loaded")
        ttk.Label(left_panel, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X, pady=(10, 0))
        
        # === Right Panel ===
        # Track Visualization
        viz_frame = ttk.LabelFrame(right_panel, text="Track Visualization")
        viz_frame.pack(fill=tk.BOTH, expand=True)
        
        # Set up matplotlib figure
        plt.rcParams['font.family'] = 'Arial'
        plt.rcParams['font.size'] = 10
        
        self.fig = Figure(figsize=(8, 6), dpi=100)
        
        # Single plot for track visualization
        self.ax = self.fig.add_subplot(111)
        self.ax.set_title("Track Layout")
        self.ax.set_xlabel("X Coordinate")
        self.ax.set_ylabel("Z Coordinate")
        self.ax.grid(True)
        self.ax.set_aspect('equal')
        
        # Create canvas
        self.canvas = FigureCanvasTkAgg(self.fig, master=viz_frame)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # Add toolbar
        from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
        toolbar_frame = ttk.Frame(viz_frame)
        toolbar_frame.pack(fill=tk.X, padx=10)
        self.toolbar = NavigationToolbar2Tk(self.canvas, toolbar_frame)
        self.toolbar.update()
        
        # Update layout
        self.fig.tight_layout()
        
    def on_line_changed(self):
        """Handle racing line selection change"""
        self.selected_line = self.line_var.get()
        self.update_visualization()
    
    def load_track_file(self, filepath):
        """Load a track from CSV file"""
        try:
            debug_mode = self.debug_mode.get()
            success = self.track.load_from_csv(filepath, debug=debug_mode)
                
            if not success:
                messagebox.showerror("Error", "Failed to load track or track is empty")
                return False
                
            self.filename = filepath
            self.status_var.set(f"Loaded: {os.path.basename(filepath)}")
            
            # Update info
            self.update_track_info()
            
            # Set to inner line by default
            self.selected_line = True
            self.line_var.set(True)
            
            # Update visualization
            self.update_visualization()
            return True
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to load track: {str(e)}")
            traceback.print_exc()
            return False
    
    def update_track_info(self):
        """Update track information display"""
        self.points_var.set(str(self.track.points_per_line))
        self.inner_count_var.set(str(len(self.track.inner_line)))
        self.outer_count_var.set(str(len(self.track.outer_line)))
    
    def open_csv(self):
        """Open CSV file dialog"""
        filetypes = [
            ("CSV Files", "*.csv"),
            ("All Files", "*.*")
        ]
        
        filepath = filedialog.askopenfilename(
            title="Open CSV Track File",
            filetypes=filetypes
        )
        
        if not filepath:
            return
            
        self.load_track_file(filepath)
    
    def save_csv(self):
        """Save track to CSV file"""
        if not self.track or (not self.track.inner_line and not self.track.outer_line):
            messagebox.showinfo("Info", "No track data to save")
            return
            
        filetypes = [("CSV Files", "*.csv")]
        
        filepath = filedialog.asksaveasfilename(
            title="Save Track to CSV",
            filetypes=filetypes,
            defaultextension=".csv"
        )
        
        if not filepath:
            return
            
        try:
            success = self.track.save_to_csv(filepath)
                
            if success:
                self.filename = filepath
                messagebox.showinfo("Success", f"Track saved to {filepath}")
                self.status_var.set(f"Saved: {os.path.basename(filepath)}")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save CSV: {str(e)}")
            traceback.print_exc()
    
    def export_dxf(self):
        """Export track to DXF file"""
        if not self.track or (not self.track.inner_line and not self.track.outer_line):
            messagebox.showinfo("Info", "No track data to export")
            return
            
        filetypes = [("DXF Files", "*.dxf")]
        
        filepath = filedialog.asksaveasfilename(
            title="Export Track to DXF",
            filetypes=filetypes,
            defaultextension=".dxf"
        )
        
        if not filepath:
            return
            
        try:
            success = self.track.export_to_dxf(filepath)
                
            if success:
                messagebox.showinfo("Success", f"Track exported to {filepath}")
                self.status_var.set(f"Exported to DXF: {os.path.basename(filepath)}")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to export DXF: {str(e)}")
            traceback.print_exc()
    
    def export_separate_dxf(self, inner_line=True):
        """Export single racing line to DXF file"""
        line_points = self.track.inner_line if inner_line else self.track.outer_line
        line_name = "Inner" if inner_line else "Outer"
        
        if not line_points:
            messagebox.showinfo("Info", f"No {line_name.lower()} racing line to export")
            return
            
        filetypes = [("DXF Files", "*.dxf")]
        
        filepath = filedialog.asksaveasfilename(
            title=f"Export {line_name} Line to DXF",
            filetypes=filetypes,
            defaultextension=".dxf"
        )
        
        if not filepath:
            return
            
        try:
            if inner_line:
                success = self.track._export_line_to_dxf(filepath, self.track.inner_line, "INNER_LINE", True)
            else:
                success = self.track._export_line_to_dxf(filepath, self.track.outer_line, "OUTER_LINE", False)
                
            if success:
                messagebox.showinfo("Success", f"{line_name} line exported to {filepath}")
                self.status_var.set(f"Exported {line_name.lower()} line to DXF")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to export {line_name.lower()} line: {str(e)}")
            traceback.print_exc()
    
    def import_dxf(self):
        """Import track from DXF file"""
        filetypes = [("DXF Files", "*.dxf")]
        
        filepath = filedialog.askopenfilename(
            title="Import Track from DXF",
            filetypes=filetypes
        )
        
        if not filepath:
            return
            
        try:
            debug_mode = self.debug_mode.get()
            self.track.set_debug(debug_mode)
            
            success = self.track.import_from_dxf(filepath)
                
            if success:
                messagebox.showinfo("Success", f"Track imported from {filepath}")
                self.status_var.set(f"Imported from DXF: {os.path.basename(filepath)}")
                
                # Update info
                self.update_track_info()
                
                # Update visualization
                self.update_visualization()
            else:
                messagebox.showerror("Error", "Failed to import track data from DXF")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to import DXF: {str(e)}")
            traceback.print_exc()
    
    def import_separate_dxf(self, inner_line=True):
        """Import single racing line from DXF file"""
        line_name = "Inner" if inner_line else "Outer"
        
        filetypes = [("DXF Files", "*.dxf")]
        
        filepath = filedialog.askopenfilename(
            title=f"Import {line_name} Line from DXF",
            filetypes=filetypes
        )
        
        if not filepath:
            return
            
        try:
            debug_mode = self.debug_mode.get()
            self.track.set_debug(debug_mode)
            
            if inner_line:
                success = self.track._import_line_from_dxf(filepath, True)
            else:
                success = self.track._import_line_from_dxf(filepath, False)
                
            if success:
                messagebox.showinfo("Success", f"{line_name} line imported from {filepath}")
                self.status_var.set(f"Imported {line_name.lower()} line from DXF")
                
                # Update info
                self.update_track_info()
                
                # Update visualization
                self.update_visualization()
            else:
                messagebox.showerror("Error", f"Failed to import {line_name.lower()} line from DXF")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to import {line_name.lower()} line: {str(e)}")
            traceback.print_exc()
    
    def update_visualization(self):
        """Update track visualization"""
        if not self.track:
            return
            
        # Clear plot
        self.ax.clear()
        
        # Plot inner line if exists
        if self.track.inner_line:
            inner_x = [p.x for p in self.track.inner_line]
            inner_z = [p.z for p in self.track.inner_line]
            inner_speeds = [p.calculate_speed_and_turn()[0] for p in self.track.inner_line]
            
            # Plot line
            self.ax.plot(inner_x + [inner_x[0]], inner_z + [inner_z[0]], 'b-', linewidth=1.5, alpha=0.5, label='Inner Line')
            
            # Add points with color based on speed
            point_colors = ['red' if s < 0.5 else ('orange' if s < 0.8 else 'green') for s in inner_speeds]
            self.ax.scatter(inner_x, inner_z, c=point_colors, s=50, edgecolors='blue', linewidths=0.5, alpha=0.7)
            
            # Add point numbers
            for i, (x, z) in enumerate(zip(inner_x, inner_z)):
                if i % 5 == 0:
                    self.ax.annotate(f"{i+1}", (x, z), fontsize=8, ha='center', va='center')
        
        # Plot outer line if exists
        if self.track.outer_line:
            outer_x = [p.x for p in self.track.outer_line]
            outer_z = [p.z for p in self.track.outer_line]
            outer_speeds = [p.calculate_speed_and_turn()[0] for p in self.track.outer_line]
            
            # Plot line
            self.ax.plot(outer_x + [outer_x[0]], outer_z + [outer_z[0]], 'r-', linewidth=1.5, alpha=0.5, label='Outer Line')
            
            # Add points with color based on speed
            point_colors = ['red' if s < 0.5 else ('orange' if s < 0.8 else 'green') for s in outer_speeds]
            self.ax.scatter(outer_x, outer_z, c=point_colors, s=50, edgecolors='red', linewidths=0.5, alpha=0.7)
            
            # Add point numbers
            for i, (x, z) in enumerate(zip(outer_x, outer_z)):
                if i % 5 == 0:
                    self.ax.annotate(f"{i+1}", (x, z), fontsize=8, ha='center', va='center')
        
        # Set labels and legend
        self.ax.set_title("Track Layout")
        self.ax.set_xlabel("X Coordinate")
        self.ax.set_ylabel("Z Coordinate")
        self.ax.grid(True, alpha=0.3)
        self.ax.set_aspect('equal')
        self.ax.legend()
        
        # Update layout
        self.fig.tight_layout()
        self.canvas.draw()


def main():
    # Check if ezdxf is installed, if not, show error message
    try:
        import ezdxf
    except ImportError:
        print("ERROR: The 'ezdxf' package is required but not installed.")
        print("Please install it with: pip install ezdxf")
        return

    parser = argparse.ArgumentParser(description='DXF Racing Line Converter')
    
    # Main argument
    parser.add_argument('file', nargs='?', default=None,
                       help='Optional input CSV file')
    
    # Debug option
    parser.add_argument('--debug', action='store_true', help='Enable debug output')
    
    args = parser.parse_args()
    
    # Run GUI
    root = tk.Tk()
    app = DXFConverterGUI(root, args.file)
    root.mainloop()


if __name__ == "__main__":
    main()