from typing import Union

# Autogenerated with SMOP 0.29
#Modified by Adam
import numpy as np
import math
from geomaglib import geoid
import calendar
import datetime as dt


def geod_to_geoc_lat(lat: float, alt: float) -> tuple[float, float]:
    # [r, theta] = geod2geoc(alpha, h);
    # [r, theta, B_r, B_theta] = geod2geoc(alpha, h, X, Z);
    # conversion from geodetic X,Z components to geocentric B_r, B_theta
    # Input:   geodetic latitude lat (deg)
    #          altitude alt [km]
    # Output:  theta (deg)
    #          r (km)

    # Nils Olsen, DSRI Copenhagen, September 2001.
    # After Langel (1987), eq. (52), (53), (56), (57)
    # Converted to python by Adam (2017)

    # Latest changes: NIO, 170903 optional conversion of magnetic components
    #                 Update to WGS-84 ellipsoid

    # WGS-84 Ellipsoid parameters
    a = 6378.137
    f = 1 / 298.257223563
    b = a * (1 - f)
    lat_rad = np.radians(lat)
    sin_alpha_2 = np.sin(lat_rad) ** 2
    cos_alpha_2 = np.cos(lat_rad) ** 2
    tmp = np.multiply(alt, np.sqrt(np.dot(a ** 2, cos_alpha_2) + np.dot(b ** 2, sin_alpha_2)))
    beta = np.arctan(np.multiply((tmp + b ** 2) / (tmp + a ** 2), np.tan(lat_rad)))
    theta = np.rad2deg(beta)
    r = np.sqrt(alt ** 2 + np.dot(2, tmp) + np.dot(a ** 2, (1 - np.dot((1 - (b / a) ** 4), sin_alpha_2))) / (
                1 - np.dot((1 - (b / a) ** 2), sin_alpha_2)))

    return r, theta


def alt_to_ellipsoid_height(alt: Union[float, np.float64, list, np.ndarray], lat: Union[float, np.float64, list, np.ndarray], lon: Union[float, np.float64, list, np.ndarray]) -> np.ndarray:
    """
    This function converts altitude MSL in kilometers to ellipsoid height in
    kilometers

    Parameters:
    alt (int or float): The original altitude in kilometers in MSL
    lat (int or float): The latitude degree from -90 to 90
    lon (int or float): The longitude degree from -180 to 360

    Returns:
    (float): Height above ellipsoid in kilometers
    """
    if np.isscalar(lat):
        lat = np.array([lat])
    if np.isscalar(lon):
        lon = np.array([lon])
    if np.isscalar(alt):
        alt = np.array([alt])

    if isinstance(lat, list):
        lat = np.array(lat)
    if isinstance(lon, list):
        lon = np.array(lon)
    if isinstance(alt, list):
        alt = np.array(alt)
    offset_x = np.zeros_like(lon, dtype=float)
    offset_y = np.zeros_like(lon, dtype=float)
    lon_mask = lon < 0
    offset_x[lon_mask] = (lon[lon_mask] + 360) * geoid.geoid["scale_factor"]
    offset_x[~lon_mask] = lon[~lon_mask] * geoid.geoid["scale_factor"]   
    # if lon < 0.0:
    #     offset_x = (lon + 360) * geoid.geoid["scale_factor"]
    # else:
    #     offset_x = lon * geoid.geoid["scale_factor"]

    offset_y = (90 - lat) * geoid.geoid["scale_factor"]

    # post_x = int(np.floor(offset_x))
    # if post_x + 1 == geoid.geoid["cols"]:
    #     post_x = post_x - 1

    # post_y = int(np.floor(offset_y))
    # if post_y + 1 == geoid.geoid["rows"]:
    #     post_y = post_y - 1

    post_x = np.floor(offset_x).astype(int)
    post_x = np.where(post_x + 1 == geoid.geoid["cols"], post_x - 1, post_x)

    post_y = np.floor(offset_y).astype(int)
    post_y = np.where(post_y + 1 == geoid.geoid["rows"], post_y - 1, post_y)

    index = np.asarray(post_y * geoid.geoid["cols"] + post_x)

    elevation_NW = np.array([geoid.geoid["height_buffer"][i] for i in index])
    elevation_NE = np.array([geoid.geoid["height_buffer"][i + 1] for i in index])
    index = (post_y + 1) * geoid.geoid["cols"] + post_x
    elevation_SW = np.array([geoid.geoid["height_buffer"][i] for i in index])
    elevation_SE = np.array([geoid.geoid["height_buffer"][i + 1] for i in index])

    
    # elevation_SW = geoid.geoid["height_buffer"][index]
    # elevation_SE = geoid.geoid["height_buffer"][index + 1]

    delta_x = offset_x - post_x
    delta_y = offset_y - post_y

    upper_y = elevation_NW + delta_x * (elevation_NE - elevation_NW)
    lower_y = elevation_SW + delta_x * (elevation_SE - elevation_SW)

    delta_height = upper_y + delta_y * (lower_y - upper_y)

    # delta_height is in meters need to convert to kilometers
    ellipsoid_height = alt + delta_height / 1000

    return ellipsoid_height

