#!/usr/bin/env python3
"""
Enhanced UTM ED50 to WGS84 Coordinate Converter

This script converts UTM coordinates in 6-degree zones from ED50 (European Datum 1950)
reference system to WGS84 format. Supports both CSV and JSON input files.

Requirements:
    pip install pyproj pandas

Usage:
    python utm_ed50_to_wgs84_converter_enhanced.py input_file.csv output_file.csv
    python utm_ed50_to_wgs84_converter_enhanced.py input_file.json output_file.json
    python utm_ed50_to_wgs84_converter_enhanced.py --interactive
"""

import argparse
import pandas as pd
import pyproj
import json
import os
from typing import Tuple, List, Optional, Union
import sys


class UTMED50ToWGS84Converter:
    """Converter for UTM ED50 coordinates to WGS84."""
    
    def __init__(self):
        """Initialize the converter with ED50 and WGS84 projections."""
        self.ed50_utm_projections = {}
        self.wgs84_utm_projections = {}
        
    def get_ed50_utm_projection(self, zone: int, northern: bool = True) -> pyproj.Proj:
        """Get ED50 UTM projection for a specific zone."""
        key = (zone, northern)
        if key not in self.ed50_utm_projections:
            hemisphere = 'N' if northern else 'S'
            proj_string = f"+proj=utm +zone={zone} +ellps=intl +towgs84=-87,-98,-121,0,0,0,0 +units=m +no_defs"
            self.ed50_utm_projections[key] = pyproj.Proj(proj_string)
        return self.ed50_utm_projections[key]
    
    def get_wgs84_utm_projection(self, zone: int, northern: bool = True) -> pyproj.Proj:
        """Get WGS84 UTM projection for a specific zone."""
        key = (zone, northern)
        if key not in self.wgs84_utm_projections:
            hemisphere = 'N' if northern else 'S'
            proj_string = f"+proj=utm +zone={zone} +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
            self.wgs84_utm_projections[key] = pyproj.Proj(proj_string)
        return self.wgs84_utm_projections[key]
    
    def convert_single_point(self, easting: float, northing: float, zone: int, 
                           northern: bool = True) -> Tuple[float, float]:
        """
        Convert a single UTM ED50 point to WGS84 lat/lon.
        
        Args:
            easting: UTM easting coordinate in meters
            northing: UTM northing coordinate in meters
            zone: UTM zone number (1-60)
            northern: True if in northern hemisphere, False if southern
            
        Returns:
            Tuple of (latitude, longitude) in WGS84 decimal degrees
        """
        if not (1 <= zone <= 60):
            raise ValueError(f"Invalid UTM zone: {zone}. Must be between 1 and 60.")
        
        # Create ED50 UTM projection
        ed50_proj = self.get_ed50_utm_projection(zone, northern)
        
        # Create WGS84 lat/lon projection
        wgs84_latlon = pyproj.Proj('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')
        
        # Create transformer from ED50 UTM to WGS84 lat/lon
        transformer = pyproj.Transformer.from_proj(ed50_proj, wgs84_latlon)
        
        # Transform coordinates directly to WGS84 lat/lon
        wgs84_lon, wgs84_lat = transformer.transform(easting, northing)
        
        return wgs84_lat, wgs84_lon
    
    def convert_dataframe(self, df: pd.DataFrame, easting_col: str, northing_col: str, 
                         zone_col: str, northern_col: Optional[str] = None,
                         northern_default: bool = True) -> pd.DataFrame:
        """
        Convert UTM ED50 coordinates in a DataFrame to WGS84.
        
        Args:
            df: Input DataFrame with UTM coordinates
            easting_col: Column name for easting coordinates
            northing_col: Column name for northing coordinates
            zone_col: Column name for UTM zone
            northern_col: Column name for northern hemisphere flag (optional)
            northern_default: Default value for northern hemisphere if column not provided
            
        Returns:
            DataFrame with additional lat/lng columns and renamed description column
        """
        result_df = df.copy()
        result_df['lat'] = None
        result_df['lng'] = None
        
        # Rename description column to name if it exists
        if 'description' in result_df.columns:
            result_df = result_df.rename(columns={'description': 'name'})
        
        for idx, row in df.iterrows():
            try:
                easting = float(row[easting_col])
                northing = float(row[northing_col])
                zone = int(row[zone_col])
                
                if northern_col and northern_col in row:
                    northern = bool(row[northern_col])
                else:
                    northern = northern_default
                
                lat, lon = self.convert_single_point(easting, northing, zone, northern)
                result_df.at[idx, 'lat'] = lat
                result_df.at[idx, 'lng'] = lon
                
            except (ValueError, KeyError) as e:
                print(f"Warning: Could not convert row {idx}: {e}")
                result_df.at[idx, 'lat'] = None
                result_df.at[idx, 'lng'] = None
        
        return result_df


