# -*- coding: utf-8 -*-

""" Collection de fonction/classe pour avoir un bel affichage

Copyright (C) Frédéric Pauget
Licence : GPLv2
"""

import os
import re
import shlex
import subprocess
import sys
if not "/usr/scripts" in sys.path:
    sys.path.append("/usr/scripts")
import cranslib.cransstrings

# Détermination de l'encodage
ENCODING = None
try:
    from locale import getpreferredencoding
    ENCODING = getpreferredencoding()
except:
    pass

if not ENCODING:
    ENCODING = sys.stdin.encoding or 'UTF-8'

# Si aucune locale n'est définie, on se met en...
if ENCODING == "ANSI_X3.4-1968":
    ENCODING = 'UTF-8'

if 'TERM' in os.environ and os.environ['TERM'] != 'unknown':
    el = subprocess.Popen('tput cr ; tput el', shell=True, stdout=subprocess.PIPE).stdout.read()
else:
    el = subprocess.Popen('tput -Tvt100 cr ; tput -Tvt100 el', shell=True, stdout=subprocess.PIPE).stdout.read()

try:
    stdout_atty = sys.stdout.isatty()
except (IOError, AttributeError):
    stdout_atty = False

def dialog(backtitle, arg, dialogrc=''):
    """
    Affiche la boîte de dialogue définie avec les arguments fournis
    (cf man dialog)

    Paramètres:
      - ``backtitle`` : (str) texte de l'en-tête, situé en haut à gauche
      - ``arg``       : (str ou list) ensemble des paramètres à passer au
                        programme dialog
      - ``dialogrc``  : (str) chemin vers le dialogrc à utiliser

      Utilises:
      - :py:var:`os.environ`
      - :py:func:`shlex.split`
      - :py:class:`subprocess.Popen`
      - :py:func:`sys.exit`

    Peut lever:
      - :py:exc:`RuntimeError`

    Retournes:
      - soit un tuple dont le premier élément est le code d'erreur (0 ou 1) et
        le second la sortie du programme dialog (une liste ou une liste vide)
      - soit rien du tout parce que sys.exit(0)
    """
    # Tant que gest_crans (au moins) n'a pas été modifié pour que arg soit une
    # liste, ce kludge est nécessaire.
    if not isinstance(arg, list):
        arg = shlex.split(to_encoding(arg))

    commande = [
        "/usr/bin/dialog",
        "--backtitle", backtitle,
        "--cancel-label", "Retour"
        ] + arg

    # On sort de l'unicode sandwich, il faut donc transformer en bytes encodés
    # en UTF-8.
    commande = [to_encoding(elem) for elem in commande]

    # On copie l'environnement afin de pouvoir écraser une variable pour
    # l'appel au programme dialog sans que cela affecte les autres processus.
    environnement = os.environ.copy()

    # Si dialog est fourni, on modifie effectivement l'environnement
    if dialogrc:
        environnement["DIALOGRC"] = dialogrc

    # Le résultat du programme dialog est dans stderr, on la récupère dans
    # un pipe
    processus = subprocess.Popen(commande, stderr=subprocess.PIPE, env=environnement)

    # Récupération du contenu du pipe
    _, sortie = processus.communicate()
    resultat = sortie.splitlines()

    # Récupération du code d'erreur
    code_erreur = processus.returncode

    if code_erreur == 1:
        # L'utilisateur a annulé
        return (1, [])
    elif code_erreur == 255 and resultat:
        # La liste des arguments passés au programme dialog est invalide
        raise RuntimeError(arg, resultat[1].strip())
    elif code_erreur == 255:
        # L'utilisateur a appuyé sur ÉCHAP. Dans ce cas, on lui demande de
        # confirmer.
        # "0" et "0" sont là pour ajuster la taille de la fenêtre à son
        # contenu
        arg_confirmation = [
            "--title", "Annulation",
            "--yesno", "Quitter ?\nLes dernières modifications seront perdues.",
            "0", "0"
        ]
        code_confirmation, _ = dialog(backtitle, arg_confirmation, dialogrc)
        if code_confirmation == 0:
            # L'utilisateur veut quitter (vraiment).
            sys.exit(0)
        else:
            # L'appui sur ÉCHAP était une fausse manip, on recommence
            return dialog(backtitle, arg, dialogrc)
    elif not resultat:
        # Quand le code d'erreur est nul, il ne faut pas que resultat soit
        # vide.
        resultat = ['']

    return (0, resultat)

