"""
Pipe String to PHG Converter
============================

This module provides functionality to convert pipe strings into PHG (Python Graphics) scripts.
Supports both world coordinate system and local coordinate system pipe descriptions.

Main Features:
- local_pipe_to_phg: Local coordinate system pipe conversion  
- world_pipestr_vis: Direct visualization for world coordinate pipes (converts to local first)
- local_pipestr_vis: Direct visualization for local coordinate pipes
- multiple_pipes_vis: Advanced visualization for multiple pipes

Pipe String Format:
World Coordinate System:
- N: North (World X+)
- S: South (World X-)
- E: East (World Z+)
- W: West (World Z-)
- U: Up (World Y+)
- D: Down (World Y-)

Local Coordinate System:
- F: Forward (Local Z+)
- B: Backward (Local Z-)
- R: Right (Local X+)
- L: Left (Local X-)
- U: Up (Local Y+)
- D: Down (Local Y-)
"""

import phg
from coordinate_system import vec3, quat, coord3

# Default color and pipe radius
DEFAULT_PIPE_COLOR = (0, 55, 255)
DEFAULT_PIPE_RADIUS = 0.3

def world_to_local_pipe_str(world_pipe_str, start_c):
    """
    Convert world coordinate system pipe string to local coordinate system
    
    Parameters:
        world_pipe_str: World coordinate pipe string (e.g., "NNEUD")
        start_c: Starting coordinate system
    
    Returns:
        Local coordinate pipe string
    """
    
    if not world_pipe_str:
        return ""

    # World coordinate system direction definitions (cardinal directions)
    directions = {
        'E': vec3(1, 0, 0),   # East (X+)
        'W': vec3(-1, 0, 0),  # West (X-)
        'N': vec3(0, 0, 1),   # North (Z+)
        'S': vec3(0, 0, -1),  # South (Z-)
        'U': vec3(0, 1, 0),   # Up (Y+)
        'D': vec3(0, -1, 0)   # Down (Y-)
    }

    # Local coordinate system direction definitions
    local_directions = {
        'F': vec3(0, 0, 1),   # Forward (local Z+)
        'B': vec3(0, 0, -1),  # Backward (local Z-)
        'R': vec3(1, 0, 0),   # Right (local X+)
        'L': vec3(-1, 0, 0),  # Left (local X-)
        'U': vec3(0, 1, 0),   # Up (local Y+)
        'D': vec3(0, -1, 0)   # Down (local Y-)
    }

    # Initialize local coordinate system
    cur_c = start_c.Q()
    local_pipe_str = ""

    for i, move in enumerate(world_pipe_str):
        if move not in directions:
            continue

        # Get movement direction vector in world coordinate system
        world_dir = directions[move]

        # Transform world direction vector to current local coordinate system
        local_dir = world_dir / cur_c

        # Find the component with largest absolute value to determine primary movement direction
        abs_components = [abs(local_dir.x), abs(local_dir.y), abs(local_dir.z)]
        max_idx = abs_components.index(max(abs_components))

        # Determine movement direction based on the largest component
        if max_idx == 0:  # X-axis
            local_move = 'L' if local_dir.x > 0 else 'R'
        elif max_idx == 1:  # Y-axis
            local_move = 'U' if local_dir.y > 0 else 'D'
        else:  # Z-axis
            local_move = 'F' if local_dir.z > 0 else 'B' # NO BACKWARD!

        # Debug first 8 characters
        if i < 8:
            print(f"      [{i}] world:'{move}' → local_dir:({local_dir.x:.2f},{local_dir.y:.2f},{local_dir.z:.2f}) → local:'{local_move}', uz:({cur_c.uz.x:.2f},{cur_c.uz.y:.2f},{cur_c.uz.z:.2f})")

        local_pipe_str += local_move

        # Update local coordinate system (when turning)
        if local_move == 'R':
            rotation = quat(-3.1416 / 2, cur_c.uy)
            cur_c = cur_c * rotation
        elif local_move == 'L':
            rotation = quat(3.1416 / 2, cur_c.uy)
            cur_c = cur_c * rotation
        elif local_move == 'U':
            rotation = quat(3.1416 / 2, -cur_c.ux)
            cur_c = cur_c * rotation
        elif local_move == 'D':
            rotation = quat(-3.1416 / 2, -cur_c.ux)
            cur_c = cur_c * rotation
            
    return local_pipe_str