def detect_file_format(file_path: str) -> str:
    """Detect if file is CSV or JSON based on extension and content."""
    _, ext = os.path.splitext(file_path.lower())
    
    if ext == '.csv':
        return 'csv'
    elif ext == '.json':
        return 'json'
    else:
        # Try to detect by reading first few lines
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                first_line = f.readline().strip()
                if first_line.startswith('[') or first_line.startswith('{'):
                    return 'json'
                else:
                    return 'csv'
        except:
            return 'csv'  # Default to CSV


def load_data(file_path: str, csv_separator: str = ';', decimal_separator: str = ',') -> pd.DataFrame:
    """Load data from CSV or JSON file."""
    file_format = detect_file_format(file_path)
    
    if file_format == 'json':
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        if isinstance(data, list):
            return pd.DataFrame(data)
        elif isinstance(data, dict):
            return pd.DataFrame([data])
        else:
            raise ValueError("JSON file must contain a list or dictionary")
    
    else:  # CSV
        return pd.read_csv(file_path, sep=csv_separator, decimal=decimal_separator)


def save_data(df: pd.DataFrame, file_path: str, file_format: str = None, csv_separator: str = ';', decimal_separator: str = ','):
    """Save data to CSV or JSON file."""
    if file_format is None:
        file_format = detect_file_format(file_path)
    
    if file_format == 'json':
        # Convert DataFrame to list of dictionaries
        data = df.to_dict('records')
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
    
    else:  # CSV
        df.to_csv(file_path, index=False, sep=csv_separator, decimal=decimal_separator)


def interactive_mode():
    """Run the converter in interactive mode."""
    converter = UTMED50ToWGS84Converter()
    
    print("UTM ED50 to WGS84 Coordinate Converter")
    print("=" * 40)
    print("Enter coordinates (type 'quit' to exit)")
    print()
    
    while True:
        try:
            # Get input
            easting_input = input("Enter easting (meters): ").strip()
            if easting_input.lower() == 'quit':
                break
            
            northing_input = input("Enter northing (meters): ").strip()
            if northing_input.lower() == 'quit':
                break
            
            zone_input = input("Enter UTM zone (1-60): ").strip()
            if zone_input.lower() == 'quit':
                break
            
            hemisphere_input = input("Enter hemisphere (N/S) [default: N]: ").strip().upper()
            if hemisphere_input == 'QUIT':
                break
            
            # Parse inputs
            easting = float(easting_input)
            northing = float(northing_input)
            zone = int(zone_input)
            northern = hemisphere_input != 'S' if hemisphere_input else True
            
            # Convert
            lat, lon = converter.convert_single_point(easting, northing, zone, northern)
            
            print(f"\nWGS84 Coordinates:")
            print(f"Latitude:  {lat:.8f}°")
            print(f"Longitude: {lon:.8f}°")
            print("-" * 40)
            
        except ValueError as e:
            print(f"Error: {e}")
            print("Please enter valid numeric values.")
        except KeyboardInterrupt:
            print("\nExiting...")
            break


