"""
Tetrahedral Electric Field Sensor Array Design
Snow Dust Project - Hardware Design Phase 1
"""

import numpy as np
from dataclasses import dataclass
from typing import Tuple, List
import matplotlib.pyplot as plt

print("="*80)
print("🔧 TETRAHEDRAL ELECTRIC FIELD SENSOR ARRAY")
print("Snow Dust Project - Phase 1: Sensor Design")
print("="*80)

# ============================================
# Physical Constants
# ============================================
PHYSICAL_CONSTANTS = {
    'vacuum_permittivity': 8.854187817e-12,  # F/m
    'air_breakdown_field': 3e6,              # V/m (dry air)
    'temperature_min': -40,                   # °C (Arctic conditions)
    'temperature_max': 85,                    # °C (electronics limit)
    'snow_density_min': 50,                   # kg/m³ (fresh snow)
    'snow_density_max': 300,                  # kg/m³ (packed snow)
}

# ============================================
# Sensor Specifications
# ============================================
@dataclass
class SensorSpecifications:
    """Complete sensor specifications"""
    
    # Physical dimensions
    arm_length: float = 0.5                   # meters (tetrahedron arm)
    sensor_diameter: float = 0.02             # meters (sensor housing)
    sensor_height: float = 0.03               # meters
    total_height: float = 1.2                 # meters (including mast)
    
    # Electrical specifications
    field_range: Tuple[float, float] = (-20000, 20000)  # V/m
    resolution: float = 0.1                   # V/m
    noise_floor: float = 0.5                  # V/m RMS
    bandwidth: float = 1000                   # Hz
    sampling_rate: int = 10000                # Hz (10 kHz)
    adc_bits: int = 16                        # bits
    
    # Environmental specs
    temperature_range: Tuple[float, float] = (-40, 85)  # °C
    humidity_range: Tuple[float, float] = (0, 100)      # %
    wind_speed_max: float = 50                # m/s (hurricane force)
    ip_rating: str = "IP68"                   # dust/water proof
    
    # Power specifications
    voltage: float = 3.3                      # V
    current_per_sensor: float = 0.01          # A (10 mA)
    power_per_sensor: float = 0.033           # W (3.3V * 10mA)
    total_power: float = 0.132                # W (4 sensors)
    
    # Communication
    interface: str = "I2C"
    data_rate: int = 400000                   # Hz (400 kHz I2C)
    protocol: str = "Modbus RTU"
    
    def print_specs(self):
        """Print complete specifications"""
        print("\n📋 SENSOR ARRAY SPECIFICATIONS:")
        print("-"*60)
        print(f"Physical Dimensions:")
        print(f"  Arm length: {self.arm_length*100:.1f} cm")
        print(f"  Sensor diameter: {self.sensor_diameter*100:.1f} cm")
        print(f"  Total height: {self.total_height:.1f} m")
        
        print(f"\nElectrical Performance:")
        print(f"  Field range: ±{self.field_range[1]/1000:.1f} kV/m")
        print(f"  Resolution: {self.resolution} V/m")
        print(f"  Noise floor: {self.noise_floor} V/m RMS")
        print(f"  Bandwidth: {self.bandwidth} Hz")
        print(f"  Sampling rate: {self.sampling_rate/1000:.1f} kHz")
        print(f"  ADC resolution: {self.adc_bits}-bit")
        
        print(f"\nEnvironmental:")
        print(f"  Temperature: {self.temperature_range[0]} to {self.temperature_range[1]} °C")
        print(f"  Humidity: {self.humidity_range[0]} to {self.humidity_range[1]} %")
        print(f"  Max wind speed: {self.wind_speed_max} m/s")
        print(f"  IP Rating: {self.ip_rating}")
        
        print(f"\nPower:")
        print(f"  Voltage: {self.voltage} V")
        print(f"  Current per sensor: {self.current_per_sensor*1000:.0f} mA")
        print(f"  Power per sensor: {self.power_per_sensor*1000:.1f} mW")
        print(f"  Total power: {self.total_power*1000:.1f} mW")
        
        print(f"\nCommunication:")
        print(f"  Interface: {self.interface}")
        print(f"  Data rate: {self.data_rate/1000:.0f} kHz")
        print(f"  Protocol: {self.protocol}")
        print("-"*60)

