import math
import doctest

# ========================================================
# ========================================================
# === ANGLE
# ========================================================
# ========================================================

class Angle:
    """ Class to convert angles for astronomy

    Angle formats are:
    
        * deg = degrees. e.g.
        * rad = radian. e.g.
    
    :Usage:

    First, instanciate an object from the class:
    
    ::
    
        angle = Angle()

    Second, assign a date in any angle format:
    
    ::
    
        angle.angle("-0d3m28.56s")

    Third, get the converted angle:    

    ::

        rad = angle.rad()
        deg = angle.deg()
        arcmin = angle.arcmin()
        arcsec = angle.arcsec()
        dms = angle.dms()
        uspzad = angle.sexagesimal(sexagesimal_format)

    :Informations:

    ::
    
        help(Angle)
        Angle().infos("doctest")
        Angle().infos("doc_methods")
    """
# ========================================================
# === attributs
# ========================================================

    # --- multiplicators for units
    _r2d = 180.0/math.pi
    _d2r = 1./_r2d    
    _r2h = _r2d/15.0
    _h2r = 1./_r2h
    _r2m = _r2d*60.0
    _m2r = 1./_r2m
    _r2s = _r2d*3600.0
    _s2r = 1./_r2s
    _r2mh = _r2m/15.0
    _mh2r = 1./_r2mh
    _r2sh = _r2s/15.0
    _sh2r = 1./_r2sh

    # --- correspondances between units and multiplicator ->r
    _u2r = {}
    _u2r["R"] = 1
    _u2r["D"] = _d2r
    _u2r["M"] = _m2r
    _u2r["S"] = _s2r
    _u2r["H"] = _h2r
    _u2r["MH"] = _mh2r
    _u2r["SH"] = _sh2r
    
# ========================================================
# === internal methods
# ========================================================

    def _init(self,angle=""):
        """ Initialize internal attributes.

        :param angle: angle is an angle in any supported format (cf. help(Angle))
        :type angle: str
        
        :Usage:

        ::
            objangle = Angle()
            objangle._init()
        """
        self._init_angle = angle
        self._init_angleformat = []
        self._computed_rad = 0
        self._angle_redefined()        
        self._computed_sexagesimal_format = "D.3"
        self._rad = 0

    def _angle_redefined(self):
        """ Initialize internal attributes when a radian angle is just defined or redefined.
        """
        self._computed_deg = 0
        self._computed_arcmin = 0
        self._computed_arcsec = 0
        self._computed_sexagesimal = 0
        self._deg = 0
        self._arcmin = 0
        self._arcsec = 0
        self._sexagesimal = 0
        self._cos_rad_modulo = math.nan
        self._sin_rad_modulo = math.nan

    def _is_number(self,s):
        """ Return True if the string is a number else return False.

        :param s: a value to test
        :type s: str
        
        :Usage:

        >>> objangle = Angle()
        >>> objangle._is_number("3e5")
        True
        >>> objangle._is_number("3a5")
        False
        """
        try:
            float(s)
            return True
        except ValueError:
            pass 
        try:
            import unicodedata
            unicodedata.numeric(s)
            return True
        except (TypeError, ValueError):
            pass 
        return False

    def _angle_compare(self, angle, operator):
        """ Comparaison of angles for various operators.

        :param angle: An angle in any supported format (cf. help(Angle))
        :type angle: Angle()
        :param operator: Operator such as == != > >= < <=
        :type operator: string
        :returns: The logic result of the comparison.
        :rtype: bool
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1._angle_compare(objangle2,">")
        12.345
        '56d28m'
        False
        
        .. note:: Does not account for the modulo.
        """
        if isinstance(angle, Angle) == False:
            angle =  Angle(angle)
        if self._computed_rad == 0:
            self.rad()
        if angle._computed_rad == 0:
            angle.rad()
        res = False
        if (self._computed_rad == 1) and (angle._computed_rad == 1):
            toeval = str(self._rad)+" "+operator+" "+str(angle._rad)
            res = eval(toeval)
        return res
    