def coul(txt, col=None, dialog=False):
    """
    Retourne la chaine donnée encadrée des séquences qui
    vont bien pour obtenir la couleur souhaitée
    Les couleur sont celles de codecol
    Il est possible de changer la couleur de fond grace aux couleur f_<couleur>
    """
    if not stdout_atty or not col:
        return txt

    codecol = {'rouge': 31,
               'vert': 32,
               'jaune': 33,
               'bleu': 34,
               'violet': 35,
               'cyan': 36,
               'gris': 30,
               'gras': 50}
    codecol_dialog = {'rouge': 1,
                      'vert':  2,
                      'jaune': 3,
                      'bleu':  4,
                      'violet': 5,
                      'cyan': 6,
                      'gris': 0,
                      'gras': 'b'}

    if dialog:
        try:
            txt = "\Z%s%s\Zn" % (codecol_dialog[col], txt)
        finally:
            return txt
    try:
        if col[:2] == 'f_':
            add = 10
            col = col[2:]
        else:
            add = 0
        txt = "\033[1;%sm%s\033[1;0m" % (codecol[col] + add, txt)
    finally:
        return txt

OK = coul('OK', 'vert')
WARNING = coul('WARNING', 'jaune')
ERREUR = coul('ERREUR', 'rouge')

def to_unicode(txt, enc=ENCODING):
    if isinstance(txt, unicode):
        return txt
    else:
        # On suppose d'abord que le texte est en UTF-8
        try:
            return txt.decode("UTF-8")
        except:
            # Sinon c'est surement de l'iso
            # donc on le décode en utf-8 \o/
            return txt.decode("UTF-8")

def to_encoding(txt, enc=ENCODING):
    return to_unicode(txt).encode(enc, 'ignore')

def cprint(txt, col='blanc', newline=True):
    t = coul(to_encoding(txt), col)
    if newline:
        print t
    else:
        print t,