# ============================================
# Tetrahedral Geometry
# ============================================
class TetrahedralGeometry:
    """Tetrahedral sensor array geometry"""
    
    def __init__(self, arm_length: float = 0.5):
        self.arm_length = arm_length
        
        # Tetrahedron vertices (regular tetrahedron)
        # Normalized coordinates
        self.base_vertices = np.array([
            [1, 0, -1/np.sqrt(2)],
            [-1, 0, -1/np.sqrt(2)],
            [0, 1, 1/np.sqrt(2)],
            [0, -1, 1/np.sqrt(2)]
        ]) * (arm_length / np.sqrt(2))
        
        # Sensor positions (centers of sphere at each vertex)
        self.sensor_positions = self.base_vertices.copy()
        
        # Calculate geometric properties
        self.calculate_geometry()
    
    def calculate_geometry(self):
        """Calculate geometric properties of tetrahedron"""
        # Edge length
        self.edge_length = np.linalg.norm(self.sensor_positions[0] - self.sensor_positions[1])
        
        # Volume of tetrahedron
        A = self.sensor_positions[0]
        B = self.sensor_positions[1]
        C = self.sensor_positions[2]
        D = self.sensor_positions[3]
        
        AB = B - A
        AC = C - A
        AD = D - A
        
        self.volume = abs(np.dot(AB, np.cross(AC, AD))) / 6.0
        
        # Center of tetrahedron
        self.centroid = np.mean(self.sensor_positions, axis=0)
        
        # Distances from centroid
        self.distances_from_center = [
            np.linalg.norm(pos - self.centroid) for pos in self.sensor_positions
        ]
        
        # Surface area
        self.calculate_surface_area()
    
    def calculate_surface_area(self):
        """Calculate surface area of tetrahedron"""
        # Face triangles
        faces = [
            [0, 1, 2],  # Face opposite vertex 3
            [0, 1, 3],  # Face opposite vertex 2
            [0, 2, 3],  # Face opposite vertex 1
            [1, 2, 3]   # Face opposite vertex 0
        ]
        
        self.face_areas = []
        for face in faces:
            v0 = self.sensor_positions[face[0]]
            v1 = self.sensor_positions[face[1]]
            v2 = self.sensor_positions[face[2]]
            
            # Area using cross product
            area = 0.5 * np.linalg.norm(np.cross(v1 - v0, v2 - v0))
            self.face_areas.append(area)
        
        self.surface_area = sum(self.face_areas)
    
    def get_sensor_coordinates(self, sensor_id: int) -> np.ndarray:
        """Get coordinates of specific sensor"""
        if 0 <= sensor_id < 4:
            return self.sensor_positions[sensor_id]
        else:
            raise ValueError("Sensor ID must be 0-3")
    
    def get_all_distances(self) -> np.ndarray:
        """Get all inter-sensor distances"""
        distances = np.zeros((4, 4))
        for i in range(4):
            for j in range(4):
                distances[i, j] = np.linalg.norm(
                    self.sensor_positions[i] - self.sensor_positions[j]
                )
        return distances
    
    def print_geometry(self):
        """Print geometric properties"""
        print("\n📐 TETRAHEDRAL GEOMETRY:")
        print("-"*60)
        print(f"Arm length: {self.arm_length:.3f} m")
        print(f"Edge length: {self.edge_length:.3f} m")
        print(f"Volume: {self.volume:.6f} m³")
        print(f"Surface area: {self.surface_area:.6f} m²")
        print(f"Centroid: {self.centroid}")
        
        print(f"\nSensor Positions:")
        for i, pos in enumerate(self.sensor_positions):
            print(f"  Sensor {i}: [{pos[0]:.3f}, {pos[1]:.3f}, {pos[2]:.3f}] m")
        
        print(f"\nDistances from centroid:")
        for i, dist in enumerate(self.distances_from_center):
            print(f"  Sensor {i}: {dist:.3f} m")
        
        print(f"\nFace areas:")
        for i, area in enumerate(self.face_areas):
            print(f"  Face {i}: {area:.6f} m²")
        
        distances = self.get_all_distances()
        print(f"\nInter-sensor distances (m):")
        print("     0       1       2       3")
        for i in range(4):
            print(f"{i}: " + " ".join(f"{distances[i,j]:.3f}" for j in range(4)))
        
        print("-"*60)