# ========================================================
# === angle methods
# ========================================================

    def angle_angle2rad(self,angle):
        """ Compute radian from any angle format

        :param angle: angle is an angle in any supported format (cf. help(Angle))
        :type angle: str
        
        :Usage:
        
        >>> objangle = Angle()
        >>> objangle.angle_angle2rad("-57d45m34s")
        ([('-57', 'D'), ('45', 'M'), ('34', 'S')], -1.0080924796783026)
        
        First integer is used to check the recognized input date format:
        0 = Error, format not known
        
        :Related topics:

        Prefer using objangle.angle() followed by objangle.rad()            
        """
        str_angle = str(angle).upper()  
        #print("str_angle = "+str_angle)
        cars = "+-.0123456789"
        # --- eliminate leading characters before the first numeric cheracter
        k = 0
        for car in str_angle:
            if (car in cars):
                break
            k += 1
        if (k==0):
            # problem, no valid numeric value found.
            pass
        str_angle = str_angle[k:]
        # --- 
        # "-0   45 ' 34 sec"
        # angle_numb = [ "-0", "45", "34" ]
        # angle_numb = [ "   ", " ' ", " sec" ]
        ind_numb = -1
        ind_unit = -1       
        angle_numbs = []
        angle_units = []
        knumb = 0
        kunit = 0
        for car in str_angle:
            if (car in cars) or (car=='E' and knumb>0):
                # --- numeric case for value
                if (knumb==0):
                    ind_numb += 1
                    angle_numbs.append('')
                angle_numbs[ind_numb] += car
                knumb += 1
                kunit = 0
            else:
                # --- alpha case for units
                if (kunit==0):
                    ind_unit += 1
                    angle_units.append('')
                if ( (car!=" ") and (car!=":") ):
                    if (car=="'"):
                        car = "M"
                    if (car=='"'):
                        car = "S"
                    angle_units[ind_unit] += car
                kunit += 1
                knumb = 0        
        # --- reformat units
        #print("angle_numbs={}".format(angle_numbs))
        #print("angle_units={}".format(angle_units))
        n_numb = len(angle_numbs)
        n_unit = len(angle_units)
        tmps = [];
        for angle_unit in angle_units:
            if angle_unit == "":
                angle_unit = ' '
            tmps.append(angle_unit)
        angle_units = tmps.copy()
        # --- simplify units
        angle_fnumbs = []
        angle_funits = []
        if (n_unit==0) and (n_numb==0):
            angle_fnumbs.append("0")
            angle_funits.append("R")
        elif (n_unit==0):
            angle_fnumbs.append(angle_numbs[0])
            angle_funits.append("D")
        elif (n_unit==1):
            angle_fnumbs.append(angle_numbs[0])
            u1 = angle_units[0]
            if (u1[0]=="R"):            
                angle_funits.append("R")
            elif (u1[0]=="M"):            
                angle_funits.append("M")
                if (n_numb==2):
                    angle_funits.append("S")
                    angle_fnumbs.append(angle_numbs[1])
            elif (u1[0]=="S"):
                angle_funits.append("S")
            elif (u1[0]=="H"):
                angle_funits.append("H")
                if (n_numb==2):
                    angle_funits.append("MH")
                    angle_fnumbs.append(angle_numbs[1])
            else:
                angle_funits.append("D")                
                if (n_numb==2):
                    angle_funits.append("M")
                    angle_fnumbs.append(angle_numbs[1])
        elif (n_numb==2):
            # cases DM or MS HM
            angle_fnumbs.append(angle_numbs[0])
            angle_fnumbs.append(angle_numbs[1])
            u1 = angle_units[0]
            u2 = angle_units[1]
            if (u1[0]=="M"):
                angle_funits.append("M")
                angle_funits.append("S")
            elif (u1[0]=="H" and u2[0]=="S"):
                angle_funits.append("H")
                angle_funits.append("SH")
            elif (u1[0]=="H"):
                angle_funits.append("H")
                angle_funits.append("MH")
            else:
                angle_funits.append("D")
                angle_funits.append("M")
        elif (n_numb==3):
            # cases DMS or HMS 
            angle_fnumbs.append(angle_numbs[0])
            angle_fnumbs.append(angle_numbs[1])
            angle_fnumbs.append(angle_numbs[2])
            u1 = angle_units[0]
            if (u1[0]=="H"):
                angle_funits.append("H")
                angle_funits.append("MH")
                angle_funits.append("SH")
            else:
                angle_funits.append("D")
                angle_funits.append("M")
                angle_funits.append("S")
        # --- compute the angle
        if (angle_fnumbs=="."):
            angle_fnumbs="0."
        #print(angle_fnumbs)
        #print(angle_funits)
        init_angleformat = list ( zip (angle_fnumbs, angle_funits) )
        sign = 1
        k=0
        rad = 0
        for init_angle in init_angleformat:
            numb = init_angle[0]
            unit = init_angle[1]
            if (k==0) and ('-' in numb):
                sign = -1
            fnumb = math.fabs(float(numb))
            mult = self._u2r[unit]
            rad += fnumb*mult
        rad *= sign
        self._rad = rad
        self._rad_modulo_2pi = math.fmod(rad,2*math.pi)
        if (self._rad_modulo_2pi<0):
            self._rad_modulo_2pi += (2*math.pi)
        return init_angleformat, rad

    def angle_rad2deg(self,rad):
        """ Compute a angle in degrees from a angle in radian
        
        :param rad: rad is an angle in radian.
        :type rad: float
        
        :Usage:

        >>> objangle = Angle()
        >>> objangle.angle_rad2deg(1)
        (0, 57.29577951308232)
        
        First integer is an error code. 0 = no problem.
            
        :Related topics:

        Prefer using objangle.angle() followed by objangle.deg()            
        """
        error = 0
        res = rad * self._r2d
        return error, res

    def angle_rad2sexagesimal(self,rad, sexagesimal_format):
        """ Compute a sexagesimal format string from radian
        
        :param rad: rad is an angle in radian
        :type rad: float
        :param sexagesimal_format: The uspzad format (see details below)
        :type sexagesimal_format: str

        Sexagesimal format:
            * u (unit) = h,H,d,D (default=D). Capital mean module [0:360[, lower case means module [-180:180[ 
            * s (separator) = " ",:,"" (default="" means letters hms or dms)
            * p (plus/minus) = +,"" (default="")
            * z (zeros) = 0,"" (default="")
            * a (angle_limits) = "",90, (+/-limit if unit D,H, default="" means 360)
            * d (sec_digits) = "",".1",".2",... (default="")
        
        Style 1:
            * To Display a R.A.: "H0.2" =>  23h07m42.49s
            * To Display a Decl.: "d+090.1" => +00d34m22.6s
            * To Display a H.A.: "h0.2" => -08h43m16.05s
        
        Style 2:
            * To Display a R.A.: "H 0.2" =>  23 07 42.49
            * To Display a Decl.: "d +090.1" => -00 34 22.6
            * To Display a H.A.: "h 0.2" => -08 43 16.05
        
        Style 3:
            * To Display a R.A.: "H:0.2" =>  23:07:42.49
            * To Display a Decl.: "d:+090.1" => -00:34:22.6
            * To Display a H.A.: "h:0.2" => -08:43:16.05
                
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle_rad2sexagesimal(-0.01,"d:-090.1")
        (0, '-00:34:22.6')
        
        First integer is used to check the recognized input date format:
        0 = Error, format not known
        
        :Related topics:

        Prefer using objangle.angle() followed by objangle.sexagesimal()            
        """
        symbols = str(sexagesimal_format)       
        digits = ""
        unit=0; # 0=deg 1=hours
        separator=0; # 0=hdms 1=space 2=:
        modulo=0; # 0=360 1=180
        sign=0; # 0=[0:modulo] 1=[-modulo/2:modulo/2]
        zeros=0; # 0=spaces 1=leading zeros
        k=0
        for car in symbols:
            if (car=='D'): 
                unit=0
                modulo=0
            if (car=='d'): 
                unit=0
                modulo=1
            if (car=='H'): 
                unit=1 
                modulo=0
            if (car=='h'):
                unit=1 
                modulo=1
            if (car==' '): 
                separator=1
            if (car=='_'): 
                separator=1
            if (car==':'): 
                separator=2
            if (car=='+'): 
                sign=1
            if (self._is_number(car)) or car=='.': 
                digits = symbols[k:]
                break
            k += 1
        #	int unit=0; // 0=deg 1=hours
        #	int modulo=0; // 0=360 1=180
        #	int separator=0; // 0=hdms 1=space 2=:
        #	int sign=0; // 0=[0:modulo] 1=[-modulo/2:modulo/2]
        # === trailing format (digits)
        angle_limit = 360
        nb_decimalsec = 2
        ld = len(digits)
        if (ld>0):
            # e.g. digits = "090.3"
            car = digits[0]
            if car=='0':
                zeros=1
                digits = digits[1:]
            # now digits = "90.3" and zeros=1
            kd = digits.find(".")
            if kd==-1:
                if (self._is_number(digits)==True):
                    angle_limit = int(digits)
            else:
                if (self._is_number(digits[0:kd])==True):
                    angle_limit = int(digits[0:kd])
                if (self._is_number(digits[kd+1:])==True):
                    nb_decimalsec = int(digits[kd+1:])
                # now digits_angle_limit = "90", digits_nb_decimal="3"
        #print("ld="+str(ld))            
        #print("symbols="+symbols)
        #print("digits="+digits)
        #print("unit="+str(unit))
        #print("modulo="+str(modulo))
        #print("separator="+str(separator))
        #print("sign="+str(sign))
        #print("zeros="+str(zeros))
        #print("angle_limit="+str(angle_limit))
        #print("nb_decimal="+str(nb_decimalsec))
        error, deg = self.angle_rad2deg(rad)
        # --- all angle in [0:360[
        deg = math.fmod(deg, 360)
        if (deg<0):
            deg = math.fmod(deg+360, 360)
        #print("deg 1 ="+str(deg))
        # --- case modulo [-180:180[
        if modulo==1:
            if deg>180:
                deg-=360
        #print("deg 2 ="+str(deg))
        # --- limit angle
        if deg>angle_limit:
            deg = angle_limit
        elif deg<-angle_limit:
            deg = -angle_limit
        #print("deg 3 ="+str(deg))
        # --- sign of the input angle
        s = 1
        if deg<0:
            s = -1
            deg = -deg
        # --- case hour/deg
        if (unit==1):
            angle = deg/15
            angle_limit = angle_limit/15
        else:
            angle = deg
        #print("angle 1 ="+str(angle))
        # --- compute the trheee components of xx mm ss
        r = angle
        xx = int(math.floor(r))
        mm = 0
        ss = 0
        r = (r-xx)*60
        mm = int(math.floor(r))
        ss = (r-mm)*60
        #print("xx ="+str(xx))
        #print("mm ="+str(mm))
        #print("ss ="+str(ss))
        # === Compute the result
        result = ""
        # --- sign
        if sign==1 and s>=0:
            result += "+"
        elif s<0:
            result += "-"
        # --- xx
        n = 1+int(math.log10(angle_limit))
        if zeros==0:
            fstring  = "{:d}"
        else:
            fstring  = "{:0"+str(n)+"d}"
        result += fstring.format(xx)
        # --- separator xx mm
        if separator==1:
            result += " "
        elif separator==2:
            result += ":"
        else:
            if unit==0:
                result += "d"
            else:
                result += "h"
        # --- mm
        if zeros==0:
            fstring  = "{:d}"
        else:
            fstring  = "{:02d}"
        result += fstring.format(mm)
        # --- separator mm ss
        if separator==1:
            result += " "
        elif separator==2:
            result += ":"
        else:
            result += "m"
        # --- ss
        if zeros==0:
            fstring  = "{:."+str(nb_decimalsec)+"f}"
        else:
            if nb_decimalsec==0:
                fstring  = "{:0"+str(nb_decimalsec+2)+".0f}"
            else:
                fstring  = "{:0"+str(nb_decimalsec+3)+"."+str(nb_decimalsec)+"f}"
        #print("fstring="+fstring)
        result += fstring.format(ss)
        # --- separator ss
        if separator==0:
            result += "s"
        # -- end
        return 0, result
        
