"""
Définition d'une classe Complex implémentant les nombres complexes. Le propos
est d'illustrer quelques aspects de la programmation objet en python.

Exemples d'utilisation de la classe Complex :

# Construction d'un complexe en coordonnées polaires
In [108]: j = Complex(1, 2*math.pi/3, polar = True)

In [109]: j
Out[109]: -0.50 + 0.87*i

# Calculs
In [110]: j+j*j
Out[110]: -1.00 + 0.00*i

# Module et conjugué
In [111]: abs(j)
Out[111]: 0.9999999999999999

In [112]: ~j
Out[112]: -0.50 - 0.87*i

"""

from math import sqrt, sin, cos, acos

# Classe de base : les vecteurs
class Vector2(object):
    """Vecteurs de R^2"""

    # Constructeur
    def __init__(self, x, y):
        self.abc = float(x)
        self.ord = float(y)

    # Représentation d'un vecteur : (x, y)
    def __repr__(self):
        return "(%.2f, %.2f)" % (self.abc, self.ord)

    # Définir la méthode __add__() permet d'écrire v1 + v2 au lieu de v1.add(v2)
    def __add__(self, v):
        x = self.abc + v.abc
        y = self.ord + v.ord

        # Plutôt que d'appeler Vector2(x, y) qui va construire une instance de
        # Vector2, on appelle self.__class__(x, y) ; __class__ est un attribut
        # spécial qui contient la classe de l'objet. Ainsi on construit une
        # instance de self.__class__ ce qui permet de retourner le bon type
        # d'objet que self soit une instance de Vector2 ou d'une classe
        # héritant de Vector2 comme c'est le cas de la classe Complex
        # ci-dessous.
        return self.__class__(x, y)

    # Pareil pour la soustraction : v1 - v2
    def __sub__(self, v):
        return self.__class__(self.abc - v.abc, self.ord - v.ord)

    # Pareil pour l'opposé : -v
    def __neg__(self):
        return self.__class__(-self.abc, -self.ord)

    # Pareil pour la multiplication : v*w calcule le produit scalaire
    def __mul__(self, v):
        return (self.abc*v.abc + self.ord*v.ord)

    def norm(self):
        return sqrt(self.abc*self.abc + self.ord*self.ord)

    # Permet d'appeler abs(v) plutôt que v.norm()
    def __abs__(self):
        return self.norm()


class Complex(Vector2):
    """Nombres complexes"""

    def __init__(self, x, y, polar = False):
        """
        Construit le complexe x + iy ; si polar est vrai x et y sont
        le module et l'argument du complexe à construire
        """

        if polar:
            x, y = x*cos(y), x*sin(y)

        # Appel au constructeur de la classe parente
        super(Complex, self).__init__(x, y)

    # Redéfinition de la représentation : x + y*i
    def __repr__(self):
        y = self.im()
        if y < 0:
            sign = "-"
            y = -y
        else:
            sign = "+"

        return "%.2f %s %.2f*i" % (self.real(), sign, y)

    def real(self):
        return self.abc

    def im(self):
        return self.ord

    # Les opérations arithmétiques sont héritées de la classe Vector2 sauf la
    # la multiplication qu'il faut redéfinir :
    def __mul__(self, z):
        return self.__class__(self.real()*z.real() - self.im()*z.im(),
                              self.real()*z.im() + self.im()*z.real())

    def conjugate(self):
        return Complex(self.real(), -self.im())

    # Permet d'appeler le conjugué de z avec la syntaxe ~z
    def __invert__(self):
        return self.conjugate()

    # la méthode module() est un synonyme de norm(), mais c'est le nom d'usage
    # pour les complexes
    def module(self):
        return self.norm()

    def arg(self):
        angle = acos(self.real()/self.module())
        if self.im() < 0:
            angle = -angle
        return angle