# -*- coding: iso-8859-1 -*-
from __future__ import division # 1/2 == .5 (par defaut, 1/2 == 0)

##--------------------------------------##
#              WxGeometrie               #
#                tabvar                  #
##--------------------------------------##
#    WxGeometrie
#    Dynamic geometry, graph plotter, and more for french mathematic teachers.
#    Copyright (C) 2005-2007  Nicolas Pourcelot
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import mathlib
import mathlib.universal_functions as maths
import math
import param
import sympy
from sympy import sympify
oo = maths.oo = sympy.oo
nan = maths.nan = sympy.nan
import pylib
import re, time

#~ try:
    #~ maths.num_oo = float(mathlib.array(1.)/0.)
#~ except ZeroDivisionError:
    #~ if param.debug:
        #~ print_error()
    #~ maths.num_oo = maths.oo

import numpy
maths.num_oo = numpy.inf
maths.num_nan = numpy.nan
maths.pi = math.pi
maths.e = math.e

import LIB
import param


def traduire_latex(expression):
    return LIB.parsers.traduire_formule(  expression,
                                            fonctions = maths.__dict__,
##                                            variables = '[_A-Za-z][_A-Za-z0-9]*',
                                            OOo = False,
                                            LaTeX = True,
                                            changer_separateurs = True,
                                            separateurs_personnels = (',', ';'))


##def convertir_en_latex(chaine):
##    chaine = pylib.regsub("[ ]+", chaine, " ") # inutile en LaTeX, mais ça peut simplifier certaines expressions regulieres
##
##    # On traite maintenant le (délicat) cas des fractions, ie. 2/3, mais aussi (pi+3)/(5-e)
##    securite = 10000
##    while "/" in chaine:
##        securite -= 1
##        if securite < 0:
##            raise RuntimeError, "Boucle probablement infinie."
##        i = chaine.find("/")
##
##        # analyse des caractères précédants, pour localiser le numérateur
##        k = i
##        parentheses = 0
##        debut_numerateur = fin_numerateur = None
##        # indices correspondants au début et à la fin du numérateur
##        while k > 0:
##            k -= 1
##            if chaine[k].isalnum():
##                if fin_numerateur is None:
##                    fin_numerateur = k
##            elif chaine[k] == ")":
##                if parentheses == 0:
##                    fin_numerateur = k
##                parentheses += 1
##            elif chaine[k] == "(":
##                parentheses -= 1
##                if parentheses == 0:
##                    debut_numerateur = k
##                    break
##            elif parentheses == 0 and fin_numerateur is not None:
##                debut_numerateur = k+1
##                break
##        if debut_numerateur is None:
##            debut_numerateur = 0
##        assert fin_numerateur is not None, "Numerateur introuvable"
##
##        numerateur = chaine[debut_numerateur : fin_numerateur+1].strip()
##        if numerateur[0] == "(":
##            numerateur = numerateur[1:-1]
##
##
##        # analyse des caractères suivants, pour localiser le dénominateur
##        k = i
##        parentheses = 0
##        debut_denominateur = fin_denominateur = None # indices correspondants au début et à la fin du numérateur
##
##        while k < len(chaine) - 1:
##            k += 1
##            if chaine[k].isalnum():
##                if debut_denominateur is None:
##                    debut_denominateur = k
##            elif chaine[k] == "(":
##                if parentheses == 0:
##                    debut_denominateur = k
##                parentheses += 1
##            elif chaine[k] == ")":
##                parentheses -= 1
##                if parentheses == 0:
##                    fin_denominateur = k
##                    break
##            elif parentheses == 0 and debut_denominateur is not None:
##                fin_denominateur = k-1
##                break
##        if fin_denominateur is None:
##           fin_denominateur = len(chaine)  - 1
##
##        assert debut_denominateur is not None, "Denominateur introuvable"
##
##        denominateur = chaine[debut_denominateur : fin_denominateur+1].strip()
##        if denominateur[0] == "(":
##            denominateur = denominateur[1:-1]
##
##        # remplacement de la fraction python par une fraction LaTeX
##        chaine = chaine[:debut_numerateur] + r"\frac{" + numerateur + "}{" + denominateur + "}" + chaine[fin_denominateur+1:]
##
##    # Autres remplacements :
##    chaine = re.sub(r"(?<!\w)pi|oo|e|sin|cos|tan|ln|exp|sqrt(?!\w)", lambda m:"\\" + m.group(), chaine)
##
##    for func in ('cos', 'sin', 'tan', 'ln', 'exp', 'sqrt'):
##        i = 0
##        while True:
##            i = chaine.find(func + '(', i)
##            if i == -1:
##                break
##            i += len(func) + 1
##            j = pylib.find_closing_bracket(chaine, start = i , brackets = '()')
##            chaine = chaine[:i-1] + '{' + chaine[i:j] + '}' + chaine[j+1:]
##
##    chaine = chaine.replace("**", "^").replace("*", " ")
##
##    return "$" + chaine + "$" # premiere ligne : la variable



def convertir_en_latex(chaine):
    u"Convertit une chaine représentant un calcul en code LaTeX."
    #TODO: problème avec les puissances qui contiennent des fractions
    #TODO: incorporer cette fonction à mathlib, et mettre en place des tests unitaires
    #TODO: c'est assez lent, à optimiser ?
    # (environ 0.5ms en conditions réelles, avec 10 à 20 appels par tableau).
