# JITTERBUG GENERATOR — Dynamo Python Script
# ============================================
#
# This script generates the complete Jitterbug geometry.
# Use in a Python Script node in Dynamo.
#
# INPUTS (create these as input ports):
#   IN[0] = edge_length (number, in cm, e.g., 15.24)
#   IN[1] = rhombus_angle (number, degrees, 90=VE, 60=Icosa, 0=Octa)
#   IN[2] = create_faces (boolean, True/False)
#
# OUTPUTS:
#   OUT[0] = vertices (list of Points)
#   OUT[1] = edges (list of Lines)
#   OUT[2] = triangle_faces (list of Surfaces)
#   OUT[3] = rhombus_faces (list of Surfaces)
#   OUT[4] = state_name (string)

import clr
import math

# Import Dynamo geometry
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

# =============================================================================
# INPUTS
# =============================================================================

edge_length = IN[0] if IN[0] else 15.24  # cm
rhombus_angle = IN[1] if IN[1] else 90   # degrees (90=VE, 60=Icosa, 0=Octa)
create_faces = IN[2] if len(IN) > 2 else True

# =============================================================================
# TOPOLOGY CONSTANTS
# =============================================================================

# 8 Triangular faces (vertex indices)
TRIANGLES = [
    (0, 4, 8),   # T0 +++
    (0, 9, 5),   # T1 ++-
    (2, 8, 6),   # T2 -++
    (2, 7, 9),   # T3 -+-
    (1, 10, 4),  # T4 +-+
    (1, 5, 11),  # T5 +--
    (3, 6, 10),  # T6 --+
    (3, 11, 7),  # T7 ---
]

# 6 Rhombus faces (vertex indices, in order)
RHOMBI = [
    (0, 4, 1, 5),    # R0 +X face
    (2, 7, 3, 6),    # R1 -X face
    (0, 9, 2, 8),    # R2 +Y face
    (1, 10, 3, 11),  # R3 -Y face
    (4, 8, 6, 10),   # R4 +Z face
    (5, 11, 7, 9),   # R5 -Z face
]

# 24 Edges (vertex index pairs)
EDGES = [
    (0, 4), (0, 5), (0, 8), (0, 9),
    (1, 4), (1, 5), (1, 10), (1, 11),
    (2, 6), (2, 7), (2, 8), (2, 9),
    (3, 6), (3, 7), (3, 10), (3, 11),
    (4, 8), (4, 10), (5, 9), (5, 11),
    (6, 8), (6, 10), (7, 9), (7, 11),
]

# =============================================================================
# VERTEX CALCULATION
# =============================================================================

def calculate_vertices_ve(edge_length):
    """
    Calculate 12 vertices for Vector Equilibrium state (rhombus_angle = 90°).
    Vertices are at permutations of (±a, ±a, 0) where a = edge_length / √2.
    """
    a = edge_length / math.sqrt(2)
    
    vertices = [
        Point.ByCoordinates( a,  a,  0),  # V0
        Point.ByCoordinates( a, -a,  0),  # V1
        Point.ByCoordinates(-a,  a,  0),  # V2
        Point.ByCoordinates(-a, -a,  0),  # V3
        Point.ByCoordinates( a,  0,  a),  # V4
        Point.ByCoordinates( a,  0, -a),  # V5
        Point.ByCoordinates(-a,  0,  a),  # V6
        Point.ByCoordinates(-a,  0, -a),  # V7
        Point.ByCoordinates( 0,  a,  a),  # V8
        Point.ByCoordinates( 0,  a, -a),  # V9
        Point.ByCoordinates( 0, -a,  a),  # V10
        Point.ByCoordinates( 0, -a, -a),  # V11
    ]
    
    return vertices