def calc_dec_year(year: int, month: int, day:int ) -> float:
    """
    Takes year, month, and day and calculates the decimal year from those inputs

    Parameters:
    year (int): The year fully written for example 2024
    month (int): The month from 1-12 where is 1 is January and 12 is December
    day (int): The day of the month from 1-31

    Returns:
    (float): The decimal year

    """
    num_days_year = 365
    if calendar.isleap(year):
        num_days_year = 366
    date = dt.datetime(year, month, day)
    day_of_year = date.timetuple().tm_yday
    return year + ((day_of_year - 1) / num_days_year)

def calc_dec_year_array(year: np.ndarray, month: np.ndarray, day:np.ndarray ) -> np.ndarray:
    """
    Takes year, month, and day and calculates the decimal year from those inputs

    Parameters:
    year (int): The year fully written for example 2024
    month (int): The month from 1-12 where is 1 is January and 12 is December
    day (int): The day of the month from 1-31

    Returns:
    (float): The decimal year
    adjusted calc_dec_year to accept vectors by Collin 01/25 
    """
    dec_year = []
    # if(year.size == 1):
    #     num_days_year = 365
    #     if calendar.isleap(year):
    #         num_days_year = 366
        
    #     date = dt.datetime(year, month, day)
    #     day_of_year = date.timetuple().tm_yday
    #     dec_year.append(year + ((day_of_year - 1) / num_days_year))
    # else:
        
    for i in range(0,year.size):
        num_days_year = 365
        if calendar.isleap(year[i]):
            num_days_year = 366
        
        date = dt.datetime(year[i], month[i], day[i])
        day_of_year = date.timetuple().tm_yday
        dec_year.append(year[i] + ((day_of_year - 1) / num_days_year))

    return np.array(dec_year)
def decimalYearToDateTime(dyear:float) ->tuple[float, float, float, float, float]:

    beginYear = dt.datetime(int(dyear),1,1)
    yearFrac = dyear - int(dyear)
    numDays = (dt.date(int(dyear)+1,1,1)-dt.date(int(dyear),1,1)).days
    theDate = dt.timedelta(days=yearFrac*numDays) + beginYear
    return theDate.year, theDate.month, theDate.day, theDate.hour, theDate.minute

def jd2000(year: int, month: int, day:int , ut: int, minutes: int) -> float:
    """
    Compute the julian day with offset 2000
    Args:
        year: yyyy
        month: 1 -12 month
        day: The day of the month
        ut: The hour from 1- 24
        minutes: The minutes in an hour 0 - 60

    Returns:
        The number of day from 0000 offset by the day since 2000
    """

    offset = dt.datetime(2000,1,1)

    diff = dt.datetime(year, month, day, ut, minutes) - offset

    total_days = diff.days + diff.seconds / (3600.0 * 24)

    return total_days