##    time0 = time.time()
    chaine = chaine.replace("**", "^")
    chaine = re.sub(r'[ ]+', ' ', chaine)
    # inutile en LaTeX, mais ça peut simplifier certaines expressions regulieres
    chaine = re.sub(r'[ ]?/[ ]?', '/', chaine)
    chaine = re.sub(r'[ ]?\^[ ]?', '^', chaine)

    fonctions = ('cos', 'sin', 'tan', 'ln', 'log', 'exp', 'sqrt')

    # On traite maintenant le (délicat) cas des fractions,
    # ie. 2/3, mais aussi (pi+3)/(5-e), ou cos(2)/3
    securite = 1000
    while "/" in chaine:
        securite -= 1
        if securite < 0:
            raise RuntimeError, "Boucle probablement infinie."
        i = chaine.find("/")

        # analyse des caractères précédants, pour localiser le numérateur
        k = i
        parentheses = 0
        # indices correspondants au début et à la fin du numérateur
        debut_numerateur = fin_numerateur = None
        #  exposant éventuel du numérateur
        puissance = ''

        while k > 0:
            k -= 1
            if chaine[k].isalnum():
                if fin_numerateur is None:
                    fin_numerateur = k
            elif chaine[k] == ")":
                if parentheses == 0:
                    fin_numerateur = k
                parentheses += 1
            elif chaine[k] == "(":
                parentheses -= 1
                if parentheses == 0:
                    debut_numerateur = k
                    break
            elif chaine[k] == '^':
                puissance = chaine[k:fin_numerateur + 1]
            elif parentheses == 0 and fin_numerateur is not None:
                debut_numerateur = k+1
                break
        if debut_numerateur is None:
            debut_numerateur = 0
        assert fin_numerateur is not None, "Numerateur introuvable"

        # On détecte la fonction qui précède éventuellement la parenthèse
        # par exemple, sqrt(2)/2 -> le numérateur est 'sqrt(2)', et pas '(2)'
        # TODO: réécrire tout ça plus proprement
        for func in fonctions:
            n = len(func)
            if chaine[:debut_numerateur].endswith(func) and\
                    (debut_numerateur == n or not chaine[debut_numerateur-n-1].isalpha()):
                debut_numerateur -= n

        numerateur = chaine[debut_numerateur : fin_numerateur+1].strip()
        if numerateur[0] == "(":
            if puissance:
                numerateur += puissance
            else:
                numerateur = numerateur[1:-1]


        # analyse des caractères suivants, pour localiser le dénominateur
        k = i
        parentheses = 0
        # indices correspondants au début et à la fin du numérateur
        debut_denominateur = fin_denominateur = None
        # exposant éventuel du dénominateur
        puissance = ''

        while k < len(chaine) - 1:
            k += 1
            if chaine[k].isalnum():
                if debut_denominateur is None:
                    debut_denominateur = k
            elif chaine[k] == "(":
                if parentheses == 0:
                    debut_denominateur = k
                parentheses += 1
            elif chaine[k] == ")":
                parentheses -= 1
                if parentheses == 0:
                    fin_denominateur = k
                    break
            elif parentheses == 0 and debut_denominateur is not None:
                fin_denominateur = k-1
                break
        if fin_denominateur is None:
           fin_denominateur = len(chaine)  - 1

        assert debut_denominateur is not None, "Denominateur introuvable"

        denominateur = chaine[debut_denominateur : fin_denominateur+1].strip()
        if chaine[fin_denominateur+1:].startswith('^'):
            m = re.match('[(][A-Za-z0-9.]+[)]|[A-Za-z0-9.]+', chaine[fin_denominateur + 2:])
            if m is not None:
                puissance = '^' + m.group()
                fin_denominateur += m.end() + 1
        if denominateur[0] == "(":
            if puissance:
                denominateur += puissance
            else:
                denominateur = denominateur[1:-1]

        if len(chaine) >= 10000:
            print "Code en cours :"
            print chaine
            raise RuntimeError, 'Memory Leak probable.'
        # remplacement de la fraction python par une fraction LaTeX
        chaine = chaine[:debut_numerateur] + r"\frac{" + numerateur + "}{" + denominateur + "}" + chaine[fin_denominateur+1:]

    assert securite >= 0

    # Autres remplacements :
    chaine = re.sub(r"(?<!\w|\\)(pi|oo|e|sin|cos|tan|ln|log|exp|sqrt)(?!\w)", lambda m:"\\" + m.group(), chaine)
    for func in fonctions:
        i = 0
        while True:
            i = chaine.find(func + '(', i)
            if i == -1:
                break
            i += len(func) + 1
            j = pylib.find_closing_bracket(chaine, start = i , brackets = '()')
            chaine = chaine[:i-1] + '{' + chaine[i:j] + '}' + chaine[j+1:]

    chaine = chaine.replace("*", " ")

    # Puissances : 2^27 -> 2^{27}
    chaine = re.sub(r'\^\([-0-9.]+\)', lambda m: '^{' + m.group()[2:-1] + '}', chaine)
    chaine = re.sub(r'\^-?[0-9.]+', lambda m: '^{' + m.group()[1:] + '}', chaine)

##    if param.debug:
##        print 'Temps pour conversion LaTeX:', time.time()- time0
    return "$" + chaine + "$"





def test_parentheses(chaine):
    u"""Retourne True si le parenthésage est correct, False sinon.

    Note: il s'agit d'un test rapide (on ne tient pas compte des guillemets, crochets...)"""
    count = 0
    n = 0
    k = 0
    while count >= 0:
        k += 1
        i = chaine.find('(', n)
        j = chaine.find(')', n)
        if i == -1:
            n = j
        elif j == -1:
            n = i
        else:
            n = min(i, j)
        if n == -1:
            break
        parenthese = chaine[n]
        assert parenthese in '()'
        count += (1 if parenthese == '(' else -1)
        n += 1
    return not count

# \hspace*{15mm}