# ============================================
# Sensor Array Analysis
# ============================================
class SensorArrayAnalysis:
    """Analysis of sensor array performance"""
    
    def __init__(self, geometry: TetrahedralGeometry, specs: SensorSpecifications):
        self.geometry = geometry
        self.specs = specs
        
    def calculate_gradient_accuracy(self) -> dict:
        """Calculate theoretical gradient calculation accuracy"""
        # For tetrahedral configuration, gradient accuracy depends on:
        # 1. Sensor positions accuracy
        # 2. Field measurement accuracy
        # 3. Numerical conditioning
        
        # Condition number of position matrix
        A = np.column_stack([
            self.geometry.sensor_positions[1] - self.geometry.sensor_positions[0],
            self.geometry.sensor_positions[2] - self.geometry.sensor_positions[0],
            self.geometry.sensor_positions[3] - self.geometry.sensor_positions[0]
        ])
        
        cond_number = np.linalg.cond(A)
        
        # Theoretical gradient resolution
        # δ(∇E) ≈ cond(A) * (δE / d) where d is typical distance
        typical_distance = self.geometry.edge_length
        field_resolution = self.specs.resolution
        
        gradient_resolution = cond_number * (field_resolution / typical_distance)
        
        # Signal-to-noise ratio for gradient
        typical_field = 1000  # V/m (typical in snowstorm)
        field_snr = typical_field / self.specs.noise_floor
        gradient_snr = field_snr / cond_number
        
        return {
            'condition_number': cond_number,
            'gradient_resolution': gradient_resolution,  # V/m²
            'field_snr': field_snr,
            'gradient_snr': gradient_snr,
            'typical_distance': typical_distance,
            'position_accuracy_required': field_resolution / (10 * cond_number)
        }
    
    def calculate_power_budget(self, operation_hours: float = 24) -> dict:
        """Calculate power budget for 24-hour operation"""
        # Power consumption
        sensor_power = self.specs.total_power  # W
        
        # Additional system power (MCU, comms, etc.)
        system_power = 0.5  # W (ESP32 + peripherals)
        
        # Heating power for -40°C operation
        # Assuming 10W heating with 50% duty cycle
        heating_power = 10.0 * 0.5  # W
        
        total_power = sensor_power + system_power + heating_power
        
        # Energy requirements
        daily_energy = total_power * 24  # Wh/day
        
        # Battery sizing (3-day autonomy)
        battery_capacity = daily_energy * 3 / 0.8  # Wh (80% DoD)
        
        # Solar panel sizing (Arctic winter - low sun)
        solar_irradiance = 50  # W/m² (Arctic winter)
        panel_efficiency = 0.2
        panel_power = solar_irradiance * panel_efficiency  # W/m²
        
        panel_area = daily_energy / (panel_power * 4)  # 4 hours effective sun
        
        return {
            'sensor_power': sensor_power,
            'system_power': system_power,
            'heating_power': heating_power,
            'total_power': total_power,
            'daily_energy': daily_energy,
            'battery_capacity': battery_capacity,
            'solar_panel_area': panel_area,
            'solar_panel_power': panel_power
        }
    
    def calculate_mechanical_stress(self, wind_speed: float) -> dict:
        """Calculate mechanical stress from wind loading"""
        # Wind force on spherical sensor
        sensor_area = np.pi * (self.specs.sensor_diameter/2)**2
        
        # Drag coefficient for sphere
        Cd = 0.47
        
        # Air density at -20°C
        rho_air = 1.395  # kg/m³
        
        # Wind force
        wind_force = 0.5 * Cd * rho_air * sensor_area * wind_speed**2
        
        # Bending moment at base (worst case)
        moment_arm = self.specs.total_height
        bending_moment = wind_force * moment_arm
        
        # Stress on arm (assuming aluminum tube)
        arm_radius = 0.01  # m
        arm_thickness = 0.001  # m
        area_moment = (np.pi/4) * (arm_radius**4 - (arm_radius - arm_thickness)**4)
        
        bending_stress = bending_moment * arm_radius / area_moment
        
        # Natural frequency
        arm_mass = 0.1  # kg (estimated)
        stiffness = 3 * 69e9 * area_moment / (self.specs.arm_length**3)  # Aluminum
        natural_freq = (1/(2*np.pi)) * np.sqrt(stiffness / arm_mass)
        
        return {
            'wind_force': wind_force,
            'bending_moment': bending_moment,
            'bending_stress': bending_stress,
            'yield_stress_aluminum': 276e6,  # Pa
            'safety_factor': 276e6 / bending_stress if bending_stress > 0 else float('inf'),
            'natural_frequency': natural_freq,
            'wind_induced_vibration_risk': natural_freq < 1.0  # Hz
        }
    
    def analyze_performance(self):
        """Complete performance analysis"""
        print("\n📊 SENSOR ARRAY PERFORMANCE ANALYSIS:")
        print("-"*60)
        
        # Gradient accuracy
        grad_accuracy = self.calculate_gradient_accuracy()
        print(f"Gradient Calculation:")
        print(f"  Condition number: {grad_accuracy['condition_number']:.2f}")
        print(f"  Gradient resolution: {grad_accuracy['gradient_resolution']:.2f} V/m²")
        print(f"  Field SNR: {grad_accuracy['field_snr']:.1f}")
        print(f"  Gradient SNR: {grad_accuracy['gradient_snr']:.1f}")
        print(f"  Required position accuracy: {grad_accuracy['position_accuracy_required']*1000:.3f} mm")
        
        # Power budget
        power = self.calculate_power_budget()
        print(f"\nPower Budget (24h operation):")
        print(f"  Sensor power: {power['sensor_power']*1000:.1f} mW")
        print(f"  System power: {power['system_power']*1000:.1f} mW")
        print(f"  Heating power: {power['heating_power']:.1f} W")
        print(f"  Total power: {power['total_power']:.1f} W")
        print(f"  Daily energy: {power['daily_energy']:.1f} Wh")
        print(f"  Battery capacity needed: {power['battery_capacity']:.1f} Wh")
        print(f"  Solar panel area needed: {power['solar_panel_area']:.2f} m²")
        
        # Mechanical analysis
        mech = self.calculate_mechanical_stress(wind_speed=30)  # 30 m/s storm
        print(f"\nMechanical Analysis (30 m/s wind):")
        print(f"  Wind force: {mech['wind_force']:.2f} N")
        print(f"  Bending moment: {mech['bending_moment']:.2f} N·m")
        print(f"  Bending stress: {mech['bending_stress']/1e6:.1f} MPa")
        print(f"  Yield stress (Al): {mech['yield_stress_aluminum']/1e6:.0f} MPa")
        print(f"  Safety factor: {mech['safety_factor']:.1f}")
        print(f"  Natural frequency: {mech['natural_frequency']:.1f} Hz")
        print(f"  Vibration risk: {'HIGH' if mech['wind_induced_vibration_risk'] else 'LOW'}")
        
        print("-"*60)