def calculate_vertices_transformed(edge_length, rhombus_angle_deg):
    """
    Calculate vertices for transformed state.
    
    This is a simplified transformation — for true constant-edge-length
    kinematics, use a constraint solver. This version approximates
    the transformation for visualization.
    
    rhombus_angle_deg: 90 = VE, ~63.43 = Icosa, 0 = Octa
    """
    
    if rhombus_angle_deg >= 89:
        return calculate_vertices_ve(edge_length), "Vector Equilibrium"
    
    # Convert to radians
    theta = math.radians(rhombus_angle_deg)
    
    # For icosahedron state (rhombus angle ≈ 63.43°)
    # At this state, the short diagonal of rhombus = edge length
    # sin(63.43°/2) = 0.5, meaning half-diagonal = edge/2
    
    # Transformation factor (1.0 at VE, decreasing toward Octa)
    # This is simplified — true kinematics is more complex
    t = rhombus_angle_deg / 90.0
    
    # Scale factor for the "spread" of vertices
    # At VE (t=1): a = edge_length / sqrt(2)
    # At Octa (t=0): vertices collapse
    a = (edge_length / math.sqrt(2)) * math.sqrt(t)
    
    # Height factor for Z coordinates
    # Vertices move inward as rhombus collapses
    h = (edge_length / math.sqrt(2)) * math.sqrt(t)
    
    vertices = [
        Point.ByCoordinates( a,  a,  0),  # V0
        Point.ByCoordinates( a, -a,  0),  # V1
        Point.ByCoordinates(-a,  a,  0),  # V2
        Point.ByCoordinates(-a, -a,  0),  # V3
        Point.ByCoordinates( a,  0,  h),  # V4
        Point.ByCoordinates( a,  0, -h),  # V5
        Point.ByCoordinates(-a,  0,  h),  # V6
        Point.ByCoordinates(-a,  0, -h),  # V7
        Point.ByCoordinates( 0,  a,  h),  # V8
        Point.ByCoordinates( 0,  a, -h),  # V9
        Point.ByCoordinates( 0, -a,  h),  # V10
        Point.ByCoordinates( 0, -a, -h),  # V11
    ]
    
    # Determine state name
    if rhombus_angle_deg > 80:
        state = "Vector Equilibrium"
    elif rhombus_angle_deg > 55:
        state = "Icosahedron (approx)"
    elif rhombus_angle_deg > 10:
        state = "Transitional"
    else:
        state = "Octahedron (approx)"
    
    return vertices, state

# =============================================================================
# GEOMETRY GENERATION
# =============================================================================

# Calculate vertices
if rhombus_angle >= 89:
    vertices, state_name = calculate_vertices_ve(edge_length), "Vector Equilibrium"
else:
    vertices, state_name = calculate_vertices_transformed(edge_length, rhombus_angle)

# Create edges (lines)
edges = []
for v1_idx, v2_idx in EDGES:
    line = Line.ByStartPointEndPoint(vertices[v1_idx], vertices[v2_idx])
    edges.append(line)

# Create faces (if requested)
triangle_faces = []
rhombus_faces = []

if create_faces:
    # Triangular faces
    for tri in TRIANGLES:
        pts = [vertices[tri[0]], vertices[tri[1]], vertices[tri[2]]]
        try:
            surface = Surface.ByPerimeterPoints(pts)
            triangle_faces.append(surface)
        except:
            pass  # Skip if surface creation fails
    
    # Rhombus faces
    for rhom in RHOMBI:
        pts = [vertices[rhom[0]], vertices[rhom[1]], vertices[rhom[2]], vertices[rhom[3]]]
        try:
            surface = Surface.ByPerimeterPoints(pts)
            rhombus_faces.append(surface)
        except:
            pass  # Skip if surface creation fails

# =============================================================================
# EDGE LENGTH VERIFICATION
# =============================================================================

edge_lengths = [e.Length for e in edges]
min_edge = min(edge_lengths)
max_edge = max(edge_lengths)
verification = "Edge lengths: min={:.3f}, max={:.3f}, target={:.3f}".format(
    min_edge, max_edge, edge_length)

# =============================================================================
# OUTPUT
# =============================================================================

OUT = [vertices, edges, triangle_faces, rhombus_faces, state_name, verification]