def _auto_tabvar(chaine = '', derivee = True, limites = True, decimales = None):
    u"""Génère le code du tableau de variations d'une fonction à variable réelle.

    On suppose que la fonction est de classe C1 sur tout intervalle ouvert de son
    ensemble de définition.
    Par ailleurs, les zéros de sa dérivée doivent être calculables pour la librairie sympy.
    """
    # TODO: gérer le paramètre 'decimales'
    str = mathlib.custom_functions.custom_str
    chaine_initiale = chaine

    # Ensemble de définition
    if ' sur ' in chaine:
        chaine, ens_def = chaine.split(' sur ')
        ens_def = mathlib.intervalles.conversion_chaine_ensemble(ens_def)
    else:
        ens_def = mathlib.intervalles.R

    # Légende de la dernière ligne
    if '=' in chaine:
        legende, chaine = chaine.split('=', 1)
    else:
        legende = 'f'

    # Conversion en expression sympy
    interprete = mathlib.custom_objects.Interprete()
    interprete.evaluer(chaine)
    expr = interprete.ans()
    # Récupération de la variable
    variables = expr.atoms(sympy.Symbol)
    if len(variables) > 1:
        raise ValueError, "Il y a plusieurs variables dans l'expression !"
    elif not variables:
        raise ValueError, "Il n'y a pas de variable dans l'expression !"
    var = variables.pop()
    # Récupération de l'ensemble de définition
    ens_def *= mathlib.custom_functions.ensemble_definition(expr, var)
    valeurs_interdites = []
    xmin = ens_def.intervalles[0].inf
    if not ens_def.intervalles[0].inf_inclus:
        valeurs_interdites.append(xmin)
    sup = xmin
    for intervalle in ens_def.intervalles:
        inf = intervalle.inf
        if sup != inf:
            # Il y a un 'trou' dans l'ensemble de définition (ex: ]-oo;0[U]2;+oo[)
            raise NotImplementedError
            #TODO: utiliser || pour noter un intervalle interdit
        sup = intervalle.sup
        if not intervalle.sup_inclus:
            valeurs_interdites.append(sup)
    xmax = sup

    # On étudie la dérivée
    derivee = expr.diff(var)
    ens_def_deriv = mathlib.custom_functions.ensemble_definition(derivee, var)
    solutions = [sol for sol in sympy.solve(derivee, var) if sol.is_real]

    # On liste toutes les valeurs remarquables pour la fonction
    # NB: on les convertit toutes au format Sympy, pour qu'il n'y ait pas
    # par exemple deux zéros "différents" listés (int(0), et sympy.Integer(0))
    valeurs = {sympify(xmin): None, sympify(xmax): None}
    for sol in solutions:
        if xmin <= sol <= xmax:
            valeurs[sympify(sol)] = 0
    for val in valeurs_interdites:
        if xmin <= val <= xmax:
            valeurs[sympify(val)] = nan
    liste_valeurs = sorted(valeurs)

##    def reel(val):
##        return val.is_real and -oo < val < oo

    # On génère le code, valeur après valeur
    code = str(var) + ';' + legende + ':'
    print "liste_valeurs", liste_valeurs, valeurs, set(valeurs), [type(val) for val in valeurs]
    for i, valeur in enumerate(liste_valeurs):
        code_point = '(' + str(valeur) + ';'
        if valeur == xmin:
            lim_plus = sympy.limit(expr, var, valeur, dir = '+')
            if valeur != -oo and valeur in valeurs_interdites:
                code_point += '|'
            # On n'affiche les limites que si 'limites == True'
            if limites or (valeur != -oo and valeur not in valeurs_interdites):
                code_point += str(lim_plus)
        elif valeur == xmax:
            lim_moins = sympy.limit(expr, var, valeur, dir = '-')
            # On n'affiche les limites que si 'limites == True'
            if limites or (valeur != +oo and valeur not in valeurs_interdites):
                code_point += str(lim_moins)
            if valeur != +oo and valeur in valeurs_interdites:
                code_point += '|'
        else:
            if valeur in valeurs_interdites:
                # On n'affiche les limites que si 'limites == True'
                lim_plus = sympy.limit(expr, var, valeur, dir = '+')
                lim_moins = sympy.limit(expr, var, valeur, dir = '-')
                if limites:
                    code_point += str(lim_moins) + '|' + str(lim_plus)
                else:
                    code_point += '|'
            else:
                # On calcule simplement l'image
                lim_moins = lim_plus = expr.subs(var, valeur)
                code_point += str(lim_moins)
        if valeur not in (-oo, oo) and valeur not in ens_def_deriv:
            # La dérivée n'est pas définie en cette valeur
            code_point += ';|'
        code_point += ')'
        if i > 0:
            print lim_precedente, lim_moins
            if lim_precedente < lim_moins:
                code += ' << '
            elif lim_precedente > lim_moins:
                code += ' >> '
            else:
                code += ' == '
        code += code_point
        lim_precedente = lim_plus

##        if i != len(liste_valeurs) - 1:
##            valeur_suivante = liste_valeurs[i + 1]
##            if valeur == -oo:
##                if valeur_suivante == +oo:
##                    val_intermediaire = 0
##                else:
##                    val_intermediaire = valeur_suivante - 1
##            elif valeur_suivante == +oo:
##                val_intermediaire = valeur + 1
##            else:
##                val_intermediaire = (valeur + valeur_suivante)/2
##            # On suppose la fonction continue sur tout intervalle de son ensemble de définition.
##            if derivee.subs(var, val_intermediaire) > 0:
##                code += ' >> '
##            else:
##                code += ' << '

    print 'Code TABVar:', code
    return tabvar(code, derivee = derivee) + '% ' + chaine_initiale + '\n'



