"""
Unit conversion utilities for CAVORA.
Handles conversions between different measurement systems and formats.

Author: Samir Baladi
"""

import numpy as np
from typing import Union, List, Tuple
from datetime import datetime, timedelta
import re


class Conversions:
    """
    Unit conversion utilities for cave survey data.
    """
    
    # ============================================================
    # Time Conversions
    # ============================================================
    
    @staticmethod
    def years_to_seconds(years: float) -> float:
        """Convert years to seconds."""
        return years * 365.25 * 24 * 3600
    
    @staticmethod
    def seconds_to_years(seconds: float) -> float:
        """Convert seconds to years."""
        return seconds / (365.25 * 24 * 3600)
    
    @staticmethod
    def days_to_years(days: float) -> float:
        """Convert days to years."""
        return days / 365.25
    
    @staticmethod
    def years_to_days(years: float) -> float:
        """Convert years to days."""
        return years * 365.25
    
    @staticmethod
    def hours_to_years(hours: float) -> float:
        """Convert hours to years."""
        return hours / (24 * 365.25)
    
    @staticmethod
    def timestamp_to_datetime(timestamp: float) -> datetime:
        """Convert Unix timestamp to datetime."""
        return datetime.fromtimestamp(timestamp)
    
    @staticmethod
    def datetime_to_timestamp(dt: datetime) -> float:
        """Convert datetime to Unix timestamp."""
        return dt.timestamp()
    
    @staticmethod
    def decimal_year_to_datetime(year: float) -> datetime:
        """Convert decimal year (e.g., 2024.5) to datetime."""
        year_int = int(year)
        frac = year - year_int
        start = datetime(year_int, 1, 1)
        end = datetime(year_int + 1, 1, 1)
        return start + (end - start) * frac
    
    @staticmethod
    def datetime_to_decimal_year(dt: datetime) -> float:
        """Convert datetime to decimal year."""
        year_start = datetime(dt.year, 1, 1)
        year_end = datetime(dt.year + 1, 1, 1)
        frac = (dt - year_start) / (year_end - year_start)
        return dt.year + frac
    
    # ============================================================
    # Length Conversions
    # ============================================================
    
    @staticmethod
    def m_to_ft(meters: float) -> float:
        """Convert meters to feet."""
        return meters * 3.28084
    
    @staticmethod
    def ft_to_m(feet: float) -> float:
        """Convert feet to meters."""
        return feet / 3.28084
    
    @staticmethod
    def m_to_cm(meters: float) -> float:
        """Convert meters to centimeters."""
        return meters * 100
    
    @staticmethod
    def cm_to_m(cm: float) -> float:
        """Convert centimeters to meters."""
        return cm / 100
    
    @staticmethod
    def m_to_mm(meters: float) -> float:
        """Convert meters to millimeters."""
        return meters * 1000
    
    @staticmethod
    def mm_to_m(mm: float) -> float:
        """Convert millimeters to meters."""
        return mm / 1000
    
    @staticmethod
    def km_to_miles(km: float) -> float:
        """Convert kilometers to miles."""
        return km * 0.621371
    
    @staticmethod
    def miles_to_km(miles: float) -> float:
        """Convert miles to kilometers."""
        return miles / 0.621371
    
    # ============================================================
    # Area Conversions
    # ============================================================
    
    @staticmethod
    def m2_to_ft2(m2: float) -> float:
        """Convert square meters to square feet."""
        return m2 * 10.7639
    
    @staticmethod
    def ft2_to_m2(ft2: float) -> float:
        """Convert square feet to square meters."""
        return ft2 / 10.7639
    
    @staticmethod
    def km2_to_miles2(km2: float) -> float:
        """Convert square kilometers to square miles."""
        return km2 * 0.386102
    
    @staticmethod
    def miles2_to_km2(miles2: float) -> float:
        """Convert square miles to square kilometers."""
        return miles2 / 0.386102
    
    @staticmethod
    def hectares_to_m2(hectares: float) -> float:
        """Convert hectares to square meters."""
        return hectares * 10000
    
    @staticmethod
    def m2_to_hectares(m2: float) -> float:
        """Convert square meters to hectares."""
        return m2 / 10000
    
    # ============================================================
    # Volume Conversions
    # ============================================================
    
    @staticmethod
    def m3_to_ft3(m3: float) -> float:
        """Convert cubic meters to cubic feet."""
        return m3 * 35.3147
    
    @staticmethod
    def ft3_to_m3(ft3: float) -> float:
        """Convert cubic feet to cubic meters."""
        return ft3 / 35.3147
    
    @staticmethod
    def m3_to_liters(m3: float) -> float:
        """Convert cubic meters to liters."""
        return m3 * 1000
    
    @staticmethod
    def liters_to_m3(liters: float) -> float:
        """Convert liters to cubic meters."""
        return liters / 1000
    
    @staticmethod
    def m3_to_gallons(m3: float) -> float:
        """Convert cubic meters to US gallons."""
        return m3 * 264.172
    
    @staticmethod
    def gallons_to_m3(gallons: float) -> float:
        """Convert US gallons to cubic meters."""
        return gallons / 264.172
    
    # ============================================================
    # Flow Rate Conversions
    # ============================================================
    
    @staticmethod
    def m3s_to_cfs(m3s: float) -> float:
        """Convert cubic meters per second to cubic feet per second."""
        return m3s * 35.3147
    
    @staticmethod
    def cfs_to_m3s(cfs: float) -> float:
        """Convert cubic feet per second to cubic meters per second."""
        return cfs / 35.3147
    
    @staticmethod
    def lps_to_m3s(lps: float) -> float:
        """Convert liters per second to cubic meters per second."""
        return lps / 1000
    
    @staticmethod
    def m3s_to_lps(m3s: float) -> float:
        """Convert cubic meters per second to liters per second."""
        return m3s * 1000
    
    @staticmethod
    def gpm_to_m3s(gpm: float) -> float:
        """Convert US gallons per minute to cubic meters per second."""
        return gpm * 0.00006309
    
    @staticmethod
    def m3s_to_gpm(m3s: float) -> float:
        """Convert cubic meters per second to US gallons per minute."""
        return m3s / 0.00006309
    
    # ============================================================
    # Temperature Conversions
    # ============================================================
    
    @staticmethod
    def celsius_to_fahrenheit(celsius: float) -> float:
        """Convert Celsius to Fahrenheit."""
        return celsius * 9/5 + 32
    
    @staticmethod
    def fahrenheit_to_celsius(fahrenheit: float) -> float:
        """Convert Fahrenheit to Celsius."""
        return (fahrenheit - 32) * 5/9
    
    @staticmethod
    def celsius_to_kelvin(celsius: float) -> float:
        """Convert Celsius to Kelvin."""
        return celsius + 273.15
    
    @staticmethod
    def kelvin_to_celsius(kelvin: float) -> float:
        """Convert Kelvin to Celsius."""
        return kelvin - 273.15
    
    # ============================================================
    # Pressure Conversions
    # ============================================================
    
    @staticmethod
    def pa_to_psi(pa: float) -> float:
        """Convert Pascals to PSI."""
        return pa * 0.000145038
    
    @staticmethod
    def psi_to_pa(psi: float) -> float:
        """Convert PSI to Pascals."""
        return psi / 0.000145038
    
    @staticmethod
    def pa_to_atm(pa: float) -> float:
        """Convert Pascals to atmospheres."""
        return pa / 101325
    
    @staticmethod
    def atm_to_pa(atm: float) -> float:
        """Convert atmospheres to Pascals."""
        return atm * 101325
    
    @staticmethod
    def bar_to_pa(bar: float) -> float:
        """Convert bar to Pascals."""
        return bar * 100000
    
    @staticmethod
    def pa_to_bar(pa: float) -> float:
        """Convert Pascals to bar."""
        return pa / 100000
    
    # ============================================================
    # Angle Conversions
    # ============================================================
    
    @staticmethod
    def deg_to_rad(degrees: float) -> float:
        """Convert degrees to radians."""
        return degrees * np.pi / 180
    
    @staticmethod
    def rad_to_deg(radians: float) -> float:
        """Convert radians to degrees."""
        return radians * 180 / np.pi
    
    @staticmethod
    def grad_to_deg(gradians: float) -> float:
        """Convert gradians to degrees."""
        return gradians * 0.9
    
    @staticmethod
    def deg_to_grad(degrees: float) -> float:
        """Convert degrees to gradians."""
        return degrees / 0.9
    
    # ============================================================
    # Concentration Conversions
    # ============================================================
    
    @staticmethod
    def ppm_to_percent(ppm: float) -> float:
        """Convert parts per million to percent."""
        return ppm / 10000
    
    @staticmethod
    def percent_to_ppm(percent: float) -> float:
        """Convert percent to parts per million."""
        return percent * 10000
    
    @staticmethod
    def ppm_to_mg_per_m3(ppm: float, molecular_weight: float, 
                         temperature: float = 25, pressure: float = 101325) -> float:
        """
        Convert ppm to mg/m³ for a gas.
        
        Args:
            ppm: Concentration in ppm
            molecular_weight: Molecular weight of gas (g/mol)
            temperature: Temperature (°C)
            pressure: Pressure (Pa)
            
        Returns:
            Concentration in mg/m³
        """
        # Ideal gas law: mg/m³ = (ppm * molecular_weight * pressure) / (gas_constant * temperature)
        gas_constant = 8.314  # J/(mol·K)
        temp_kelvin = temperature + 273.15
        
        return (ppm * molecular_weight * pressure) / (gas_constant * temp_kelvin * 10)
    
    # ============================================================
    # Coordinate Conversions
    # ============================================================
    
    @staticmethod
    def dd_to_dms(decimal_degrees: float) -> Tuple[int, int, float]:
        """
        Convert decimal degrees to degrees, minutes, seconds.
        
        Args:
            decimal_degrees: Decimal degrees
            
        Returns:
            Tuple of (degrees, minutes, seconds)
        """
        degrees = int(decimal_degrees)
        minutes_full = (decimal_degrees - degrees) * 60
        minutes = int(minutes_full)
        seconds = (minutes_full - minutes) * 60
        
        return degrees, minutes, seconds
    
    @staticmethod
    def dms_to_dd(degrees: int, minutes: int, seconds: float) -> float:
        """
        Convert degrees, minutes, seconds to decimal degrees.
        
        Args:
            degrees: Degrees
            minutes: Minutes
            seconds: Seconds
            
        Returns:
            Decimal degrees
        """
        return degrees + minutes/60 + seconds/3600
    
    @staticmethod
    def utm_to_latlon(easting: float, northing: float, zone: int, 
                      northern: bool = True) -> Tuple[float, float]:
        """
        Convert UTM coordinates to latitude/longitude (simplified).
        
        Args:
            easting: UTM easting (m)
            northing: UTM northing (m)
            zone: UTM zone number
            northern: True for northern hemisphere
            
        Returns:
            Tuple of (latitude, longitude) in decimal degrees
        """
        # Simplified conversion - for accurate results use pyproj
        # This is a placeholder for demonstration
        
        # Constants
        a = 6378137.0  # WGS84 semi-major axis
        f = 1/298.257223563  # WGS84 flattening
        k0 = 0.9996  # UTM scale factor
        
        # Calculations (simplified - for demonstration only)
        lat = (northing / 111320) - 0.0002 * np.sin(2 * northing / 111320)
        lon = (easting - 500000) / (111320 * np.cos(np.radians(lat))) + (zone * 6 - 183)
        
        return lat, lon
    
    # ============================================================
    # String Parsing
    # ============================================================
    
    @staticmethod
    def parse_coordinate(coord_str: str) -> float:
        """
        Parse coordinate string to decimal degrees.
        
        Supports formats:
        - DD.dddd
        - DD° MM.mmm'
        - DD° MM' SS.s"
        
        Args:
            coord_str: Coordinate string
            
        Returns:
            Decimal degrees
        """
        coord_str = coord_str.strip()
        
        # Try decimal degrees first
        try:
            return float(coord_str)
        except ValueError:
            pass
        
        # Try DD° MM.mmm'
        dms_pattern = r'(\d+)[°\s]\s*(\d+(?:\.\d+)?)[\'\s]'
        match = re.search(dms_pattern, coord_str)
        if match:
            degrees = float(match.group(1))
            minutes = float(match.group(2))
            return degrees + minutes/60
        
        # Try DD° MM' SS.s"
        dms_pattern2 = r'(\d+)[°\s]\s*(\d+)[\'\s]\s*(\d+(?:\.\d+)?)[\"\s]'
        match = re.search(dms_pattern2, coord_str)
        if match:
            degrees = float(match.group(1))
            minutes = float(match.group(2))
            seconds = float(match.group(3))
            return degrees + minutes/60 + seconds/3600
        
        raise ValueError(f"Could not parse coordinate: {coord_str}")


# Example usage
if __name__ == "__main__":
    print("="*60)
    print("CAVORA - Unit Conversions")
    print("="*60)
    
    conv = Conversions()
    
    # Time conversions
    years = 10.5
    print(f"\nTime Conversions:")
    print(f"  {years} years = {conv.years_to_days(years):.1f} days")
    print(f"  {years} years = {conv.years_to_seconds(years):.1e} seconds")
    
    # Length conversions
    meters = 100
    print(f"\nLength Conversions:")
    print(f"  {meters} m = {conv.m_to_ft(meters):.2f} ft")
    print(f"  {meters} m = {conv.m_to_mm(meters):.0f} mm")
    
    # Flow rate conversions
    m3s = 5.0
    print(f"\nFlow Rate Conversions:")
    print(f"  {m3s} m³/s = {conv.m3s_to_cfs(m3s):.2f} cfs")
    print(f"  {m3s} m³/s = {conv.m3s_to_gpm(m3s):.0f} gpm")
    
    print("\n" + "="*60)