# ========================================================
# === get/set methods
# ========================================================

    def angle(self, angle=""):
        """ Set the input angle in any format
        
        :param angle: angle is an angle in any supported format (cf. help(Angle))
        :type angle: str
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle("23d 27m")
        '23d 27m'
        
        :Related topics:

        After using objangle.angle() get conversions with methods as objangle.deg()
        or objdate.rad().            
        """
        if angle != "":
            if (angle != self._init_angle):
                self._init(angle)
        return self._init_angle

    def rad(self):
        """ Get the angle in radian
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle("-23 d 56'")
        "-23 d 56'"
        >>> objangle.rad()
        -0.4177154676439762
        
        :Related topics:

        Before use objdate.angle() to set the input angle.
        """
        if (self._computed_rad == 0):
            init_angleformat, rad = self.angle_angle2rad(self._init_angle)
            self._init_angleformat = init_angleformat
            self._computed_rad = 1        
            self._rad = rad
        return self._rad

    def deg(self):
        """ Get the angle in degrees
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle("-23 d 56'")
        "-23 d 56'"
        >>> objangle.deg()
        -23.933333333333334
        
        :Related topics:

        Before use objdate.angle() to set the input angle.
        """
        if (self._computed_deg == 0):
            if (self._computed_rad == 0):
                self.rad()
            if (len(self._init_angleformat) > 0):                    
                error, deg = self.angle_rad2deg(self._rad)
                if error==0:
                    self._computed_deg = 1
                    self._deg = deg
                    return self._deg
            return -1            
        return self._deg

    def arcmin(self):
        """ Get the angle in arcmin
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle("-23 d 56'")
        "-23 d 56'"
        >>> objangle.arcmin()
        -1436.0
        
        :Related topics:

        Before use objdate.angle() to set the input angle.
        """
        if (self._computed_arcmin == 0):
            if (self._computed_rad == 0):
                self.rad()
            if (len(self._init_angleformat) > 0):                    
                error, deg = self.angle_rad2deg(self._rad)
                arcmin = deg*60
                if error==0:
                    self._computed_arcmin = 1
                    self._arcmin = arcmin
                    return self._arcmin
            return -1            
        return self._arcmin

    def arcsec(self):
        """ Get the angle in arcsec
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle("-23 d 56'")
        "-23 d 56'"
        >>> objangle.arcsec()
        -86160.0
        
        :Related topics:

        Before use objdate.angle() to set the input angle.
        """
        if (self._computed_arcsec == 0):
            if (self._computed_rad == 0):
                self.rad()
            if (len(self._init_angleformat) > 0):                    
                error, deg = self.angle_rad2deg(self._rad)
                arcsec = deg*3600
                if error==0:
                    self._computed_arcsec = 1
                    self._arcsec = arcsec
                    return self._arcsec
            return -1            
        return self._arcsec

    def sexagesimal(self,sexagesimal_format):
        """ Get the angle in sexagesimal
        
        :param rad: rad is an angle in radian
        :type rad: float
        :param sexagesimal_format: The uspzad format (see details below)
        :type sexagesimal_format: str

        Sexagesimal format:
            * u (unit) = h,H,d,D (default=D). Capital mean module [0:360[, lower case means module [-180:180[ 
            * s (separator) = " ",:,"" (default="" means letters hms or dms)
            * p (plus/minus) = +,"" (default="")
            * z (zeros) = 0,"" (default="")
            * a (angle_limits) = "",90, (+/-limit if unit D,H, default="" means 360)
            * d (sec_digits) = "",".1",".2",... (default="")
        
        Style 1:
            * To Display a R.A.: "H0.2" =>  23h07m42.49s
            * To Display a Decl.: "d+090.1" => +00d34m22.6s
            * To Display a H.A.: "h0.2" => -08h43m16.05s
        
        Style 2:
            * To Display a R.A.: "H 0.2" =>  23 07 42.49
            * To Display a Decl.: "d +090.1" => -00 34 22.6
            * To Display a H.A.: "h 0.2" => -08 43 16.05
        
        Style 3:
            * To Display a R.A.: "H:0.2" =>  23:07:42.49
            * To Display a Decl.: "d:+090.1" => -00:34:22.6
            * To Display a H.A.: "h:0.2" => -08:43:16.05
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle(-0.57)
        -0.57
        >>> objangle.sexagesimal("d:-090.1")
        '-00:34:12.0'
                
        :Related topics:

        Before use objdate.angle() to set the input angle.
        """
        if (self._computed_sexagesimal == 0) or (sexagesimal_format != self._computed_sexagesimal_format):
            if (self._computed_rad == 0):
                self.rad()
            if (len(self._init_angleformat) > 0):                    
                error, sexagesimal = self.angle_rad2sexagesimal(self._rad, sexagesimal_format)
                if error==0:
                    self._computed_sexagesimal = 1
                    self._sexagesimal = sexagesimal
                    self._computed_sexagesimal_format = sexagesimal_format
                    return self._sexagesimal
            return -1            
        return self._sexagesimal