def tabvar(chaine = "", derivee = True, limites = True, decimales = None):
    u"""Indiquer les variations de la fonction.

Exemples :
f: (-oo;3) << (1;2;0) << (3;+oo|-oo) << (5;2) >> (+oo;-oo)
\\sqrt{x};(\\sqrt{x})';x: 0;0;| << +oo;+oo"""

    chaine_originale = chaine = chaine.strip()


    #ligne_variable = ligne_derivee = ligne_fonction = ""

    if not ':' in chaine and not '>>' in chaine and not '==' in chaine and not '<<' in chaine:
        return _auto_tabvar(chaine, derivee = derivee, limites = limites, decimales = decimales)

    chaine = chaine.replace("-oo", "-\\infty").replace("+oo", "+\\infty")

    liste = chaine.split(":", 1)

    if len(liste) == 1 or len(liste[0].strip()) == 0:
        ligne_variable = "x"
        ligne_derivee = "f'(x)"
        ligne_fonction = "f"

    else:
        legende, chaine = liste
        legende = legende.split(";")

        if len(legende) > 1:
            ligne_variable = legende[0].strip()
            ligne_fonction = legende[1].strip()

            if len(legende) > 2:
                ligne_derivee = legende[2].strip()
            else:
                deb = ligne_fonction.find("(")
                if deb == -1:
                    ligne_derivee = ligne_fonction[:].strip() + "'(" + ligne_variable + ")"
                else:
                    ligne_derivee = ligne_fonction[:deb].strip() + "'(" + ligne_variable + ")"

        else: # un seul argument pour la legende: c'est alors la fonction
            ligne_fonction = legende[0].strip()
            deb = ligne_fonction.find("(")
            fin = ligne_fonction.find(")")
            if deb == -1:
                ligne_variable = "x"
                ligne_derivee = ligne_fonction + "'(x)"
            else:
                ligne_variable = ligne_fonction[deb+1:fin].strip()
                ligne_derivee = ligne_fonction[:deb].strip() + "'(" + ligne_variable + ")"




    # on découpe la chaîne, en une suite contenant soit les valeurs de x, f(x) (et éventuellement f'(x)),
    # soit le sens de variation entre ces valeurs.
    # ex: "-oo;3 << 1;2 >> 3;-oo|+oo << 5;2 << +oo;+oo" devient
    # ["-oo;3", "<<", "1;2", ">>", "3;-oo|+oo", "<<", "5;2", "<<", "+oo;+oo"]

    sequence = re.split(r"(>>|<<|==|\|\|)", chaine.strip())

    if not sequence[0]:
        # en l'absence d'indication, x varie de -oo...
        sequence[0] = "-\\infty;"
    if not sequence[-1]:
        # ... à +oo
        sequence[-1] = "+\\infty;"

    def formater(chaine):
        chaine = chaine.strip()
        if chaine not in ("<<", ">>", "==", '||'):
            # Les valeurs sont éventuellement encadrées par des parenthèses (facultatives) pour plus de lisibilité.
            # On enlève ici les parenthèses. ex: (-2;0) devient -2;0
            if chaine[0] == '(' and chaine[-1] == ')' and test_parentheses(chaine[1:-1]):
                chaine = chaine[1:-1]
            if ";" not in chaine:
                chaine += ";" # il faut au minimum un ";" pour indiquer l'absence de valeur
        return chaine

    sequence = [formater(elt) for elt in sequence]

    # On effectue un premier balayage uniquement pour detecter les niveaux.
    niveaux = [] # chaque element de la liste correspond a une portion de tableau de variation comprise entre deux valeurs interdites.
    # A chacune de ces portions va correspondre un doublon (niveau minimal atteint, niveau maximal atteint) qu'on ajoute a la liste.
    niveau = niveau_minimum = niveau_maximum = 0
    for elt in sequence:
        if (";" in elt and "|" in elt.split(";")[1]) \
                or elt == '||': # presence d'une valeur interdite
            niveaux.append((niveau_minimum, niveau_maximum))
            niveau = niveau_minimum = niveau_maximum = 0
        else:
            if elt == "<<":
                niveau += 1
            elif elt == ">>":
                niveau -= 1
            if niveau < niveau_minimum:
                niveau_minimum = niveau
            if niveau > niveau_maximum:
                niveau_maximum = niveau
    niveaux.append((niveau_minimum, niveau_maximum))
    ecart_maximal = max(val[1] - val[0] for val in niveaux)

    # L'environnement tabvar ne permet pas de positionner un texte entre deux lignes.
    # Si, dans la 3e partie du tableau (la fonction elle-même),
    # le nombre de lignes (c-à-d. ecart_maximal+1) est impair,
    # on décale le texte légèrement vers le haut pour le centrer verticalement (via raisebox).
    if ecart_maximal%2:
        ligne_fonction = "\\niveau{" + str((ecart_maximal+2)//2) +"}{" + str(ecart_maximal+1) + "}\\raisebox{0.5em}{$" + ligne_fonction + "$}"
    else:
        ligne_fonction = "\\niveau{" + str((ecart_maximal+2)//2) +"}{" + str(ecart_maximal+1) + "}" + ligne_fonction

    #print "niveaux: ", niveaux
    colonnes = 'C|' # ex: 'CCCCC' pour 5 colonnes centrées
    portion = 0 # indique la derniere portion traitee (les portions sont delimitees par les bornes de l'ensemble de definition et les valeurs interdites)
    debut = True

    def en_latex(chaine):
        return convertir_en_latex(chaine)[1:-1]

    # Deuxieme et dernier balayage :
    # on parcourt maintenant la liste pour construire colonne par colonne le tableau de variations.
    for i in xrange(len(sequence)):
        # on justifie apres chaque etape, ce qui rend une eventuelle relecture du tableau plus agreable
        n = max(len(ligne_variable), len(ligne_derivee), len(ligne_fonction))
        ligne_variable = ligne_variable.ljust(n)
        ligne_derivee = ligne_derivee.ljust(n)
        ligne_fonction = ligne_fonction.ljust(n)

        ligne_variable += "&"
        if debut:
            debut = False
            ligne_fonction += "&\\niveau{" + str(1-niveaux[portion][0]) +"}{" + str(ecart_maximal+1) + "}"
        else:
            ligne_fonction += "&"
        ligne_derivee += "&"

        elt = sequence[i]
        if elt in (">>", "<<", "==", "||"):  # il s'agit d'une variation
            colonnes += ('N' if elt == '||' else 'C')
            #ligne_variable += " "
            if elt == "<<":
                ligne_derivee += "+"
                ligne_fonction += r"\croit"
            elif elt == ">>":
                ligne_derivee += "-"
                ligne_fonction += r"\decroit"
            elif elt == "==":
                ligne_derivee += "0"
                ligne_fonction += r"\constante"
            else:
                ligne_variable += r"\hspace*{15mm}"

        else: # il s'agit des valeurs entre deux variations
            valeurs = elt.split(";")
            # valeurs = x, f(x), et eventuellement f'(x).
            # Si f(x) n'est pas defini (valeur interdite), il y a (en general) une valeur-double pour f(x) :
            # les limites a gauche et a droite, separees par un "|". (Idem pour f'(x)).


            largeur = max((3 if "|" in val else 1) for val in valeurs)
            # 3 si x est une valeur interdite pour f ou f' (ie. une valeur contient un "|"), 1 sinon.

            if largeur == 3: # x est une valeur interdite pour f(x) ou f'(x)
                ligne_variable += " &" + en_latex(valeurs[0]) + "& "

                vals_fonc = valeurs[1].split("|")
                if len(vals_fonc) == 2: # x est une valeur interdite pour f(x)
                    portion += 1 # on change de portion
                    ligne_fonction += en_latex(vals_fonc[0]) + "&\\dbarre&" \
                                    + "\\niveau{" + str(1-niveaux[portion][0]) +"}{" + str(ecart_maximal+1) + "}" \
                                    + en_latex(vals_fonc[1])
                else:
                    ligne_fonction += " &" + en_latex(vals_fonc[0]) + "&"

                if len(valeurs) < 3: # le nombre derive n'est pas specifie
                    valeurs.append("|") # si la fonction n'est pas definie en x, sa derivee non plus

                vals_deriv = valeurs[2].split("|")
                if len(vals_deriv) == 2:
                    ligne_derivee += en_latex(vals_deriv[0]) + "&\\dbarre&" + en_latex(vals_deriv[1])
                else:
                    ligne_derivee += " &" + en_latex(vals_deriv[0]) + "&"

            else: # x n'est pas une valeur interdite
                ligne_variable += en_latex(valeurs[0])
                ligne_fonction += en_latex(valeurs[1])

                if len(valeurs) < 3: # le nombre derive n'est pas specifie
                    if 0 < i < len(sequence)-1 and sequence[i-1] != sequence[i+1]:
                        # Changement de sens de variation en x :
                        # la dérivée s'annule donc.
                        valeurs.append("0")
                    else:
                        valeurs.append(" ")
                ligne_derivee += en_latex(valeurs[2])

            colonnes += largeur*'C'



    code = "\\[\\begin{tabvar}{|" + colonnes + "|}\n\\hline\n"
    code += ligne_variable + "\\\\\n"

    if derivee:
        code += "\\hline\n" + ligne_derivee + "\\\\\n"

    code += "\\hline\n" + ligne_fonction + "\\\\\n"

    code += "\\hline\n\\end{tabvar}\\]\n% " + chaine_originale + "\n"

    return code




# exemple: x;sin(x)::-5;5;0.1:0.01 (variable;expression:valeur initiale;valeur finale;pas:precision d'arrondi).
# exemple: x;h(x):sin(x)+1:-5;0;0.5|0;3;1:0.01 (variable;expression:valeur initiale;valeur finale;pas:precision d'arrondi).

def tabval(chaine = "", icomma = True):
    u"""Syntaxe:
variable;fonction:expression:valeur initiale;valeur finale;pas:precision d'arrondi

Exemples:
x;\\sin(x)::-5;5;0.1:0.01
x;h(x):sin(x)+1:-5;0;0.5|0;3;1:0.01"""

# f(x)=x+4:-5,-4..0 ; 2 ; 5,7..10// 12,14..20
# f(x)=x+4:-5..-4..0; 2; 5..7..10// 12,14..20
# f(x)=x+4:-5 -4+1..0

    chaine_originale = chaine = chaine.strip()
    chaine = chaine.replace(r'\\', '\n').replace('//', '\n')
    sequence = chaine.split(":", 2)

    legende = [txt.strip() for txt in sequence[0].split("=", 1)]
    if len(legende) == 2:
        fonction, expression = legende
        # On devine la variable (en principe, elle est entre parenthèses)
        deb = fonction.find("(")
        fin = fonction.find(")")
        if deb == -1:
            variable = "x"
        else:
            variable = fonction[deb+1:fin].strip()
    else:
        fonction = expression = legende[0]
        # Reste à deviner la variable.
        # On cherche les lettres isolées (sauf 'e', qui représente exp(1))
        m = re.search('(?<![A-Za-z])[A-DF-Za-df-z](?![A-Za-z])', expression)
        # Si on n'en trouve pas, la variable sera 'x'
        variable = m.group() if m else 'x'

    precision = (float(eval(sequence[1].strip('[] '), maths.__dict__)) if len(sequence) == 3 else 0.01)

    lignes = [txt.strip() for txt in sequence[-1].split('\n') if txt.strip()]

    # On génère le code LaTeX
    code = "\\begin{center}"

    # On commence par créer la liste des valeurs
    for ligne in lignes:
        ensemble_valeurs = set()
        for intervalle in ligne.split(';'):
            if '..' in intervalle:
                premier, dernier = intervalle.split('..')
                if ',' in premier:
                    premier, suivant = premier.split(',')
                else:
                    suivant = None
                first_val = float(eval(premier, maths.__dict__))
                last_val = float(eval(dernier, maths.__dict__))
                if suivant is None:
                    pas = 1
                else:
                    next_val = float(eval(suivant, maths.__dict__))
                    pas = next_val - first_val
                ensemble_valeurs.update(numpy.arange(first_val, last_val, pas))
                ensemble_valeurs.add(last_val)
            else:
                ensemble_valeurs.add(float(eval(intervalle, maths.__dict__)))

        valeurs = sorted(ensemble_valeurs)

        nbr_colonnes = len(valeurs) + 1

        code_variable = "$" + variable + "$ "
        code_expression = "$" + fonction + "$ "

        expression = traduire_latex(expression)

        def formater(expr):
            s = str(expr).rstrip('0')
            if s[-1] == '.':
                s = s[:-1]
            s = s.replace(".", ",")
            if not icomma:
                s = "\\nombre{" + s + "} "
            return ' $' + s + '$ '


        for val in valeurs: # on construit le tableau colonne par colonne
            n = max(len(code_variable), len(code_expression))
            # on justifie avant chaque nouvelle colonne (le code LaTeX sera plus agréable à lire !)
            code_variable = code_variable.ljust(n)
            code_expression = code_expression.ljust(n)
            code_variable += '&' + formater(val)
            try:
                dict = maths.__dict__.copy()
                dict.update({variable: val})
                evaluation = eval(expression, dict)
                if evaluation in (maths.num_oo, maths.num_nan, -maths.num_oo, maths.oo, maths.nan, -maths.oo):
                    code_expression += "& $\\times$ "
                else:
                    code_expression += '&' + formater(precision*round(evaluation/precision))
            except:
                LIB.print_error()
                code_expression += "& $\\times$ "

        code += "\n\\begin{tabular}{|" + nbr_colonnes*"c|" + "}\n\\hline\n"
        code += code_variable + "\\\\\n"
        code += "\\hline\n"
        code += code_expression + "\\\\\n"
        code += "\\hline\n\\end{tabular}\n"

    return code + "\\end{center}\n% " + chaine_originale + "\n"


#def _extraire_facteurs(liste):
#
#
#def extraire_facteurs(chaine):
#    chaine = mathlib.traduire_expression(chaine)
#    chaine = chaine.replace('/', '*')
#    facteurs = [chaine]
#    facteurs = _extraire_facteurs([chaine])

def _extraire_facteurs(chaine):
    # 1. On enlève les parenthèses superflues
    while chaine[0] == '(' and chaine[-1] == ')' and test_parentheses(chaine[1:-1]):
        chaine = chaine[1:-1]
    # Premier test rapide
    if '*' not in chaine and '/' not in chaine:
        return [chaine]

    # 2. On regarde s'il s'agit d'une somme/différence
    # On commence par enlever les '+' ou '-' en début de chaîne (-2x n'est pas une différence)
    _chaine = chaine.lstrip('+-')
    for symbole in ('+', '-'):
        if len(mathlib.advanced_split(_chaine, symbole)) > 1:
            return [chaine]
            # c'est une somme/différence, pas de décomposition en facteurs

    # 3. On découpe autour des '*' (en tenant compte des parenthèses)
    facteurs = mathlib.advanced_split(chaine, '*')
    if len(facteurs) == 1:
        facteurs = mathlib.advanced_split(chaine, '/')
        if len(facteurs) == 1:
            # Ce n'est ni un produit ni un quotient
            return facteurs

    # 4. On redécoupe récursivement chacun des facteurs
    decomposition = []
    for facteur in facteurs:
        decomposition.extend(extraire_facteurs(facteur))
    return decomposition

def extraire_facteurs(chaine):
##    chaine = mathlib.parsers._ajouter_mult_manquants(chaine, fonctions = maths.__dict__)
    chaine = mathlib.parsers.traduire_formule(chaine, fonctions = maths.__dict__).replace('**', '^')
    # Pour faciliter la décomposition en produit,
    # il est important que la puissance ne soit pas notée '**'.
    return [mathlib.parsers.simplifier_ecriture(facteur) for facteur in _extraire_facteurs(chaine)]


##def auto_tabsign_old(chaine, cellspace = False):
##    str = mathlib.custom_functions.custom_str
##
##    # Décomposition en produit
##    interprete = mathlib.custom_objects.Interprete()
##    interprete.evaluer(chaine)
##    expr = interprete.ans()
##    variables = expr.atoms(sympy.Symbol)
##    if len(variables) > 1:
##        raise ValueError, "Il y a plusieurs variables dans l'expression !"
##    elif not variables:
##        raise ValueError, "Il n'y a pas de variable dans l'expression !"
##
##    var = variables.pop()
##    if expr.is_Mul:
##        facteurs = expr.args
##    else:
##        facteurs = (expr,)
##
##    xmin = -oo
##    xmax = +oo
##    code = ''
##
##    # Étude du signe de chaque facteur
##    for facteur in facteurs:
##        if facteur.is_Pow and facteur.args[1] == -1:
##            facteur = 1/facteur
##            denominateur = True
##        else:
##            denominateur = False
##
##        valeurs = {}
##
##        # On récupère les valeurs interdites
##        ens_def = mathlib.custom_functions.ensemble_definition(facteur)
##        inf = ens_def.intervalles[0].inf
##        xmin = max(xmin, inf)
##        sup = inf
##        valeurs[inf] = None
##        for intervalle in ens_def.intervalles:
##            inf = intervalle.inf
##            if sup != inf:
##                # Il y a un 'trou' dans l'ensemble de définition (ex: ]-oo;0[U]2;+oo[)
##                raise NotImplementedError
##            sup = intervalle.sup
##            valeurs[sup] = None
##        xmax = min(xmax, sup)
##
##        # On récupère les zéros
##        zeros = mathlib.custom_functions.nul(facteur)
##        for intervalle in zeros:
##            if not intervalle.singleton:
##                # Les zéros ne sont pas des valeurs isolées
##                raise NotImplementedError
##            valeurs[intervalle.inf] = 0
##
##        liste_valeurs = sorted(valeurs)
##        print 'list_vals:', liste_valeurs
##
##        code += '//' + str(facteur) + ':'
##        for i, valeur in enumerate(liste_valeurs):
##            if valeur not in (-oo, oo):
##                if valeurs[valeur] is None:
##                    code += '!'
##                code += str(valeur)
##            if i != len(liste_valeurs) - 1:
##                valeur_suivante = liste_valeurs[i + 1]
##                if valeur == -oo:
##                    val_intermediaire = valeur_suivante - 1
##                elif valeur_suivante == +oo:
##                    val_intermediaire = valeur + 1
##                else:
##                    val_intermediaire = (valeur + valeur_suivante)/2
##                # On suppose la fonction continue sur tout intervalle de son ensemble de définition.
##                if facteur.subs(var, val_intermediaire) > 0:
##                    code += ' ++ '
##                else:
##                    code += ' -- '
##
##    code = str(var) + ':' + str(xmin) + ';' + str(xmax) + code + '//' + chaine
##    print 'Code TABSign:', code
##    return tabsign(code)



def _auto_tabsign(chaine, cellspace = False):
    u"""Génère le code du tableau de signe à partir d'une expression à variable réelle.

    On suppose que l'expression est continue sur tout intervalle de son
    ensemble de définition.
    Par ailleurs, ses zéros doivent être calculables pour la librairie sympy.
    """
    str = mathlib.custom_functions.custom_str

    chaine_initiale = chaine

    # Ensemble de définition
    if ' sur ' in chaine:
        chaine, ens_def = chaine.split(' sur ')
        ens_def = mathlib.intervalles.conversion_chaine_ensemble(ens_def)
    else:
        ens_def = mathlib.intervalles.R

    # Légende de la dernière ligne
    if '=' in chaine:
        legende, chaine = chaine.split('=', 1)
    else:
        legende = chaine

    # Décomposition en produit
    facteurs = extraire_facteurs(chaine)#.replace('/', '*'))
    # Conversion en expression sympy
    interprete = mathlib.custom_objects.Interprete()
    interprete.evaluer(chaine)
    expr = interprete.ans()
    # Récupération de la variable
    variables = expr.atoms(sympy.Symbol)
    if len(variables) > 1:
        raise ValueError, "Il y a plusieurs variables dans l'expression !"
    elif not variables:
        raise ValueError, "Il n'y a pas de variable dans l'expression !"
    var = variables.pop()
    # Récupération de l'ensemble de définition
    ens_def *= mathlib.custom_functions.ensemble_definition(expr, var)
    print '-> Ensemble de definition:', ens_def
    valeurs_interdites = []
    xmin = ens_def.intervalles[0].inf
    if not ens_def.intervalles[0].inf_inclus:
        valeurs_interdites.append(xmin)
    sup = xmin
    for intervalle in ens_def.intervalles:
        inf = intervalle.inf
        if sup != inf:
            # Il y a un 'trou' dans l'ensemble de définition (ex: ]-oo;0[U]2;+oo[)
            raise NotImplementedError
        sup = intervalle.sup
        if not intervalle.sup_inclus:
            valeurs_interdites.append(sup)
    xmax = sup

    # Première ligne du tableau
    code = str(var) + ':' + str(xmin) + ';' + str(xmax)

    # Étude du signe de chaque facteur
    for facteur in facteurs:
        interprete.evaluer(facteur)
        f_expr = interprete.ans()
        f_ens_def = mathlib.custom_functions.ensemble_definition(f_expr, var)
        valeurs = {xmin: None, xmax: None}
        solutions = [sol for sol in sympy.solve(f_expr, var) if sol.is_real and xmin <= sol <= xmax]
        for sol in solutions:
            valeurs[sol] = 0
        for val in valeurs_interdites:
            if val not in f_ens_def and val not in (-oo, oo):
                valeurs[val] = nan
        liste_valeurs = sorted(valeurs)
        print liste_valeurs
        # On génère le code de la ligne
        code += '// '
        print solutions, valeurs_interdites
        if solutions and all(sol in valeurs_interdites for sol in solutions):
            code += '!'
        code += facteur + ':'
        for i, valeur in enumerate(liste_valeurs):
            if valeurs[valeur] == 0:
                code += str(valeur)
            elif valeurs[valeur] == nan:
                code += '!' + str(valeur)

            if i != len(liste_valeurs) - 1:
                valeur_suivante = liste_valeurs[i + 1]
                if valeur == -oo:
                    if valeur_suivante == +oo:
                        val_intermediaire = 0
                    else:
                        val_intermediaire = valeur_suivante - 1
                elif valeur_suivante == +oo:
                    val_intermediaire = valeur + 1
                else:
                    val_intermediaire = (valeur + valeur_suivante)/2
                # On suppose la fonction continue sur tout intervalle de son ensemble de définition.
                if f_expr.subs(var, val_intermediaire) > 0:
                    code += ' ++ '
                else:
                    code += ' -- '

    code += '// ' + legende
    print 'Code TABSign:', code
    return tabsign(code, cellspace = cellspace) + '% ' + chaine_initiale + '\n'



def tabsign(chaine = '', cellspace = False):
    u"""Indiquer ligne par ligne le signe de chaque facteur.
La dernière ligne (produit ou quotient) est générée automatiquement.

Exemples:
x:-pi;pi //  sin(x):-pi -- 0 ++ pi //  !cos(x):-- -pi/2 ++ pi/2 -- // tan(x)
x:-2;2 // x+1:-- -1 ++ // !x-1:-- 1 ++

Le point d'exclamation avant une expression signifie qu'elle correspond à un dénominateur."""

    chaine_originale = chaine = chaine.strip()
    chaine = chaine.replace('//', '\n').replace(r'\\', '\n').replace("-oo", r"-\infty").replace("+oo", r"+\infty")

    lignes = [ligne.strip() for ligne in chaine.split('\n') if ligne.strip()]

    if len(lignes) == 1:
        if ':' in lignes:
            lignes = [''] + lignes
        else:
            return _auto_tabsign(lignes[0], cellspace = cellspace)

    # 'resultat' est la dernière ligne, sauf si elle contient ':'
    # (Dans ce cas, 'resultat' sera généré automatiquement plus tard, à partir des autres lignes).
    resultat = (lignes.pop() if ':' not in lignes[-1] else '')

    ligne_variable = lignes.pop(0)
    print lignes
    if ":" in ligne_variable:
        variable, donnees_variable = ligne_variable.split(":")
    elif ";" in ligne_variable:
        variable = ""
        donnees_variable = ligne_variable
    else:
        variable = ligne_variable
        donnees_variable = ""

    variable = variable.strip()
    if ';' not in donnees_variable:
        donnees_variable += ';'

    xmin, xmax = donnees_variable.split(';')
    # Par défaut, la variable varie entre -oo et +oo
    xmin = xmin.strip() if xmin.strip() else r'-\infty'
    xmax = xmax.strip() if xmax.strip() else r'+\infty'

    # Séparation de la légende et des autres données pour chaque ligne
    expressions = []
    donnees_expressions = []
    for ligne in lignes:
        expression, signe_expression = ligne.split(":")
        expressions.append(expression.strip())
        donnees_expressions.append(signe_expression.strip())

    # Au besoin, on génère la légende de la dernière ligne (c.à-d. le résultat - produit ou quotient)
    if resultat == "":
        numerateur = []
        denominateur = []
        for expression in expressions:
            if expression[0] == "!":
                liste = denominateur
                expression = expression[1:]
            else:
                liste = numerateur
            if "+" in expression or "-" in expression[1:]:
                # il s'agit d'une somme ou d'une difference, il faut donc des parentheses
                liste.append("(" + expression + ")")
            else:
                liste.append(expression)
        if denominateur:
            if len(denominateur) == 1 and denominateur[0][0] == "(" and denominateur[0][-1] == ")":
                denominateur[0] = denominateur[0][1:-1]
            if len(numerateur) == 1 and numerateur[0][0] == "(" and numerateur[0][-1] == ")":
                numerateur[0] = numerateur[0][1:-1]
            if not numerateur:
                numerateur = ["1"]
            resultat = "\\frac{%s}{%s}" %("".join(numerateur), "".join(denominateur))
        else:
            resultat = "".join(numerateur)

    # Cas particulier : 'produit' de 1 seul élément -> inutile d'afficher une ligne résultat
    if len(expressions) == 1 and not expressions[0].startswith("!"):
        afficher_resultat = False
    else:
        afficher_resultat = True

    # Au besoin, on génère la légende de la première ligne (c.à-d. la variable)
    if not variable:
        # On cherche les lettres isolées (sauf 'e', qui représente exp(1))
        m = re.search('(?<![A-Za-z])[A-DF-Za-df-z](?![A-Za-z])', resultat)
        # Si on n'en trouve pas, la variable sera 'x'
        variable = m.group() if m else 'x'

    # On récupère la liste de toutes les valeurs de x
    valeurs = set() # va contenir toutes les valeurs numeriques
    correspondances = {} # servira à retrouver le code (LaTeX notamment) à partir de la valeur numérique.

    # On convertit xmin et xmax en valeurs numériques
    for xm in (xmin, xmax):
        xm_num = eval(traduire_latex(xm), maths.__dict__)
        valeurs.add(xm_num)
        correspondances[xm_num] = xm

    # Idem pour toutes les autres valeurs
    for i, donnees in enumerate(donnees_expressions):
        donnees = re.split(r'(\+\+|--|00)', donnees)
        for j, valeur in enumerate(donnees):
            valeur = valeur.strip()
            if valeur not in ('++', '--', '00', ''):
                # On préformate les donnees en vue d'un traitement ulterieur.
                # Ex: '-5 ++ (1;|) -- 4' devient [(-5, "0"), "+", (1, "|"), "-", (4, "0")].
                if valeur[0] == '(' and valeur[-1] == ')' and test_parentheses(valeur[1:-1]):
                    valeur = valeur[1:-1]
                vals = [val.strip() for val in valeur.split(';')]
                if len(vals) == 1:
                    if vals[0].startswith('!'):
                        vals[0] = vals[0][1:]
                        vals.append('|')
                    else:
                        vals.append('0')
                else:
                    vals[1] = '$' + vals[1] + '$'
                if '=' in vals[0]:
                    vals[0], valeur_num = vals[0].split('=', 1)
                    valeur_num = eval(traduire_latex(valeur_num), maths.__dict__)
                else:
                    valeur_num = eval(traduire_latex(vals[0]), maths.__dict__)
                valeurs.add(valeur_num)
                correspondances[valeur_num] = vals[0]
                vals[0] = valeur_num
                donnees[j] = vals
        print donnees
        donnees_expressions[i] = donnees

    valeurs = sorted(valeurs)


    indices_denominateurs = []
    # indique que les lignes correspondantes se trouvent au denominateur

    # On initialise le code LaTeX de chaque ligne avec sa légende.
    # NB: la liste 'lignes' contiendra le code LaTeX pour chaque ligne.
    n = max(len(chaine) for chaine in expressions + [variable, resultat])
    lignes = [convertir_en_latex(variable)]
    i = 1
    for expression in expressions:
        if expression[0] == "!":
            expression = expression[1:]
            indices_denominateurs.append(i)
        lignes.append(convertir_en_latex(expression))
        i += 1
    lignes.append(convertir_en_latex(resultat))

    # On justifie le texte de la première colonne pour que le code LaTeX généré soit plus lisible.
    n = max(len(texte) for texte in lignes) + 1
    for i, ligne in enumerate(lignes):
        lignes[i] = ligne.ljust(n)

    def latex_signe(val, co):
        if val == nan:
            return '||'
        elif val > 0:
            return '+' if not co%2 else ''
        elif val < 0:
            return '$-$' if not co%2 else ''
        else:
            return '0'

    dict_signes = {'++': 1, '--': -1, '00': 0}


    # On génère maintenant le code LaTeX correspondant au tableau proprement dit.
    # On procède colonne par colonne.
    nbr_colonnes = 2*len(valeurs)
    for co in xrange(1, nbr_colonnes):
        colonne = ['' for i in xrange(len(donnees_expressions) + 2)]

        # Première ligne (valeurs de la variable)
        if co%2:
            # Il s'agit d'une valeur (et non d'un signe + ou -)
            valeur_num = valeurs[(co - 1)//2]
            valeur = correspondances[valeur_num]

            # On applique un formatage LaTeX à certaines expressions :
            colonne[0] = convertir_en_latex(valeur)

        signe = 1
        # (1 pour positif, -1 pour négatif, nan pour valeur interdite -> cf. latex_signe())

        # Autres lignes
        for li, donnees in enumerate(donnees_expressions):
            # À quel endroit de la ligne sommes-nous ?
            valeurs_precedentes = [k for k, val in enumerate(donnees) if not isinstance(val, basestring) and val[0]<=valeur_num]
            if valeurs_precedentes:
                # position de la dernière valeur de la ligne
                position = valeurs_precedentes[-1]
                if co%2: # on est au niveau d'une valeur
                    if donnees[position][0] == valeur_num:
                        if donnees[position][1] == '|':
                            colonne[li + 1] = '||'
                            signe = nan
                        else:
                            colonne[li + 1] = donnees[position][1]
                            signe *= 0
                        if li + 1 in indices_denominateurs:
                            signe = nan
                else: # on est entre deux valeurs
                    assert position + 1 < len(donnees), "Verifiez que les valeurs de la ligne sont bien rangees dans l'ordre croissant."
                    signe_ = dict_signes[donnees[position + 1]]
                    colonne[li + 1] += latex_signe(signe_, co) # le signe qui est juste apres la derniere valeur
                    signe *= signe_
            else: # on est au debut
                if co%2 == 0: # on est entre deux valeurs
                    signe_ = dict_signes[donnees[1]]
                    colonne[li + 1] += latex_signe(signe_, co)
                    signe *= signe_

        # Dernière ligne : signe du produit ou du quotient
        colonne[-1] += latex_signe(signe, co)

        # On centre le texte dans la colonne pour que le code LaTeX généré soit plus lisible.
        n = max(len(texte) for texte in colonne) + 2
        for i, ligne in enumerate(colonne):
            colonne[i] = ligne.center(n)

        # Une fois la colonne entièrement générée, le texte de la colonne est rajouté à chaque ligne
        for num, text in enumerate(colonne):
            lignes[num] += '&' + text


    # Cas particulier : 'produit' de 1 seul élément
    if not afficher_resultat:
        lignes.pop()

    # Et on rassemble les lignes
    if cellspace:
        code = "\\begin{center}\n\\begin{tabular}{|Sc|" + (nbr_colonnes - 1)*"Sc" + "|}\n\\hline\n"
    else:
        code = "\\begin{center}\n\\begin{tabular}{|c|" + (nbr_colonnes - 1)*"c" + "|}\n\\hline\n"
    for ligne in lignes:
        code += ligne + "\\\\\n\\hline\n"
    code += "\\end{tabular}\n\\end{center}\n% " + chaine_originale + "\n"
    return code




#2x+1:-;-1/2;+#-3x+2:-;2/3;+
