from math import sin, cos, tan, atan, sqrt, radians, degrees


class KeplerOrbit(object):
    def __init__(self, sma, eccentricity, inclination=0, longitude=0, argument=0):
        self.sma = sma
        self.eccentricity = eccentricity
        self.inclination = inclination
        self.longitude = longitude
        self.argument = argument
        self.__true_anomaly_factor = sqrt((1 + eccentricity)/(1 - eccentricity))
        self.__distance_factor = sma * (1 - eccentricity ** 2)

    @property
    def inclination(self):
        return degrees(self._inclination)

    @inclination.setter
    def inclination(self, value):
        self.inclination_radian = radians(value)

    @property
    def inclination_radian(self):
        return self._inclination

    @inclination_radian.setter
    def inclination_radian(self, value):
        self._inclination = value
        self.__sin_inclination = sin(self._inclination)
        self.__cos_inclination = cos(self._inclination)

    @property
    def longitude(self):
        return degrees(self._longitude)

    @longitude.setter
    def longitude(self, value):
        self.longitude_radian = radians(value)

    @property
    def longitude_radian(self):
        return self._longitude

    @longitude_radian.setter
    def longitude_radian(self, value):
        self._longitude = value
        self.__sin_longitude = sin(self._longitude)
        self.__cos_longitude = cos(self._longitude)

    @property
    def argument(self):
        return degrees(self._argument)

    @argument.setter
    def argument(self, value):
        self.argument_radian = radians(value)

    @property
    def argument_radian(self):
        return self._argument

    @argument_radian.setter
    def argument_radian(self, value):
        self._argument = value
        self.__sin_argument = sin(self._argument)
        self.__cos_argument = cos(self._argument)

    def eccentric_anomaly(self, mean_anomaly):
        e1 = mean_anomaly
        e2 = mean_anomaly + self.eccentricity * sin(e1)
        while abs(e1 - e2) > 0.000001:
            e1, e2 = e2, mean_anomaly + self.eccentricity * sin(e2)
        return e2

    def true_anomaly(self, mean_anomaly):
        eccentric_anomaly = self.eccentric_anomaly(mean_anomaly)
        return 2 * atan(self.__true_anomaly_factor * tan(eccentric_anomaly / 2))

    def orbit(self, mean_anomaly):
        mean_anomaly = radians(mean_anomaly)
        phi = self.true_anomaly(mean_anomaly)
        r = self.__distance_factor / (1 + self.eccentricity * cos(phi))
        x = r * cos(phi)
        y = r * sin(phi)
        z = 0

        # phi = longitude, theta = inclination, psi = argument
        x, y = (x * self.__cos_longitude + y * self.__sin_longitude,
               -x * self.__sin_longitude + y * self.__cos_longitude)
        x, z = (x * self.__cos_inclination + z * self.__sin_inclination,
               -x * self.__sin_inclination + z * self.__cos_inclination)
        x, y = (x * self.__cos_argument + y * self.__sin_argument,
               -x * self.__sin_argument + y * self.__cos_argument)

        return x, y, z