def local_pipe_to_phg(local_pipe_str, start_c, pipe_color=DEFAULT_PIPE_COLOR, pipe_radius=DEFAULT_PIPE_RADIUS):
    """
    Convert local coordinate system pipe string to PHG script

    Parameters:
        local_pipe_str: Local coordinate pipe string (e.g., "FFRUD")
        start_c: Starting coordinate system
        pipe_color: Pipe color as (R, G, B) tuple
        pipe_radius: Pipe radius

    Returns:
        PHG script string
    """

    # Parse color
    r, g, b = pipe_color

    # Pipe component mapping
    pipe_components = {
        'F': f'{{{{rgb:{r},{g},{b};md:cylinder {pipe_radius} 1.0;rx:90;z:-0.5;}};z:1.0}}',
        'B': f'{{{{rgb:{r},{g},{b};md:cylinder {pipe_radius} 1.0;rx:-90;z:-1.5;}};z:-1.0}}',
        'R': f'{{{{{{rgb:{r},{g},{b};md:elbow 90 0.5 {pipe_radius};x:0.5;z:0.5;}}ry:90;x:1.0;z:-1.0}}}},{{x:1.0;ry:-90;}}',
        'L': f'{{{{{{rgb:{r},{g},{b};md:elbow 90 0.5 {pipe_radius};x:0.5;z:0.5;}}ry:-90;m:x;z:-1.0;x:-1.0}}}},{{x:-1.0;ry:90;}}',
        'U': f'{{{{{{rgb:{r},{g},{b};md:elbow 90 0.5 {pipe_radius};x:0.5;z:0.5;}}rz:90;y:1.0;z:-1}}}},{{y:1.0;rx:90;}}',
        'D': f'{{{{{{rgb:{r},{g},{b};md:elbow 90 0.5 {pipe_radius};x:0.5;z:0.5;}}rz:-90;y:-1;z:-1}}}},{{y:-1.0;rx:-90;}}'
    }

    # Store all pipe component scripts
    script_parts = []

    # Iterate through each character in pipe string
    for move_char in local_pipe_str:
        if move_char not in pipe_components:
            print(f"Warning: Unknown pipe character '{move_char}', skipped")
            continue

        # Directly use local coordinate system movement commands
        script_parts.append(pipe_components[move_char])

    # Combine all pipe component scripts
    inner_script = ',\n'.join(script_parts)

    # Get quaternion of starting coordinate system
    q = start_c.Q()

    # Build complete PHG script
    complete_script = f"""{{
    {{xyz:{start_c.o.x},{start_c.o.y},{start_c.o.z};q:{q.w},{q.x},{q.y},{q.z};s:0.5;
    <{inner_script}>}}
    }}"""

    return complete_script


def world_pipe_to_phg(world_pipe_str, start_c, pipe_color=DEFAULT_PIPE_COLOR, pipe_radius=DEFAULT_PIPE_RADIUS):
    """
    Convert world coordinate pipe string to PHG script.

    Parameters:
        world_pipe_str: World coordinate pipe string (e.g., "NNEUD")
        start_c: Starting coordinate system
        pipe_color: Pipe color as (R, G, B) tuple
        pipe_radius: Pipe radius

    Returns:
        PHG script string
    """
    local_pipe_str = world_to_local_pipe_str(world_pipe_str, start_c)
    return local_pipe_to_phg(local_pipe_str, start_c, pipe_color, pipe_radius)

