# -*- coding: utf-8 -*-
# #############################################################
#                                            ..
#                       ....  ............   ........
#                     .     .......   .            ....  ..
#                   .  ... ..   ..   ..    ..   ..... .  ..
#                   .. .. ....@@@.  ..  .       ........  .
#              ..  .  .. ..@.@@..@@.  .@@@@@@@   @@@@@@. ....
#         .@@@@. .@@@@. .@@@@..@@.@@..@@@..@@@..@@@@.... ....
#       @@@@... .@@@.. @@ @@  .@..@@..@@...@@@.  .@@@@@.    ..
#     .@@@..  . @@@.   @@.@@..@@.@@..@@@   @@ .@@@@@@..  .....
#    ...@@@.... @@@    .@@.......... ........ .....        ..
#   . ..@@@@.. .         .@@@@.   .. .......  . .............
#  .   ..   ....           ..     .. . ... ....
# .    .       ....   ............. .. ...
# ..  ..  ...   ........ ...      ...
#  ................................
#
# #############################################################
# impression_canon.py
#
#     Classe impression pour l'imprimante canon iR C3580i
#
# Copyright (c) 2006, 2007, 2008, 2009 by Cr@ns (http://www.crans.org)
# #############################################################
"""
Classe pour gérer l'envoie de pdf à l'imprimante.
Calcule le coût des options d'impression.
"""
__version__ = '9.11'

import sys, os.path
from gestion.config import impression as config_impression
from commands import getstatusoutput

if '/usr/scripts' not in sys.path:
    sys.path.append('/usr/scripts')
from cranslib.utils import logs
from subprocess import Popen, PIPE
from base import FichierInvalide, SoldeInsuffisant, PrintError, SettingsError

# ######################################################## #
#                      CONSTANTES                          #
# ######################################################## #

FICHIER_LOG = "/var/log/log_couts/impressions"

SNMP_CAPA_B = "mib-2.43.11.1.1.8.1.1"
SNMP_CAPA_C = "mib-2.43.11.1.1.8.1.2"
SNMP_CAPA_M = "mib-2.43.11.1.1.8.1.3"
SNMP_CAPA_Y = "mib-2.43.11.1.1.8.1.4"
SNMP_TON_B = "mib-2.43.11.1.1.9.1.1"
SNMP_TON_C = "mib-2.43.11.1.1.9.1.2"
SNMP_TON_M = "mib-2.43.11.1.1.9.1.3"
SNMP_TON_Y = "mib-2.43.11.1.1.9.1.4"
SNMP_BAC1 = "mib-2.43.8.2.1.10.1.2"
SNMP_BAC2 = "mib-2.43.8.2.1.10.1.3"
SNMP_BAC3 = "mib-2.43.8.2.1.10.1.4"
SNMP_BAC4 = "mib-2.43.8.2.1.10.1.5"
SNMP_COUNT_A4 = "enterprises.1602.1.11.1.4.1.4.113"
SNMP_COUNT_A3 = "enterprises.1602.1.11.1.4.1.4.112"
SNMP_COUNT_A4c = "enterprises.1602.1.11.1.4.1.4.123"
SNMP_COUNT_A3c = "enterprises.1602.1.11.1.4.1.4.122"
SNMP_COUNT_TOT = "enterprises.1602.1.11.1.4.1.4.101"
SNMP_ETAT = "hrPrinterStatus.1"
SNMP_ERR = "hrPrinterDetectedErrorState.1"

DECOUVERT_AUTHORISE = config_impression.decouvert

DICT_AGRAFAGE = { "None" : "aucune agrafe",
                  "TopLeft" : u"agrafe en haut à gauche",
                  "TopRight" : u"agrafe en haut à droite",
                  "BottomLeft" : u"agrafe en bas à gauche",
                  "BottomRight" : u"agrafe en bas à droite",
                  "Left": u"deux agrafes sur le bord gauche",
                  "Right" : u"deux agrafes sur le bord droit",
                  "Top" : u"deux agrafes sur le bord supérieur",
                  "Bottom" : u"deux agrafes sur le bord inférieur",
}

