#####################################################
# @module    file_utils.py                          #
# @brief     Common unit handling tools             #
# @author    Original Code form multiple authors    #
# @date      Sept 2020                              #
# @copyright Racelogic Ltd., 2020                   #
#####################################################

from math import cos, sin, sqrt, atan2

class Location_MinE5:
    def __init__(self, minE5=None):
        if not minE5 is None:
            self.lat = minE5.lat
            self.lng = minE5.lng
            self.alt_m = minE5.alt_m
        else:
            self.lat = None
            self.lng = None
            self.alt_m = None

class Location_Rads:
    def __init__(self, minE5=None):
        if not minE5 is None:
            self.lat = Unit_Conv.minE5_to_rad(minE5.lat)
            self.lng = Unit_Conv.minE5_to_rad(minE5.lng)
            self.alt_m = minE5.alt_m
        else:
            self.lat = None
            self.lng = None
            self.alt_m = None

class Location_ECEF:
    WGS_84_ELLIPSOID_A = 6378137.0
    WGS_84_ELLIPSOID_B = 6356752.314245
    WGS_84_ECC_SQUARED = 0.00669437999019741354678198566736

    def __init__(self):
        self.x = None
        self.y = None
        self.z = None

    def from_rads(self, rads):
        sin_lat = sin(rads.lat)
        cos_lat = cos(rads.lat)
        sin_lng = sin(rads.lng)
        cos_lng = cos(rads.lng)
        chi = sqrt(1.0 - (type(self).WGS_84_ECC_SQUARED * sin_lat * sin_lat))
        self.x = ((type(self).WGS_84_ELLIPSOID_A / chi) + rads.alt_m) * cos_lat * cos_lng
        self.y = ((type(self).WGS_84_ELLIPSOID_A / chi) + rads.alt_m) * cos_lat * sin_lng
        self.z = (type(self).WGS_84_ELLIPSOID_A * (1.0 - type(self).WGS_84_ECC_SQUARED)/chi + rads.alt_m) * sin_lat

class Location_ECEF_Ref:
    def __init__(self):
        self.x = None
        self.y = None
        self.z = None
        self.phi = None
        self.sin_phi = None
        self.cos_phi = None

    def from_ecef(self, ecef):
        self.x = ecef.x
        self.y = ecef.y
        self.z = ecef.z
        self.phi = atan2(self.z, sqrt((self.x * self.x) + (self.y * self.y)))
        self.sin_phi = sin(self.phi)
        self.cos_phi = cos(self.phi)

    def from_minE5(self, minE5):
        self.from_ecef(Location_ECEF().from_rads(Location_Rads(minE5))) #Potentially Broken

def valid_lat_minE5(lat_minE5):
    return True if lat_minE5 <= 540000000 and lat_minE5 >= -540000000 else False

def valid_lng_minE5(lng_minE5):
    return True if lng_minE5 <= 1080000000 and lng_minE5 >= -1080000000 else False

class Unit_Conv:
    @staticmethod
    def dist(m=None, ft=None):
        if m is not None:
            return m * 3.28084
        if ft is not None:
            return ft * 0.3048

    @staticmethod
    def speed(kmh=None, mph=None):
        if kmh is not None:
            return kmh * 0.621371
        if mph is not None:
            return mph * 1.60934

    @staticmethod
    def mps_to_units(units, speed):
        if units == 'km/h':
            speed = speed * 3.6
        elif units == 'mph':
            speed = speed * 2.2369362921
        else:
            raise TypeError('unsupported sys units: ' + str(units))
        return speed

    @staticmethod
    def mps_to_mmps(mps):
        return int(0.5 + (mps * 1000.0))

    @staticmethod
    def m_to_mm(metres):
        return int(0.5 + (metres * 1000.0))

    @staticmethod
    def miles_to_m(miles):
        return (miles * 1609.344)

    @staticmethod
    def kts_to_mps(knts):
        return (knts * 0.51444444444)

    @staticmethod
    def mps2_to_mg(mps2):
        return int((mps2 * 100.019716213) + (0.5 if mps2 >= 0.0 else -0.5))

    @staticmethod
    def mps2_to_g(mps2):
        return (mps2 * 0.1019716213)

    @staticmethod
    def deg_to_degE7(degs):
        return int((degs * 10000000.0) + (0.5 if degs >= 0.0 else -0.5))

    @staticmethod
    def deg_to_minE5(degs):
        return int((degs * 6000000.0) + (0.5 if degs >= 0.0 else -0.5))

    @staticmethod
    def degE7_to_deg(degE7):
        return float(degE7) / 10000000.0

    @staticmethod
    def min_to_minE5(minutes):
        return int((minutes * 100000.0) + (0.5 if minutes >= 0.0 else -0.5))

    @staticmethod
    def minE5_to_min_str(minE5):
        val = abs(minE5)
        i = val // 100000
        f = val - (i * 100000)
        if minE5 < 0:
            i *= -1
        return '{}.{}'.format(i, f)

    @staticmethod
    def minE5_to_min(minE5):
        if ((minE5 == None) or (minE5 == 2147483647)):
            return float('NaN')
        else:
            return (float(minE5) / 100000.0)

    @staticmethod
    def minE5_to_deg(minE5):
        if ((minE5 == None) or (minE5 == 2147483647)):
            return float('NaN')
        else:
            return (float(minE5) / 6000000.0)

    @staticmethod
    def minE5_to_rad(minE5):
        return (minE5 * (3.1415926535897932384626433832795 / 1080000000.0))