def world_pipestr_vis(world_pipe_strs, start_c=None, pipe_colors=None, pipe_radius=DEFAULT_PIPE_RADIUS):
    """
    Direct visualization for world coordinate pipe strings with support for multiple pipes
    
    Parameters:
        world_pipe_strs: World coordinate pipe string or list of strings (e.g., "NNEUD" or ["NNE", "SSU"])
        start_c: Starting coordinate system, defaults to origin
        pipe_colors: Single color or list of colors as (R, G, B) tuples
        pipe_radius: Pipe radius
    
    Returns:
        PHG script string
    """
    # Handle single string input
    if isinstance(world_pipe_strs, str):
        world_pipe_strs = [world_pipe_strs]
    
    # Handle single color input
    if pipe_colors is None:
        pipe_colors = [DEFAULT_PIPE_COLOR] * len(world_pipe_strs)
    elif isinstance(pipe_colors, tuple):
        pipe_colors = [pipe_colors] * len(world_pipe_strs)
    
    # Ensure colors list matches pipes list length
    if len(pipe_colors) != len(world_pipe_strs):
        pipe_colors = [DEFAULT_PIPE_COLOR] * len(world_pipe_strs)
        print("Warning: Colors list length doesn't match pipes list, using default colors")
    
    # Use default origin if no starting coordinate provided
    if start_c is None:
        start_c = coord3(vec3(0, 0, 0), quat(1, 0, 0, 0))

    # Generate PHG scripts for all pipes
    script_parts = []
    for i, (pipe_str, color) in enumerate(zip(world_pipe_strs, pipe_colors)):
        # Convert world coordinate pipe string to local coordinate system first
        local_pipe_str = world_to_local_pipe_str(pipe_str, start_c)
        # Generate PHG script from local pipe string
        phg_script = local_pipe_to_phg(local_pipe_str, start_c, color, pipe_radius)
        # Extract inner content between outer braces
        inner_content = phg_script.strip()[1:-1].strip()
        script_parts.append(inner_content)

    # Combine all pipe scripts
    combined_script = "{\n" + ",\n".join(script_parts) + "\n}"

    # Call visualization function
    try:
        phg.vis(combined_script)
        print(f"Visualizing {len(world_pipe_strs)} world coordinate pipes (converted to local)")
    except ImportError:
        print("Warning: Cannot import visualization module, returning PHG script")

    return combined_script

def local_pipestr_vis(local_pipe_strs, start_c=None, pipe_colors=None, pipe_radius=DEFAULT_PIPE_RADIUS):
    """
    Direct visualization for local coordinate pipe strings with support for multiple pipes
    
    Parameters:
        local_pipe_strs: Local coordinate pipe string or list of strings (e.g., "FFRUD" or ["FFR", "LLU"])
        start_c: Starting coordinate system, defaults to origin
        pipe_colors: Single color or list of colors as (R, G, B) tuples
        pipe_radius: Pipe radius
    
    Returns:
        PHG script string
    """
    # Handle single string input
    if isinstance(local_pipe_strs, str):
        local_pipe_strs = [local_pipe_strs]
    
    # Handle single color input
    if pipe_colors is None:
        pipe_colors = [DEFAULT_PIPE_COLOR] * len(local_pipe_strs)
    elif isinstance(pipe_colors, tuple):
        pipe_colors = [pipe_colors] * len(local_pipe_strs)
    
    # Ensure colors list matches pipes list length
    if len(pipe_colors) != len(local_pipe_strs):
        pipe_colors = [DEFAULT_PIPE_COLOR] * len(local_pipe_strs)
        print("Warning: Colors list length doesn't match pipes list, using default colors")
    
    # Use default origin if no starting coordinate provided
    if start_c is None:
        start_c = coord3(vec3(0, 0, 0), quat(1, 0, 0, 0))

    # Generate PHG scripts for all pipes
    script_parts = []
    for i, (pipe_str, color) in enumerate(zip(local_pipe_strs, pipe_colors)):
        phg_script = local_pipe_to_phg(pipe_str, start_c, color, pipe_radius)
        # Extract inner content between outer braces
        inner_content = phg_script.strip()[1:-1].strip()
        script_parts.append(inner_content)

    # Combine all pipe scripts
    combined_script = "{\n" + ",\n".join(script_parts) + "\n}"

    # Call visualization function
    try:
        phg.vis(combined_script)
        print(f"Visualizing {len(local_pipe_strs)} local coordinate pipes")
    except ImportError:
        print("Warning: Cannot import visualization module, returning PHG script")

    return combined_script

def multiple_pipes_vis(pipe_configs, coordinate_system='world'):
    """
    Advanced visualization for multiple pipes with individual configurations
    
    Parameters:
        pipe_configs: List of pipe configuration dictionaries with keys:
            - 'pipe_str': Pipe string (required)
            - 'start_c': Starting coordinate (optional, defaults to origin)
            - 'color': Pipe color (optional, defaults to DEFAULT_PIPE_COLOR)
            - 'radius': Pipe radius (optional, defaults to DEFAULT_PIPE_RADIUS)
        coordinate_system: 'world' or 'local' coordinate system
    
    Returns:
        PHG script string
    """
    script_parts = []
    
    for i, config in enumerate(pipe_configs):
        pipe_str = config.get('pipe_str')
        start_c = config.get('start_c', coord3(vec3(0, 0, 0), quat(1, 0, 0, 0)))
        color = config.get('color', DEFAULT_PIPE_COLOR)
        radius = config.get('radius', DEFAULT_PIPE_RADIUS)
        
        if coordinate_system.lower() == 'world':
            # Convert world coordinate pipe string to local coordinate system first
            local_pipe_str = world_to_local_pipe_str(pipe_str, start_c)
            phg_script = local_pipe_to_phg(local_pipe_str, start_c, color, radius)
        else:
            # Use local coordinate pipe string directly
            phg_script = local_pipe_to_phg(pipe_str, start_c, color, radius)
        
        # Extract inner content
        inner_content = phg_script.strip()[1:-1].strip()
        script_parts.append(inner_content)
    
    # Combine all pipe scripts
    combined_script = "{\n" + ",\n".join(script_parts) + "\n}"
    
    # Call visualization
    try:
        phg.vis(combined_script)
        print(f"Visualizing {len(pipe_configs)} pipes in {coordinate_system} coordinate system")
    except ImportError:
        print("Warning: Cannot import visualization module, returning PHG script")
    
    return combined_script