# ========================================================
# === debug methods
# ========================================================
    
    def infos(self, action) -> None:
        """ To get informations about this class
        
        :param action: A command to run a debug action (see examples).
        :type action: string
        
        :Example:
            
        Angle().infos("doctest")
        Angle().infos("doc_methods")
        Angle().infos("internal_attributes")
        Angle().infos("public_methods")        
        """
        if (action == "doc_methods"):
            publics = [x for x in dir(self) if x[0]!="_"]
            for public in publics:
                varname = "{}".format(public)
                if (callable(getattr(self,varname))==True):
                    print("\n{:=^40}".format(" method "+varname+" "))
                    t = "Angle()."+varname+".__doc__"
                    tt =eval(t)
                    print(tt)
        if (action == "doctest"):
            if __name__ == "__main__":
                print("\n{:~^40}".format("doctest"))
                #doctest.testmod(verbose=True, extraglobs={'objangle': Angle()})
                doctest.testmod(verbose=True)
        if (action == "internal_attributes"):
            internals = [x for x in dir(self) if x[0]=="_" and x[1]!="_"]
            for internal in internals:
                varname = "{}".format(internal)
                #if (hasattr(self,varname)==True):
                if (callable(getattr(self,varname))==False):
                    print(varname + "=" + str(getattr(self,varname)))
        if (action == "public_methods"):
            publics = [x for x in dir(self) if x[0]!="_"]
            for public in publics:
                varname = "{}".format(public)
                if (callable(getattr(self,varname))==True):
                    print(varname)