def main():
    """Main function to handle command line arguments and file processing."""
    parser = argparse.ArgumentParser(
        description="Convert UTM ED50 coordinates to WGS84 format (supports CSV and JSON)",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Interactive mode
  python utm_ed50_to_wgs84_converter_enhanced.py --interactive
  
  # Convert CSV file (semicolon-separated by default)
  python utm_ed50_to_wgs84_converter_enhanced.py input.csv output.csv
  
  # Convert CSV file with comma separator
  python utm_ed50_to_wgs84_converter_enhanced.py input.csv output.csv --separator ,
  
  # Convert JSON file
  python utm_ed50_to_wgs84_converter_enhanced.py input.json output.json
  
  # Convert with custom column names
  python utm_ed50_to_wgs84_converter_enhanced.py input.csv output.csv --easting-col X --northing-col Y --zone-col ZONE
  
  # Using full paths
  python /full/path/to/script.py /full/path/to/input.csv /full/path/to/output.csv
        """
    )
    
    parser.add_argument('input_file', nargs='?', help='Input file with UTM coordinates (CSV or JSON)')
    parser.add_argument('output_file', nargs='?', help='Output file for WGS84 coordinates (CSV or JSON)')
    parser.add_argument('--interactive', '-i', action='store_true', 
                       help='Run in interactive mode')
    parser.add_argument('--easting-col', default='easting', 
                       help='Column name for easting coordinates (default: easting)')
    parser.add_argument('--northing-col', default='northing', 
                       help='Column name for northing coordinates (default: northing)')
    parser.add_argument('--zone-col', default='zone', 
                       help='Column name for UTM zone (default: zone)')
    parser.add_argument('--northern-col', 
                       help='Column name for northern hemisphere flag (optional)')
    parser.add_argument('--northern-default', action='store_true', default=True,
                       help='Default value for northern hemisphere (default: True)')
    parser.add_argument('--separator', '-s', default=';',
                       help='CSV separator character (default: ;)')
    parser.add_argument('--decimal', '-d', default=',',
                       help='CSV decimal separator character (default: ,)')
    
    args = parser.parse_args()
    
    if args.interactive:
        interactive_mode()
        return
    
    if not args.input_file or not args.output_file:
        parser.error("Both input_file and output_file are required when not in interactive mode")
    
    try:
        # Check if input file exists
        if not os.path.exists(args.input_file):
            print(f"Error: Input file '{args.input_file}' not found")
            print(f"Current working directory: {os.getcwd()}")
            sys.exit(1)
        
        # Load data
        print(f"Reading input file: {args.input_file}")
        df = load_data(args.input_file, args.separator, args.decimal)
        
        # Check required columns
        required_cols = [args.easting_col, args.northing_col, args.zone_col]
        missing_cols = [col for col in required_cols if col not in df.columns]
        if missing_cols:
            print(f"Error: Missing required columns: {missing_cols}")
            print(f"Available columns: {list(df.columns)}")
            sys.exit(1)
        
        # Convert coordinates
        print("Converting coordinates...")
        converter = UTMED50ToWGS84Converter()
        result_df = converter.convert_dataframe(
            df, 
            args.easting_col, 
            args.northing_col, 
            args.zone_col,
            args.northern_col,
            args.northern_default
        )
        
        # Save output
        print(f"Saving output file: {args.output_file}")
        save_data(result_df, args.output_file, csv_separator=args.separator, decimal_separator=args.decimal)
        
        # Print summary
        total_rows = len(result_df)
        successful_conversions = result_df['lat'].notna().sum()
        print(f"\nConversion complete!")
        print(f"Total rows: {total_rows}")
        print(f"Successful conversions: {successful_conversions}")
        print(f"Failed conversions: {total_rows - successful_conversions}")
        
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main() 