def tableau(data, titre=None, largeur=None, alignement=None, format=None, dialog=False, width=None):
    """
    Retourne une chaine formatée repésentant un tableau.

          data : liste de listes, chacune contenant les valeurs d'une ligne

         titre : liste des titres
                 Si none, n'affiche pas de ligne de titre

       largeur : liste des largeurs des colonnes, '*' met la plus grande
                 largeur possible.
                 Si None, réduit aux max chaque colonne

    alignement : liste des alignements : c = centrer
                                         g = gauche
                                         d = droit
                 Si None, met c pour chaque colonne

        format : liste des formats : s = string
                                     o = octet
                 Si None, s pour chaque colonne
    """
    sep_col = u'|'
    if data:
        nbcols = len(data[0])
    elif titre:
        nbcols = len(titre)
    else:
        return u'Aucune donnée'

    # Formats
    #########
    if not format:
        format = ['s'] * nbcols

    def reformate(data, format):
        if format == 's':
            try:
                return unicode(data)
            except:
                sys.stderr.write("Cannot cast to unicode %r\n" % data)
                return unicode(data, errors='ignore')

        elif format == 'o':
            data = float(data)
            if data > 1024**3:
                return str(round(data/1024**3, 1)) + 'Go'
            elif data > 1024**2:
                return str(round(data/1024**2, 1)) + 'Mo'
            elif data > 1024:
                return str(round(data/1024, 1)) + 'ko'
            else:
                return str(round(data, 1)) + 'o'

    data = [[reformate(ligne[i], format[i]) for i in range(nbcols)] for ligne in data]

    # Largeurs
    ##########
    if not largeur:
        largeur = [max([len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m', '', ligne[i])) for ligne in data]) for i in range(nbcols)]
    elif '*' in largeur:
        if width:
            cols = width
        else:
            rows, cols = get_screen_size()
            if dialog:
                cols = cols - 6
        for i in range(nbcols):
            if largeur[i] in ['*', -1]:
                largeur[i] = max(cols - sum([l for l in largeur if l != '*']) - nbcols - 1, 3)
                break

    # Alignement
    ############
    if not alignement:
        alignement = ['c'] * nbcols

    def aligne(data, alignement, largeur):
        # Longeur sans les chaines de formatage
        l = len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m', '', data))

        # Alignement
        if l > largeur:
            # découpage d'une chaine trop longue
            regexp = re.compile('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m')
            new_data = u''
            new_len = 0

            # On laisse la mise en forme et on coupe les caratères affichés
            while True:
                s = regexp.search(data)
                if s and not s.start():
                    # c'est de la mise en forme
                    new_data += data[:s.end()]
                    data = data[s.end():]
                elif new_len < largeur - 1:
                    # c'est un caratère normal, et il y a la place
                    new_data += data[0]
                    data = data[1:]
                    new_len += 1
                else:
                    # c'est un caratère normal mais on a dépassé le max
                    data = data[1:]
                if not data:
                    return new_data + '*'

        elif l == largeur:
            return data

        elif alignement == 'g':
            return u' ' + data + u' '*(largeur-l-1)

        elif alignement == 'd':
            return u' '*(largeur-l-1) + data + u' '

        else:
            return u' '*((largeur-l)/2) + data + u' '*((largeur-l+1)/2)

    data = [[aligne(ligne[i], alignement[i], largeur[i]) for i in range(nbcols)] for ligne in data]

    # Le titre
    ##########
    if titre:
        # ligne de titre
        chaine = sep_col + sep_col.join([aligne(titre[i], 'c', largeur[i]) for i in range(nbcols)]) + sep_col + u'\n'
        # ligne de séparation
        chaine += sep_col + u'+'.join([u'-'*largeur[i] for i in range(nbcols)]) + sep_col + u'\n'
    else:
        chaine = u''

    # Les données
    #############
    chaine += u'\n'.join([sep_col + sep_col.join([ligne[i] for i in range(nbcols)]) + sep_col for ligne in data])

    return chaine

def get_screen_size():
    """Retourne la taille de l'écran.

    Sous la forme d'un tuble (lignes, colonnes)"""

    try:
        from termios import TIOCGWINSZ
        from struct import pack, unpack
        from fcntl import ioctl
        s = pack("HHHH", 0, 0, 0, 0)
        rows, cols = unpack("HHHH", ioctl(sys.stdout.fileno(), TIOCGWINSZ, s))[:2]
        return (rows, cols)
    except:
        return (24, 80)

def prompt(prompt, defaut=u'', couleur='gras'):
    """
    Pose la question prompt en couleur (défaut gras), retourne la réponse.
    """
    prompt = cranslib.cransstrings.decode_dammit(prompt)
    defaut = cranslib.cransstrings.decode_dammit(defaut)
    # coul renvoie alors un unicode
    prompt_s = coul(prompt, couleur)
    if defaut:
        prompt_s += u" [%s]" % defaut
    # On fait tout pour ne pas faire crasher le script appelant
    # si on lève une erreur, on la rattrappe et on laisse une chance
    # de refaire une proposition.
    # On laisse néanmoins la possibilité de sortir sur un Ctrl+C
    while True:
        try:
            v = raw_input(prompt_s.encode(ENCODING))
            v = cranslib.cransstrings.decode_dammit(v).strip()
            if not v:
                v = defaut
            return v
        except UnicodeDecodeError as error:
            try:
                print "UnicodeDecodeError rattrappée (chaîne : %s)" % v
            except UnicodeDecodeError as error2:
                print "UnicodeDecodeError catched but could not be displayed. Trying ASCII."

class anim:
    """ Permet de créer une animation :
        truc................./
        truc.................-
        truc.................\
        truc.................|
        ou une barre de progression si le nombre total d'itérations est founi.
        """
    def __init__(self, truc, iter=0):
        """ Affichage de :
            truc................."""
        self.txt = truc + '.'*(45-len(truc))
        self.c = 1
        self.iter = iter
        sys.stdout.write(self.txt)
        sys.stdout.flush()

    def reinit(self):
        """ Efface la ligne courrante et
        affiche  :      truc................. """
        sys.stdout.write(el + self.txt)
        if self.iter:
            sys.stdout.write(' '*28)
            sys.stdout.write(el + self.txt)
        sys.stdout.flush()

    def cycle(self):
        """ Efface la ligne courrante et
        affiche  :      truc..................?
        ? caratère variant à chaque appel """
        if self.iter != 0:
            prog = float(self.c) / float(self.iter)
            pprog = float(self.c-1) / float(self.iter)
            n = int(20 * prog)
            if 100*prog != 100*pprog:
                msg = "%s[%s>%s] %3i%%" % (self.txt, '='*n, ' '*(20 - n), int(100*prog))
                sys.stdout.write(el + msg)
        else:
            sys.stdout.write(el + self.txt)
            sys.stdout.write('/-\|'[self.c%4])
        sys.stdout.flush()
        self.c += 1
