"""
Unity game engine code generator for MechanicsDSL.

Generates C# MonoBehaviour scripts for Unity physics simulation.
"""
from typing import Dict, List, Optional, Any
import sympy as sp

try:
    from mechanics_dsl.codegen.base import CodeGenerator
except ImportError:
    CodeGenerator = object


class UnityGenerator(CodeGenerator if CodeGenerator != object else object):
    """
    Generate Unity C# scripts from MechanicsDSL.
    
    Creates MonoBehaviour components that simulate physics
    using RK4 integration in FixedUpdate.
    
    Example:
        gen = UnityGenerator(compiler)
        gen.generate('PendulumSimulator.cs')
    """
    
    @property
    def target_name(self) -> str:
        return "unity"
    
    @property
    def file_extension(self) -> str:
        return ".cs"
    
    def __init__(self, compiler=None):
        super().__init__()
        self.compiler = compiler
        if compiler:
            self.system_name = getattr(compiler, 'system_name', 'Physics')
            self.coordinates = getattr(compiler.simulator, 'coordinates', [])
            self.parameters = dict(getattr(compiler.simulator, 'parameters', {}))
            self.accelerations = getattr(compiler, 'accelerations', {})
        else:
            self.system_name = 'Physics'
            self.coordinates = []
            self.parameters = {}
            self.accelerations = {}
    
    def generate(self, output_file: Optional[str] = None) -> str:
        """Generate Unity C# code."""
        class_name = self._to_pascal_case(self.system_name)
        
        code = f'''using UnityEngine;
using System;

/// <summary>
/// {class_name} physics simulation.
/// Generated by MechanicsDSL.
/// </summary>
public class {class_name}Simulator : MonoBehaviour
{{
    [Header("Physical Parameters")]
{self._generate_parameters()}

    [Header("State Variables")]
{self._generate_state_fields()}

    [Header("Visualization")]
    public bool showTrail = true;
    public LineRenderer trailRenderer;
    public int maxTrailPoints = 100;
    
    [Header("Simulation")]
    public float timeScale = 1.0f;
    public bool useFixedDeltaTime = true;
    
    private float[] state;
    private float[] k1, k2, k3, k4, temp;
    
    void Start()
    {{
        InitializeState();
        if (trailRenderer == null && showTrail)
        {{
            trailRenderer = gameObject.AddComponent<LineRenderer>();
            trailRenderer.positionCount = 0;
            trailRenderer.startWidth = 0.05f;
            trailRenderer.endWidth = 0.02f;
        }}
    }}
    
    void InitializeState()
    {{
        int n = {len(self.coordinates) * 2};
        state = new float[n];
        k1 = new float[n];
        k2 = new float[n];
        k3 = new float[n];
        k4 = new float[n];
        temp = new float[n];
        
        // Initial conditions
{self._generate_initial_conditions()}
    }}
    
    void FixedUpdate()
    {{
        float dt = useFixedDeltaTime ? Time.fixedDeltaTime : Time.deltaTime;
        dt *= timeScale;
        
        // RK4 integration step
        RK4Step(dt);
        
        // Update transform
        UpdateTransform();
        
        // Update trail
        if (showTrail && trailRenderer != null)
        {{
            UpdateTrail();
        }}
    }}
    
    void RK4Step(float dt)
    {{
        float t = Time.time;
        
        // k1
        ComputeDerivatives(t, state, k1);
        
        // k2
        for (int i = 0; i < state.Length; i++)
            temp[i] = state[i] + 0.5f * dt * k1[i];
        ComputeDerivatives(t + 0.5f * dt, temp, k2);
        
        // k3
        for (int i = 0; i < state.Length; i++)
            temp[i] = state[i] + 0.5f * dt * k2[i];
        ComputeDerivatives(t + 0.5f * dt, temp, k3);
        
        // k4
        for (int i = 0; i < state.Length; i++)
            temp[i] = state[i] + dt * k3[i];
        ComputeDerivatives(t + dt, temp, k4);
        
        // Update state
        for (int i = 0; i < state.Length; i++)
            state[i] += dt * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i]) / 6.0f;
    }}
    
    void ComputeDerivatives(float t, float[] y, float[] dydt)
    {{
        // Unpack state
{self._generate_state_unpack()}
        
        // Compute accelerations
{self._generate_accelerations()}
        
        // Pack derivatives
{self._generate_derivatives_pack()}
    }}
    
    void UpdateTransform()
    {{
        // Override in subclass for specific visualization
{self._generate_transform_update()}
    }}
    
    void UpdateTrail()
    {{
        if (trailRenderer.positionCount < maxTrailPoints)
        {{
            trailRenderer.positionCount++;
        }}
        else
        {{
            // Shift positions
            for (int i = 0; i < maxTrailPoints - 1; i++)
                trailRenderer.SetPosition(i, trailRenderer.GetPosition(i + 1));
        }}
        trailRenderer.SetPosition(trailRenderer.positionCount - 1, transform.position);
    }}
    
    /// <summary>
    /// Reset simulation to initial conditions.
    /// </summary>
    public void ResetSimulation()
    {{
        InitializeState();
        if (trailRenderer != null)
            trailRenderer.positionCount = 0;
    }}
    
    /// <summary>
    /// Get current state as array.
    /// </summary>
    public float[] GetState()
    {{
        return (float[])state.Clone();
    }}
    
    /// <summary>
    /// Set state from array.
    /// </summary>
    public void SetState(float[] newState)
    {{
        if (newState.Length == state.Length)
            Array.Copy(newState, state, state.Length);
    }}
}}
'''
        
        if output_file:
            with open(output_file, 'w') as f:
                f.write(code)
        
        return code
    
    def _to_pascal_case(self, name: str) -> str:
        """Convert snake_case to PascalCase."""
        return ''.join(word.capitalize() for word in name.split('_'))
    
    def _generate_parameters(self) -> str:
        """Generate parameter fields."""
        lines = []
        for name, value in self.parameters.items():
            lines.append(f"    public float {name} = {value}f;")
        return '\n'.join(lines) if lines else "    // No parameters"
    
    def _generate_state_fields(self) -> str:
        """Generate state variable fields."""
        lines = []
        for coord in self.coordinates:
            lines.append(f"    [SerializeField] private float {coord};")
            lines.append(f"    [SerializeField] private float {coord}_dot;")
        return '\n'.join(lines) if lines else "    // No state variables"
    
    def _generate_initial_conditions(self) -> str:
        """Generate initial condition assignments."""
        lines = []
        for i, coord in enumerate(self.coordinates):
            lines.append(f"        state[{2*i}] = {coord};")
            lines.append(f"        state[{2*i + 1}] = {coord}_dot;")
        return '\n'.join(lines) if lines else "        // No initial conditions"
    
    def _generate_state_unpack(self) -> str:
        """Generate state unpacking code."""
        lines = []
        for i, coord in enumerate(self.coordinates):
            lines.append(f"        float {coord} = y[{2*i}];")
            lines.append(f"        float {coord}_dot = y[{2*i + 1}];")
        return '\n'.join(lines) if lines else "        // No state to unpack"
    
    def _generate_accelerations(self) -> str:
        """Generate acceleration computations."""
        lines = []
        for coord in self.coordinates:
            # Simplified - real implementation would convert sympy to C#
            lines.append(f"        float {coord}_ddot = 0.0f; // TODO: Generated from symbolic")
        return '\n'.join(lines) if lines else "        // No accelerations"
    
    def _generate_derivatives_pack(self) -> str:
        """Generate derivative packing."""
        lines = []
        for i, coord in enumerate(self.coordinates):
            lines.append(f"        dydt[{2*i}] = {coord}_dot;")
            lines.append(f"        dydt[{2*i + 1}] = {coord}_ddot;")
        return '\n'.join(lines) if lines else "        // No derivatives"
    
    def _generate_transform_update(self) -> str:
        """Generate transform update (basic rotation for angles)."""
        if not self.coordinates:
            return "        // No coordinates"
        
        coord = self.coordinates[0]
        return f'''        // Example: rotate based on first coordinate
        float angle = state[0] * Mathf.Rad2Deg;
        transform.localRotation = Quaternion.Euler(0, 0, -angle);'''


__all__ = ['UnityGenerator']