# ============================================
# Bill of Materials
# ============================================
class BillOfMaterials:
    """Bill of Materials for sensor array"""
    
    def __init__(self):
        self.components = {
            'sensors': [
                {'item': 'Electric Field Mill Sensor', 'qty': 4, 'unit_price': 180.00, 'supplier': 'JCI Chilworth'},
                {'item': '16-bit ADC (ADS1115)', 'qty': 4, 'unit_price': 8.50, 'supplier': 'Texas Instruments'},
                {'item': 'Precision Op-Amp (OPA2171)', 'qty': 8, 'unit_price': 3.20, 'supplier': 'Texas Instruments'},
            ],
            'electronics': [
                {'item': 'Microcontroller (ESP32)', 'qty': 1, 'unit_price': 12.00, 'supplier': 'Espressif'},
                {'item': 'I2C Multiplexer (TCA9548A)', 'qty': 1, 'unit_price': 4.50, 'supplier': 'Texas Instruments'},
                {'item': 'Voltage Regulator (LT3045)', 'qty': 2, 'unit_price': 6.80, 'supplier': 'Analog Devices'},
                {'item': 'Digital Isolator (ADuM3151)', 'qty': 1, 'unit_price': 9.20, 'supplier': 'Analog Devices'},
            ],
            'mechanical': [
                {'item': 'Aluminum Tube (25mm OD, 1mm wall)', 'qty': 4, 'unit_price': 15.00, 'supplier': 'Local'},
                {'item': 'Stainless Steel Fasteners', 'qty': 1, 'unit_price': 25.00, 'supplier': 'McMaster-Carr'},
                {'item': 'IP68 Enclosure', 'qty': 5, 'unit_price': 35.00, 'supplier': 'Polycase'},
                {'item': 'Thermal Insulation', 'qty': 1, 'unit_price': 40.00, 'supplier': 'Local'},
            ],
            'power': [
                {'item': 'LiFePO4 Battery (12V, 20Ah)', 'qty': 1, 'unit_price': 85.00, 'supplier': 'Battle Born'},
                {'item': 'Solar Panel (50W)', 'qty': 1, 'unit_price': 120.00, 'supplier': 'Renogy'},
                {'item': 'MPPT Charge Controller', 'qty': 1, 'unit_price': 45.00, 'supplier': 'Victron'},
            ]
        }
    
    def calculate_totals(self):
        """Calculate total costs"""
        totals = {}
        grand_total = 0
        
        for category, items in self.components.items():
            cat_total = sum(item['qty'] * item['unit_price'] for item in items)
            totals[category] = cat_total
            grand_total += cat_total
        
        return totals, grand_total
    
    def print_bom(self):
        """Print complete bill of materials"""
        print("\n📋 BILL OF MATERIALS (BOM):")
        print("-"*80)
        
        totals, grand_total = self.calculate_totals()
        
        for category, items in self.components.items():
            print(f"\n{category.upper()}:")
            print(f"{'Item':<40} {'Qty':>6} {'Unit Price':>12} {'Total':>12} {'Supplier':<20}")
            print("-"*80)
            
            for item in items:
                total = item['qty'] * item['unit_price']
                print(f"{item['item']:<40} {item['qty']:>6} "
                      f"${item['unit_price']:>11.2f} ${total:>11.2f} {item['supplier']:<20}")
            
            print(f"{'Category Total:':<58} ${totals[category]:>11.2f}")
        
        print("-"*80)
        print(f"{'GRAND TOTAL:':<58} ${grand_total:>11.2f}")
        print("-"*80)
        
        print(f"\n💡 Notes:")
        print(f"  • Prices are estimates for small quantities")
        print(f"  • Add 20% for contingency and shipping")
        print(f"  • Total with contingency: ${grand_total*1.2:.2f}")
        print(f"  • Assembly labor not included")