# Usage Examples
if __name__ == "__main__":
    # Create starting coordinate system (example)
    start_coord = coord3(vec3(0, 0, 0), quat(1, 0, 0, 0))

    # Example 1: World coordinate pipe string visualization
    example_world_pipe = "NNEUD"  # North twice, east, up, down
    print("World Coordinate Pipe Example:")
    print(f"Original world pipe: '{example_world_pipe}'")
    
    # Convert to local coordinate system
    local_pipe_str = world_to_local_pipe_str(example_world_pipe, start_coord)
    print(f"Converted to local: '{local_pipe_str}'")
    
    # Generate PHG script
    phg_script = local_pipe_to_phg(local_pipe_str, start_coord)
    print("Generated PHG Script:")
    print(phg_script)
    print()

    # Example 2: Single local coordinate pipe string
    example_local_pipe = "FFRUD"  # Movements in local coordinate system
    print("Local Coordinate Pipe Example:")
    phg_script = local_pipe_to_phg(example_local_pipe, start_coord)
    print("Generated PHG Script:")
    print(phg_script)
    print()

    # Example 3: Multiple pipes visualization
    print("Multiple Pipes Visualization Example:")
    
    # Multiple world coordinate pipes with different colors
    world_pipes = ["NNE", "SSU", "EEW"]
    world_colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]  # Red, Green, Blue
    world_pipestr_vis(world_pipes, start_coord, world_colors)
    print()

    # Multiple local coordinate pipes
    local_pipes = ["FFR", "BBL", "UUD"]
    local_colors = [(255, 255, 0), (255, 0, 255), (0, 255, 255)]  # Yellow, Magenta, Cyan
    local_pipestr_vis(local_pipes, start_coord, local_colors)
    print()

    # Example 4: Advanced multiple pipes configuration
    print("Advanced Multiple Pipes Configuration:")
    pipe_configs = [
        {
            'pipe_str': 'NNE',
            'start_c': coord3(vec3(0, 0, 0), quat(1, 0, 0, 0)),
            'color': (255, 100, 100),
            'radius': 0.4
        },
        {
            'pipe_str': 'SSU', 
            'start_c': coord3(vec3(5, 0, 0), quat(1, 0, 0, 0)),
            'color': (100, 255, 100),
            'radius': 0.3
        },
        {
            'pipe_str': 'FFR',  # Local coordinate pipe
            'start_c': coord3(vec3(0, 5, 0), quat(1, 0, 0, 0)),
            'color': (100, 100, 255),
            'radius': 0.2
        }
    ]
    
    # Visualize with mixed coordinate systems
    multiple_pipes_vis(pipe_configs, 'world')  # First two are world, third is local but will be treated as world
    print()

    # Example 5: Explicit coordinate system specification
    print("Explicit Coordinate System Example:")
    world_configs = [
        {
            'pipe_str': 'NNE',
            'start_c': coord3(vec3(0, 0, 0), quat(1, 0, 0, 0)),
            'color': (255, 0, 0),
            'radius': 0.3
        }
    ]
    
    local_configs = [
        {
            'pipe_str': 'FFR',
            'start_c': coord3(vec3(3, 0, 0), quat(1, 0, 0, 0)),
            'color': (0, 255, 0),
            'radius': 0.3
        }
    ]
    
    # Visualize world coordinate pipe
    world_result = multiple_pipes_vis(world_configs, 'world')
    # Visualize local coordinate pipe
    local_result = multiple_pipes_vis(local_configs, 'local')
