#-*- coding:utf-8 -*-
from BitVector import *
from sympy.mpmath import *

mp.prec = 100

class Logistique:
    '''
    La suite logistique.

      - On itère f(X) = mu.X.(1-X), à partir de x0.
      - On regarde ensuite où se situe f(X) par rapport aux frontières...
      - S'il se situe entre la frontière k et k+1, alors on retourne valeurs[k].

    Exemple d'utilisation :

        suiteLogistique = Logistique(0.65,4)
        Un = suiteLogistique.iterateur()
        print Un.next()

    ''' 

    def __init__(self, x0, mu, frontiere = [0.5], valeurs = [0,1]):
        '''Constructeur.'''
        if len(frontiere)+1!=len(valeurs):
            raise ValueError("La valeur de retour doit\
                              être égale à la valeur frontière +1.")
        self._x = mpf(str(x0))
        self._mu = mpf(str(mu))
        self._frontiere = frontiere
        self._valeurs = valeurs


    def iterateur(self):
        '''Crée un générator.'''
        while True:
            k=0
            while self._x > self._frontiere[k]:
                k+=1
                if k==len(self._frontiere):
                    break
            yield self._valeurs[k]
            self._x = self._mu*self._x*(1-self._x) 

    def termes(self, N):
        ''' Retourne une liste de N termes de la suite.'''
        temp = self.iterateur()
        l = []
        for k in range(N):
            l.append(temp.next())
        return BitVector(bitlist = l)




class Uns:
    ''' Suite de uns '''
    def __init__(self):
        '''Constructeur.'''
        pass
        
    def iterateur(self):
        ''' generator de la suite de uns.'''
        while True:
            yield 1

    def termes(self, N):
        ''' Retourne une liste de N termes de la suite.'''
        temp = self.iterateur()
        l = []
        for k in range(N):
            l.append(temp.next())
        return BitVector(bitlist = l)




class Arnold:
    ''' Le chat d'arnold :
            X[n+1] = (X[n] + Y[N])%N
            Y[n+1] = (X[n] + 2*Y[N])%N.

        Les arguments de ce constructeur sont le couple initial, et le 
        modulo N.
        '''                

    def __init__(self, (x0,y0), N, l = 1, dim = 2):
        '''Constructeur.'''
        assert x0 >= 0 and y0 >= 0
        assert x0 < N and y0 < N
        assert dim in [1, 2]

        self._x, self._y = x0, y0
        self._l = l
        self._N = N
        self._dim = dim


    def iterateur(self):
        '''Generator de la suite Chat d'Arnold. Possède une version 1 dimension :
           au lieu de retourner le couple (x,y), on retourne x*N+y.
           '''
        if self._dim == 2:
            while True:
                yield (self._x,self._y)
                x, y = self._x, self._y
                self._x = (x + y)%self._N
                self._y = (self._l*x + (self._l+1)*y)%self._N
        elif self._dim ==1 : 
            while True:
                yield self._x*self._N + self._y
                x, y = self._x, self._y
                self._x = (x + y)%self._N
                self._y = (self._l*x + (self._l+1)*y)%self._N



    def termes(self, N):
        ''' Retourne une liste de N termes de la suite.'''
        temp = self.iterateur()
        l = []
        for k in range(N):
            l.append(temp.next())
        return l




class TelQuel:
    ''' 
    Renvoie la suite des coefficients, tel quel.
    
    Par exemple, 
        * TelQuel(2, 5) : 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4,
                          0, 1, 2, ...
        * TelQuel((2, 3), (4, 5)) : (2, 3), (3, 3), (0, 4), (1, 4),
                                    (2, 4), (3, 4), (0, 0), (1, 0),
                                    (2, 0), (3, 0)
        * TelQuel((2, 3), (4, 5), sens = 'indirect') : (2, 3),
                                    (2,4), (2, 5), (3, 2), (3, 3)...
    Un booléen (boucle), fixé à True par défaut, permet d'arrêter les 
    itérations une fois que l'ensemble des coefficients a été atteints,
    quand il est mis à False.
        '''
    def __init__(self, debut, fin, sens = 'direct', boucle = True, termes = 0,
                pas = 1):
        '''
        Constructeur.
        '''
        self._boucle = boucle
        self._compteur = 0
        self._termes = termes
        self._pas = pas
        if isinstance(debut, int):
            assert isinstance(fin, int)
            self._debut = (debut,)
            self._fin = (fin,)
        else:
            self._debut = tuple(debut)
            self._fin = tuple(fin)
        assert len(self._debut) == len(self._fin)
        self._direct = sens == 'direct'


    def iterateur(self):
        y = self._debut
        z = self._fin
        x = list(y)
        while True:
            if len(x) == 1:
                yield x[0]
            else:
                yield tuple(x)
            if self._direct:
                vecteur = range(len(x))
            else:
                vecteur = range(len(x)-1,-1,-1)
            for k in vecteur:
                x[k] = (x[k] + self._pas)%z[k]                
                if x[k]!=0:
                    break
            if not self._boucle:
                if tuple(x) == self._debut:
                    raise StopIteration
            self._compteur +=1
            if self._compteur == self._termes:
                raise StopIteration
                        
    
    def termes(self, N):
        ''' Retourne une liste de N termes de la suite.'''
        temp = self.iterateur()
        l = []
        for k in range(N):
            l.append(temp.next())
        return l




class Doublement:
    ''' Retourne un générateur : U[n+1] = (2*U[n] + V[n] + n)%N
        
        Dans ce qui précède :
          - Une option peut être passée au constructeur pour ne pas 
            avoir le +n.
          - V est un générateur ou une liste :
               * Si c'est un générateur, on boucle éternellement,
               * Si c'est une liste, on met 0 quand on est arrivé à 
                  la fin de la liste.
          - Si V n'est pas fourni au constructeur, alors V[n] = 0.
        
        Le modulo peut être soit un entier, soit un tuple.
        '''
        
    def __init__(self, U0, modulo, generateur = [], plusN = True):
        '''
        Constructeur.
        '''
        self._compteur = -1
        self._plusN = plusN
        self._modulo = modulo
        if isinstance(modulo, int):
            self._modulo = (self._modulo,)
        if isinstance(U0, int):
            self._U = [U0]*len(self._modulo)
        else:
            self._U = U0
        if isinstance(generateur, list) :
            self._generateur = self._genere(generateur)
        else :
            self._generateur = generateur


    def _genere(self, liste):
        while True :
            if self._compteur < len(liste):
                yield liste[self._compteur]
            else:
                yield 0

        
    def iterateur(self):
        Vn = self._generateur
        while True :
            L = []
            for k in range(len(self._modulo)):
                L.append(self._U[k])
                self._compteur += 1
                self._U[k] = 2*self._U[k] + Vn.next()
                if self._plusN:
                    self._U[k] += self._compteur
                self._U[k] %= self._modulo[k]
            if len(L) == 1:
                yield L[0]
            else:
                yield tuple(L)


    def termes(self, N):
        ''' Retourne une liste de N termes de la suite.'''
        temp = self.iterateur()
        l = []
        for k in range(N):
            l.append(temp.next())
        return l
        