# ============================================
# Main Execution
# ============================================
def main():
    """Main function - run complete sensor design"""
    print("\n" + "="*80)
    print("🎯 SNOW DUST PROJECT - PHASE 1: SENSOR ARRAY DESIGN")
    print("="*80)
    
    # Step 1: Define specifications
    print("\n1. DEFINING SPECIFICATIONS")
    specs = SensorSpecifications()
    specs.print_specs()
    
    # Step 2: Design geometry
    print("\n2. DESIGNING TETRAHEDRAL GEOMETRY")
    geometry = TetrahedralGeometry(arm_length=specs.arm_length)
    geometry.print_geometry()
    
    # Step 3: Performance analysis
    print("\n3. PERFORMANCE ANALYSIS")
    analysis = SensorArrayAnalysis(geometry, specs)
    analysis.analyze_performance()
    
    # Step 4: Bill of Materials
    print("\n4. BILL OF MATERIALS")
    bom = BillOfMaterials()
    bom.print_bom()
    
    # Step 5: Design recommendations
    print("\n5. DESIGN RECOMMENDATIONS:")
    print("-"*60)
    print("🔧 Mechanical:")
    print("  • Use 6061-T6 aluminum for arms")
    print("  • Add vibration dampers at joints")
    print("  • Use stainless steel fasteners for corrosion resistance")
    
    print("\n⚡ Electrical:")
    print("  • Implement active heating for sensors")
    print("  • Use shielded twisted pair cables")
    print("  • Add surge protection for lightning")
    
    print("\n📡 Communication:")
    print("  • Use RS-485 for long distance reliability")
    print("  • Implement error correction (CRC-16)")
    print("  • Add watchdog timer for automatic recovery")
    
    print("\n🔧 Assembly:")
    print("  • Calibrate sensors in known field")
    print("  • Verify thermal performance to -40°C")
    print("  • Test wind resistance to 50 m/s")
    
    print("-"*60)
    
    # Final summary
    print("\n✅ SENSOR ARRAY DESIGN COMPLETE")
    print("="*80)
    print("\nNext steps:")
    print("  1. Procure components from BOM")
    print("  2. Manufacture mechanical parts")
    print("  3. Assemble and test electronics")
    print("  4. Calibrate sensors")
    print("  5. Field testing")
    
    print("\n📅 Estimated timeline: 4-6 weeks")
    print("💰 Estimated cost: $1,500 - $2,000")
    print("🎯 Ready for: Laboratory validation (Phase 2)")
    
    print("\n" + "="*80)

if __name__ == "__main__":
    main()