#AVAIL_AGRAFES = ["None", "TopLeft", "TopRight", "Left", "BottomLeft", "BottomRight", "Right"]
#les agrafes sur les côtés (*2) sont désactivées, car elles nécessitent des
#feuilles en A4. On alimente désormais l'imprimante exclusivement en A4R
#(plus faible probabilité de bourrer)
AVAIL_AGRAFES = ["None", "TopLeft", "TopRight", "BottomLeft", "BottomRight"]

DICT_PAPIER = { 'A4' : "Papier A4 ordinaire",
                'A3' : "Papier A3 ordinaire",
                #'A4tr' : "Transparent A4",
                }

def _uniq_jid():
    """ Alloue un jid unique """
    fname = '/var/impression/jid'
    ## Maybe need a lock ?
    try:
        f = file(fname,'r+')
        cur = int(f.read())+1
        f.seek(0)
    except (IOError,ValueError):
        cur = 0
        f = file(fname,'w')
    f.write(str(cur))
    f.close()
    return cur

# ######################################################## #
#                    CLASSE IMPRESSION                     #
# ######################################################## #
#
#
class impression:
    """impression

    Un objet impression correspond à un fichier pdf et un adhérent.
    """
    # fichier (chemin)
    _fichier = ""
    # adherent (instance)
    _adh = None
    # paramettres
    _settings = {
        'agrafage': 'None',
        'papier': 'A4',
        'couleur': False,
        'recto_verso': False,
        'livret': False,
        'copies': 1,
        'portrait': True,
    }
    # le prix de l'impression
    _prix = 0.0
    _pages = 0
    # le cout de base encre pour une impression en couleurs/n&b
    #    (prix pour papier A4)
    _base_prix_nb = 0.0
    _base_prix_couleurs = 0.0

    # Format du pdf, tout droit sorti de pdfinfo
    # (les dimensions sont donc en pts)
    _format = '(A4)'
    _width = 595.28
    _height = 841.89
    
    # Jid unique, à définir avant l'impression
    _jid = 0

    def __init__(self, path_to_pdf, adh = None):
        """impression(path_to_pdf [, adh])

        Crée un nouvel objet impression à partir du fichier pdf pointé
        par path_to_pdf. Si adh ext donné, il peut être soit une
        instance d'un objet adhérent de crans_ldap soit le login de
        l'adhérent. Lève l'exception FichierInvalide si le fichier
        n'existe pas ou si ce n'est pas un pdf.
        """

        # On ouvre les logs
        self.log = logs.getFileLogger('impression')

        self._fichier = path_to_pdf
        self._adh = adh

        # on verifie que le fichier existe
        if not os.path.isfile(path_to_pdf):
            raise FichierInvalide, ("Fichier introuvable", path_to_pdf)
        if not open(path_to_pdf).read().startswith("%PDF"):
            raise FichierInvalide, ("Le fichier ne semble pas etre un PDF", path_to_pdf)

        # on compte les pages et on regarde le format
        pdfinfo = Popen(["pdfinfo",self._fichier],stdout=PIPE,stderr=PIPE).communicate()
        if pdfinfo[1] <> '':
            raise FichierInvalide(u"pdfinfo n'arrive pas a lire le fichier (il est peut-etre corrompu ou protege par un mot de passe), https://wiki.crans.org/VieCrans/ImpressionReseau#Format_des_fichiers",path_to_pdf)
        self._pages = -1
        for line in pdfinfo[0].split('\n'):
            if line.startswith('Pages'):
                self._pages = int(line.split()[1])
            elif line.startswith('Page size'):
                size = line.split()
                if len(size) <= 6:
                    self._format = "Unknown"
                else:
                    self._format = size[6]

                self._width = float(size[2])
                self._height = float(size[4])
                # Hack pour mieux reconnaître les formats
                w = min(self._width,self._height)
                h = max(self._width,self._height)
                err = 100
                if abs(w - 595) < err and abs(h - 842) < err:
                    self._format = "(A4)"
                elif abs(w - 842) < err and abs(h - 1180) < err:
                    self._format = "(A3)"
        if self._pages <= 0:
            raise FichierInvalide(u"Impossible de lire le nombre de pages",path_to_pdf)

        if not self._format in ['(A4)','(A3)']:
            pass
            #raise FichierInvalide, u"Seuls les formats A3 et A4 sont supportes"
        # calcule le prix de l'encre tout de suite
        self._calcule_prix()


    def _pdfbook(self):
        page = "pdfinfo \"%s\" | grep Pages | awk '{print $2}'" % self._fichier
        (status, npage) = getstatusoutput(page)
        if status != 0:
            self.log.error("pdfinfo status:%d |  rep: %s" % (status, npage))
            raise FichierInvalide, ("pdfinfo: Impossible de trouver le nombre de page du fichier",
                                    self._fichier)
        if int(int(npage)/4*4) < int(npage):
            sig=int((int(npage)/4 +1)*4)
        else:
            sig=int(npage)
        if self._settings['papier'] == 'A3':
            newfile =  self._fichier[:-4] + '-a3book.pdf'
            pdfbook = "pdfbook --signature %s --paper a3paper %%s --outfile %s" % (sig,newfile)
        else:
            newfile = self._fichier[:-4] + '-book.pdf'
            pdfbook = "pdfbook --signature %s %%s --outfile %s" % (sig,newfile)
        (status, rep) = getstatusoutput(pdfbook % self._fichier)
        self.log.info("%s |  rep: %s" % ((pdfbook % self._fichier), rep))
        self._fichier = newfile
        if status != 0:
            self.log.error("pdfbook status:%d |  rep: %s" % (status, rep))
            raise FichierInvalide, ("pdfbook: Impossible de convertir le fichier",
                                    self._fichier)


    def changeSettings(self, **kw):
        """changeSettings([keyword=value])

        Change les parametres de l'impression, recalcule et renvoie le nouveau prix.
        Lève une exceotion SettingError si les paramètres son invalides.
        """
        #recalcule et renvoie le prix

        couleur = kw.get('couleur', None)
        if couleur in [True, False]:
            self._settings['couleur'] = couleur
        elif couleur == "True":
            self._settings['couleur'] = True
        elif couleur == "False":
            self._settings['couleur'] = False

        try:
            if int(kw['copies']) >= 1:
                self._settings['copies'] = int(kw['copies'])
        except:
            pass

        recto_verso =  kw.get('recto_verso', None)
        if recto_verso == "True": recto_verso = True
        if recto_verso == "False": recto_verso = False
        if recto_verso in [True, False]:
            self._settings['recto_verso'] = recto_verso

        papier = kw.get('papier', None)
        if papier in ['A4', 'A3', 'A4tr']:
            self._settings['papier'] = papier
            if papier == 'A4tr':
                self._settings['recto_verso'] = False
                self._settings['agrafage'] = 'None'

        agrafage = kw.get('agrafage', None)
        if agrafage in ["None", "TopLeft", "Top", "TopRight",
                        "Left", "Right", "BottomLeft", "BottomRight"]:
            self._settings['agrafage'] = agrafage

        livret = kw.get('livret', None)
        if livret == "True": livret = True
        if livret == "False": livret = False
        if livret in [True, False]:
            self._settings['livret'] = livret
            if livret:
                self._settings['portrait'] = True	#Le mode paysage est géré par _pdfbook
                self._settings['recto_verso'] = True
                self._settings['agrafage'] = 'None'
                if self._settings['papier'] == 'A4tr':
                    self._settings['papier'] = 'A4'
            else:
                self._settings['portrait'] = self._width < self._height

        return self._calcule_prix()

    def printSettings(self):
        """printSettings()

        Affiche les paramètres courrants sur la sortie standard
        """
        if self._settings['couleur']:
            print "Type impression: Couleur"
        else:
            print "Type impression: Noir et blanc"

        print "Papier: " + DICT_PAPIER[self._settings['papier']]

        if self._settings['livret']:
            print u"Agrafage: Livret (piqûre à cheval)"
        else:
            print "Agrafage: " + DICT_AGRAFAGE[self._settings['agrafage']]
            if self._settings['recto_verso']:
                print "Disposition: recto/verso"
            else:
                print "Disposition: recto"

        print "Copies: " + str(self._settings['copies'])


    def prix(self):
        """prix()

        Renvoie le prix courrant de l'impression
        """
        return self._prix

    def fileName(self):
        """fileName()

        renvoie le nom du fichier pdf (exemple : monPdf.pdf)
        """
        return os.path.basename(self._fichier)

    def filePath(self):
        """filePath()

        renvoie le chemin d'accès au fichier pdf.
        """
        return self._fichier

    def pages(self):
        """pages()

        renvoie le nombre de pages du document (page au sens nombre de
        faces à imprimer et non le nombre de feuilles)
        """
        return self._pages


    def imprime(self):
        """imprime()

        imprime le document pdf. débite l'adhérent si adhérent il y a.
        (si il a été indiqué à l'initialisation de l'objet)
        """
        # Ce fichier est rempli par le script print_status.py du même dossier
        # à intervalle régulier
        with open('/usr/scripts/var/print_status/error.txt', 'r') as stat:
            err = stat.read()
            if err:
                raise PrintError('Imprimante en panne :' + err)
        self._jid = _uniq_jid()

        # debite l'adhérent si adherent il y a
        if (self._adh != None):
            adh = self._adh.split('@')
            if len(adh) > 1:
                adh = adh[1:]
            adh = self._get_adh(adh[0])
            self._calcule_prix() # Normalement inutile, mais évite les races
            if (self._prix > (adh.solde() - DECOUVERT_AUTHORISE)):
                raise SoldeInsuffisant
            adh.solde(-self._prix, "impression(%d): %s par %s" % (self._jid,self._fichier,self._adh))
            adh.save()
            del adh
        # imprime le document
        self._exec_imprime()

    def _calcule_prix(self):

        faces = self._pages

        if self._settings['livret']:
            feuilles = int((faces+3)/4)
            faces = 2 * feuilles
        elif self._settings['recto_verso']:
            feuilles = int(faces/2.+0.5)
        else:
            feuilles = faces

        if (self._settings['papier'] == "A3"):
            c_papier = config_impression.c_a3
            pages = 2*faces
        else:
            pages = faces
            if self._settings['papier'] == "A4tr":
                c_papier = config_impression.c_trans
            else:
                c_papier = config_impression.c_a4

        if self._settings['couleur']:
            c_impression = c_papier * feuilles + config_impression.c_face_couleur * pages
        else:
            c_impression = c_papier * feuilles + config_impression.c_face_nb * pages

        # Cout des agrafes
        if self._settings['agrafage'] in ["Top", "Bottom", "Left", "Right"] or self._settings['livret']:
            nb_agrafes = 2
        elif self._settings['agrafage'] in ["None", None]:
            nb_agrafes = 0
        else:
            nb_agrafes = 1

        if feuilles <= 50:
            c_agrafes = nb_agrafes * config_impression.c_agrafe
        else:
            c_agrafes = 0

        c_total = int(self._settings['copies'] * ( c_impression +
                      c_agrafes ) + 0.5) # arrondi et facture

        self._prix = float(c_total)/100
        return self._prix

    def _get_adh(self, adh):
        if type(adh) == str:
            from gestion.ldap_crans import CransLdap
            adh = CransLdap().getProprio(adh, 'w')
        return adh



    ## ################################# ##
    ## fonction qui imprime pour de vrai ##
    ## ################################# ##
    ##
    def _exec_imprime(self):
        """ Envoie l'impression a l'imprimante avec les parametres actuels """

        if self._settings['livret']:
            self._pdfbook()

        if (self._adh != None):
            self.log.info('Impression(%d) [%s] : %s' % (self._jid, self._adh, self._fichier))
        else:
            self.log.info("Impression(%d) : %s" % (self._jid, self._fichier))

        # Envoi du fichier à CUPS
        options = ''
        # Création  de la liste d'options
        # pour le nombre de copies et specifie non assemblee
        #options += '-# %d -o Collate=True' % self.nb_copies

        # Pour spécifier l'imprimante
        options += ' -P canon_irc3580'


        # Pour spécifier un jobname de la forme jid:adh:nom_du_fichier
        jobname = '%d:%s:%s' % (self._jid, self._adh, self._fichier.split('/')[-1].replace("\"","\\\""))
        # Ce nom apparaît sur l'interface d'impression de l'imprimante:
        options += " -o CNDocName=\"%s\"" %jobname

        # Et dans lpq:
        options += " -T \"%s\"" % jobname

        # Pour donner le login de l'adherent
        options += ' -U \"%s\"' % self._adh

        # Pour spécifier la version du language postscript utilisé par pdftops
