'''
module: coords
description: manipulation de vecteur 3D
'''

from math import sqrt, cos, radians, sin

class Coords:
    COS_PI_SUR_6 = sqrt(3)/2
    SIN_PI_SUR_6 = 0.5

    def __init__(self, x, y, z):
        '''
        Constructeur
        x, y, z: coordonnées du vecteur
        '''
        self.__x = x
        self.__y = y
        self.__z = z

    @property
    def x(self):
        # Accesseur attribut x
        return self.__x

    @property
    def y(self):
        # Accesseur attribut y
        return self.__y

    @property
    def z(self):
        # Accesseur attribut z
        return self.__z

    def __add__(self, B):
        '''
        Surcharge opérateur + pour calcul A + B
        '''
        x = self.__x + B.__x
        y = self.__y + B.__y
        z = self.__z + B.__z
        return Coords(x, y, z)

    def __iadd__(self, B):
        '''
        Surcharge opérateur += pour calcul A += B
        '''
        self.__x += B.__x
        self.__y += B.__y
        self.__z += B.__z
        return self

    def __sub__(self, B):
        '''
        Surcharge opérateur - pour calcul A - B
        '''
        x = self.__x - B.__x
        y = self.__y - B.__y
        z = self.__z - B.__z
        return Coords(x, y, z)

    def __isub__(self, B):
        '''
        Surcharge opérateur -= pour calcul A -= B
        '''
        self.__x -= B.__x
        self.__y -= B.__y
        self.__z -= B.__z
        return self

    def __mul__(self, B):
        '''
        Si B de type Coords:
            Surcharge opérateur * pour calcul A * B
            * compris comme produit vectoriel
        Sinon B compris comme réel et simple multiplication
        '''
        if type(B) == Coords:
            x = self.__y * B.__z - self.__z * B.__y
            y = self.__z * B.__x - self.__x * B.__z
            z = self.__x * B.__y - self.__y * B.__x
        else:
            x = self.__x * B
            y = self.__y * B
            z = self.__z * B
        return Coords(x, y, z)

    @property
    def norm2(self):
        '''
        renvoie la norme**2 du vecteur
        '''
        return self.__x**2 + self.__y**2 + self.__z**2

    def rotate(self, n, deg_angle):
        '''
        calcule les nouvelles coordonnées en tournant autour du vecteur
        dont les coordonées sont fourniées, l'axe étant en (0,0,0)
        préconditon : vecteur n normé
        '''
        assert n.norm2 == 1
        # primo, séparer la composante perp à n et la composant colinéaire
        colin = n.scalaire(self.scalaire(n))
        non_colin = self - colin
        ortho = n * non_colin
        rad_angle = radians(deg_angle)
        c = cos(rad_angle)
        s = sin(rad_angle)
        if deg_angle % 90 == 0:
            c = int(c)
            s = int(s)
        x = non_colin.__x * c + ortho.__x * s + colin.__x
        y = non_colin.__y * c + ortho.__y * s + colin.__y
        z = non_colin.__z * c + ortho.__z * s + colin.__z
        return Coords(x, y, z)

    @property
    def iso(self):
        '''
        renvoie les coordonnées (x,y) écran pour une perspective isométrique
        en tenant compte du fait que l'axe y est renversé sur un écran
        '''
        x_ecran = (self.__y - self.__x) * self.COS_PI_SUR_6
        y_ecran = -self.__z + (self.__x + self.__y)*self.SIN_PI_SUR_6
        return x_ecran, y_ecran

    def scalaire(self, n):
        '''
        Selon le cas :
          n: Coords, vecteur 3D.
             renvoie le produit scalaire
          n: réel
             renvoie le vecteur obtenu par multiplication par n
        '''
        if type(n) == Coords:
            return self.__x * n.__x + self.__y * n.__y + self.__z * n.__z
        x = n * self.__x
        y = n * self.__y
        z = n * self.__z
        return Coords(x, y, z)

    def scalaire_xyz(self, x, y, z):
        '''
        alias de la fonction produit scalaire
        x, y, z: coordonnées du vecteur
        '''
        return self.__x * x + self.__y * y + self.__z * z

    def __str__(self):
        '''
        transtypage -> str
        '''
        return "({} ; {} ; {})".format(self.__x, self.__y, self.__z)