# ========================================================
# === special methods
# ========================================================
        
    def __init__(self, angle=""):
        """ Object initialization where angle is the input in any format.

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : string
        
        """
        self._init(angle)

    def __add__(self, angle):
        """ Add an angle to an angle.

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The result of the addition
        :rtype: Angle()
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle = objangle1 + objangle2 ; objangle.sexagesimal("D")
        12.345
        '56d28m'
        '68d48m42.00s'
        """
        if isinstance(angle, Angle) == False:
            angle =  Angle(angle)
        res = Angle()
        if self._computed_rad == 0:
            self.rad()
        if angle._computed_rad == 0:
            angle.rad()            
        if (self._computed_rad == 1) and (angle._computed_rad == 1):
            rad = self._rad + angle._rad
            deg = rad * self._r2d
            res = Angle(deg)
        return res

    def __radd__(self, angle):
        """ Right addition an angle to an angle.
        """
        return self + angle    

    def __iadd__(self, angle):
        """ Add an angle to an angle.
        """
        return self + angle
    
    def __sub__(self, angle):
        """ Subtract an angle to an angle.
        
        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The result of the subtraction
        :rtype: Angle()
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle = objangle1 - objangle2 ; objangle.sexagesimal("D")
        12.345
        '56d28m'
        '315d52m42.00s'
        """
        if isinstance(angle, Angle) == False:
            angle =  Angle(angle)
        res = Angle()
        if self._computed_rad == 0:
            self.rad()
        if angle._computed_rad == 0:
            angle.rad()            
        if (self._computed_rad == 1) and (angle._computed_rad == 1):
            rad = self._rad - angle._rad
            deg = rad * self._r2d
            res = Angle(deg)
        return res


    def __rsub__(self, angle):
        """ Right subtraction only an angle to an angle.
        """
        if isinstance(angle, Angle) == True:
            return self - angle    
        else:
            return angle
    
    def __isub__(self, angle):
        """ Subtract  an angle to an angle.
        
        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The result of the subtraction
        :rtype: Angle()
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1 -= objangle2 ; objangle1.sexagesimal("D")
        12.345
        '56d28m'
        '315d52m42.00s'
        """
        return self - angle

    def __eq__(self, angle):
        """ Comparaison of angles. Return True if angles are defined and equals.

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The logic result of the comparison.
        :rtype: bool
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1 == objangle2
        12.345
        '56d28m'
        False
        
        .. note:: Does not account for the modulo.
        """
        return self._angle_compare(angle, "==")

    def __ne__(self, angle):
        """ Comparaison of angles. Return True if angles are defined and not equals.

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The logic result of the comparison.
        :rtype: bool
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1 != objangle2
        12.345
        '56d28m'
        True
        
        .. note:: Does not account for the modulo.
        """
        return self._angle_compare(angle, "!=")

    def __gt__(self, angle):
        """ Comparaison of angles. Return True if self > angle

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The logic result of the comparison.
        :rtype: bool
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1 > objangle2
        12.345
        '56d28m'
        False
        
        .. note:: Does not account for the modulo.
        """
        return self._angle_compare(angle,">")
    
    def __ge__(self, angle):
        """ Comparaison of angles. Return True if self >= angle

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The logic result of the comparison.
        :rtype: bool
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1 >= objangle2
        12.345
        '56d28m'
        False
        
        .. note:: Does not account for the modulo.
        """
        return self._angle_compare(angle,">=")

    def __lt__(self, angle):
        """ Comparaison of angles. Return True if self < angle

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The logic result of the comparison.
        :rtype: bool
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1 < objangle2
        12.345
        '56d28m'
        True
        
        .. note:: Does not account for the modulo.
        """
        return self._angle_compare(angle,"<")
        
    def __le__(self, angle):
        """ Comparaison of angles. Return True if self <= angle

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: The logic result of the comparison.
        :rtype: bool
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(12.345) ; objangle2.angle("56d28m") ; objangle1 <= objangle2
        12.345
        '56d28m'
        True
        
        .. note:: Does not account for the modulo.
        """
        return self._angle_compare(angle,"<=")

    def __mod__(self, angle):
        """ Modulo of an angle by another angle. Return an angle

        :param angle : An angle in any supported format (cf. help(Angle))
        :type angle : Angle()
        :returns: An angle
        :rtype: Angle()
        
        :Example:

        >>> objangle1 = Angle()
        >>> objangle2 = Angle()
        >>> objangle1.angle(30.56) ; objangle2.angle(2.34) ; (objangle1 % objangle2).deg()
        30.56
        2.34
        0.14000000000000412
        
        """
        if isinstance(angle, Angle) == False:
            angle =  Angle(angle)
        if self._computed_deg == 0:
            self.deg()
        if angle._computed_deg == 0:
            angle.deg()
        anglemod = Angle()
        if (self._computed_deg == 1) and (angle._computed_deg == 1):
            deg = math.fmod(self._deg, angle._deg)
            if (deg<0):
                deg += angle._deg
            anglemod.angle(deg)
        return anglemod

    def __mul__(self, multiplier):
        """ multiplication of an angle by a float or int. Return an angle

        :param multiplier: A real number
        :type multiplier: float
        :returns: An angle
        :rtype: Angle()
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle(30.43) ; (objangle*2).deg()
        30.43
        60.86000000000001
        
        """
        if isinstance(multiplier, (int, float)) == False:
            raise TypeError
            return ""
        if self._computed_deg == 0:
            self.deg()
        anglemult = Angle()
        if (self._computed_deg == 1):
            deg = self._deg * multiplier
            anglemult.angle(deg)
        return anglemult

    def __rmul__(self, multiplier):
        """ Right multiplication of an angle by a float or int.
        """
        return self * multiplier

    def __truediv__(self, divisor):
        """ division of an angle by a float or int. Return an angle

        :param divisor: A real number
        :type divisor: float
        :returns: An angle
        :rtype: Angle()
        
        :Example:

        >>> objangle = Angle()
        >>> objangle.angle(30.43) ; (objangle/2).deg()
        30.43
        15.215000000000002
        
        """
        if isinstance(divisor, (int, float)) == False:
            raise TypeError
            return ""
        if (divisor==0):
            raise ZeroDivisionError
        if self._computed_deg == 0:
            self.deg()
        anglediv = Angle()
        if (self._computed_deg == 1):
            deg = self._deg / divisor
            anglediv.angle(deg)
        return anglediv

# ========================================================
# ========================================================
# ========================================================