#        options += ' -o pdf-level3'

        # Pour demander une page de garde
        #options += ' -o job-sheets=crans' #page de garde de type standard
        #options += " -o job-billing=%.2f" % self.cout
        #options += ' -o job-sheets=none'

        #Indique la présence d'un bac de sortie avec agrafeuse
 #       options += " -o Option20=MBMStaplerStacker -o OutputBin=StackerDown"

        if self._settings['papier'] == 'A4tr':
            options += ' -o InputSlot=SideDeck -o MediaType=OHP'
            options += ' -o PageSize=A4'
        elif self._settings['papier'] == 'A4':
            options += ' -o PageSize=A4'
        else:
            options += ' -o pdf-expand -o pdf-paper=841x1190 -o PageSize=A3'

        if self._settings['portrait']:
            if self._settings['recto_verso']:
                options += ' -o sides=two-sided-long-edge'
            else:
                options += ' -o sides=one-sided'
        else:
            if self._settings['recto_verso']:
                options += ' -o sides=two-sided-short-edge -o landscape'
            else:
                options += ' -o sides=one-sided -o landscape'
        if self._settings['couleur']:
            options += ' -o CNColorMode=color'
        else:
            options += ' -o CNColorMode=mono'

        if self._settings['livret']:
            options += ' -o CNSaddleStitch=True'
            options += ' -o OutputBin=TrayC'
        else:
            options += ' -o OutputBin=TrayA'
            options += ' -o Collate=StapleCollate -o StapleLocation=%s' % self._settings['agrafage']


        if not self._settings['livret'] and self._settings['agrafage'] in ['None', None]:
            left = self._settings['copies']
            while left >= 100:
                cmd = "lpr %s -# %d %s" % (options, 99, self._fichier)
                left -= 99
                (status, rep) = getstatusoutput(cmd)
                self.log.info("printing: %s" % cmd)
                if status != 0:
                    self.log.error("erreur impression")
                    self.log.error("lpr status:%d |  rep: %s" % (status, rep))
                    raise PrintError, "%s \n   status:%d rep: %s" % (cmd, status, rep)
            cmd = "lpr %s -# %d %s" % (options, left, self._fichier)
            (status, rep) = getstatusoutput(cmd)
            self.log.info("printing: %s" % cmd)
            if status != 0:
                self.log.error("erreur impression")
                self.log.error("lpr status:%d |  rep: %s" % (status, rep))
                raise PrintError, "%s \n   status:%d rep: %s" % (cmd, status, rep)

        else:
            cmd = "lpr %s %s" % (options, self._fichier)
            self.log.info("printing [%s]: %s" % (cmd, self._settings['copies']))
            for i in range(self._settings['copies']):
                (status, rep) = getstatusoutput(cmd)
                if status != 0:
                    self.log.error("erreur impression")
                    self.log.error("lpr status:%d |  rep: %s" % (status, rep))
                    raise PrintError, "%s \n   status:%d rep: %s" % (cmd, status, rep)

