#!/usr/bin/env python
# -*- coding: utf-8 -*-
# #############################################################
#                                            ..
#                       ....  ............   ........
#                     .     .......   .            ....  ..
#                   .  ... ..   ..   ..    ..   ..... .  ..
#                   .. .. ....@@@.  ..  .       ........  .
#              ..  .  .. ..@.@@..@@.  .@@@@@@@   @@@@@@. ....
#         .@@@@. .@@@@. .@@@@..@@.@@..@@@..@@@..@@@@.... ....
#       @@@@... .@@@.. @@ @@  .@..@@..@@...@@@.  .@@@@@.    ..
#     .@@@..  . @@@.   @@.@@..@@.@@..@@@   @@ .@@@@@@..  .....
#    ...@@@.... @@@    .@@.......... ........ .....        ..
#   . ..@@@@.. .         .@@@@.   .. .......  . .............
#  .   ..   ....           ..     .. . ... ....
# .    .       ....   ............. .. ...
# ..  ..  ...   ........ ...      ...
#  ................................
#
# #############################################################
# __init__.py
#
#     Classe impression
#
# Copyright (c) 2006 by www.crans.org
# #############################################################
"""
Classe pour gérer l'envoie de pdf à l'imprimante.
Calcule le coût des options d'impression.
"""
__version__ = '1'

import sys, syslog, os.path
sys.path.append('/usr/scripts/gestion')
sys.path.append('/usr/scripts/')
import config.impression
import cout
from cranslib.utils import QuoteForPOSIX as escapeForShell
import cranslib.utils.logs
log = cranslib.utils.logs.getFileLogger('impression')
# Début : Ajout log pour réestimer les coûts
import time
import hptools
comm = hptools.snmp(host="imprimante.adm.crans.org",version="1",community="public")
# Fin

# ######################################################## #
#                      CONSTANTES                          #
# ######################################################## #
#
#
# Début : Ajout log pour réestimer les coûts
FICHIER_LOG="/var/log/log_couts/impressions"

SNMP_CAR_B_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.18.1.0"
SNMP_CAR_C_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.18.2.0"
SNMP_CAR_M_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.18.3.0"
SNMP_CAR_Y_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.18.4.0"
SNMP_CAR_B_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.1.0"
SNMP_CAR_C_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.2.0"
SNMP_CAR_M_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.3.0"
SNMP_CAR_Y_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.4.0"
SNMP_TAM_B_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.37.5.0"
SNMP_TAM_C_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.37.6.0"
SNMP_TAM_M_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.37.7.0"
SNMP_TAM_Y_PC="enterprises.11.2.3.9.4.2.1.4.1.10.1.1.37.8.0"
SNMP_TAM_B_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.5.0"
SNMP_TAM_C_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.6.0"
SNMP_TAM_M_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.7.0"
SNMP_TAM_Y_PR="enterprises.11.2.3.9.4.2.1.4.1.10.5.1.1.8.0"
# Fin
DECOUVERT_AUTHORISE = config.impression.decouvert

PAS_D_AGRAPHES = "pasdagraphes"
AGRAPHE_DIAGONALE = "agraphediagonale"
UNE_AGRAPHE = "uneagraphe"
DEUX_AGRAPHE = "Deuxagraphes"
TROIS_AGRAPHE = "troisAgraphes"
STITCHING = "stitching"
AGRAPHES_VALEURS_POSSIBLES = [PAS_D_AGRAPHES, AGRAPHE_DIAGONALE, UNE_AGRAPHE, DEUX_AGRAPHE, TROIS_AGRAPHE, STITCHING]
NB_AGRAPHES = {
    PAS_D_AGRAPHES: 0,
    AGRAPHE_DIAGONALE: 1,
    UNE_AGRAPHE: 1,
    DEUX_AGRAPHE: 2,
    TROIS_AGRAPHE: 3,
    STITCHING: 6,
}

PAPIER_A4 = "A4"
PAPIER_A4_TRANSPARENT = "A4tr"
PAPIER_A3 = "A3"
PAPIER_VALEURS_POSSIBLES = [PAPIER_A4, PAPIER_A4_TRANSPARENT]

IMPRESSION_COULEUR = "couleurs"
IMPRESSION_NB = "nb"
COULEURS_VALEURS_POSSIBLES = [IMPRESSION_COULEUR, IMPRESSION_NB]

IMPRESSION_RECTO = "recto"
IMPRESSION_RECTO_VERSO = "rectoverso"
IMPRESSION_RECTO_VERSO_SHORT = "rectoversoshort"
DISPOSITION_VALEURS_POSSIBLES = [IMPRESSION_RECTO, IMPRESSION_RECTO_VERSO, IMPRESSION_RECTO_VERSO_SHORT]

PU_AGRAPHE = config.impression.c_agrafe / 100.
PU_FEUILLE = {
    PAPIER_A4: config.impression.c_a4/100,
    PAPIER_A4_TRANSPARENT: config.impression.c_trans/100,
    PAPIER_A3: config.impression.c_a3/1000,
}


LABELS = {
    PAS_D_AGRAPHES: "pas d'agrafe",
    AGRAPHE_DIAGONALE: 'une agrafe en diagonale',
    UNE_AGRAPHE: 'une agrafe en haut',
    DEUX_AGRAPHE: '2 agrafes',
    TROIS_AGRAPHE: '3 agrafes',
    STITCHING: 'stitching (6 agrafes)',

    PAPIER_A4: "A4",
    PAPIER_A4_TRANSPARENT: "A4 transparent",
    PAPIER_A3: "A3",

    IMPRESSION_COULEUR: "impression couleurs",
    IMPRESSION_NB: "impression noir et blanc",

    IMPRESSION_RECTO: "impression recto",
    IMPRESSION_RECTO_VERSO: "impression recto-verso",
    IMPRESSION_RECTO_VERSO_SHORT: "impression recto-verso",
}

LPR_OPTIONS = {
    PAS_D_AGRAPHES: ' -o StapleLocation=None',
    AGRAPHE_DIAGONALE: ' -o StapleLocation=1diagonal',
    UNE_AGRAPHE: ' -o StapleLocation=1parallel',
    DEUX_AGRAPHE: ' -o StapleLocation=2parallel',
    TROIS_AGRAPHE: ' -o StapleLocation=3parallel',
    STITCHING: ' -o StapleLocation=Stitching',

    PAPIER_A4: ' -o InputSlot=Tray4 -o pdf-paper=571x817  -o PageSize=A4',
    PAPIER_A4_TRANSPARENT: ' -o PageSize=A4 -o InputSlot=Tray1 -o Media=Transparency',
    PAPIER_A3: ' -o pdf-expand -o pdf-paper=825x1166 -o InputSlot=Tray3 -o HPPaperPolicy=A3 -o PageSize=A3',

    IMPRESSION_COULEUR: ' -o HPColorasGray=False',
    IMPRESSION_NB: ' -o HPColorasGray=True',

    IMPRESSION_RECTO: ' -o sides=one-sided',
    IMPRESSION_RECTO_VERSO:  ' -o sides=two-sided-long-edge',
    IMPRESSION_RECTO_VERSO_SHORT: ' -o sides=two-sided-short-edge',
}


# ######################################################## #
#                       ERREURS                            #
# ######################################################## #
#

class FichierInvalide(Exception):
    """
    Exception renvoyée lorsqu'un fichier ne passe pas.
    utilisée avec deux arguments : une chaîne décrivant l'erreur et une chaîne avec le nom du fichier
    """
    def __str__(self):
        """
        Description de l'erreur.
        """
        return self.args[0]
    def file(self):
        """
        Nom du fichier qui pose problème
        """
        try:
            return self.args[1]
        except:
            return "n/a"

class SoldeInsuffisant(Exception):
    """
    Solde insuffisant pour l'impression demandée
    """
    pass
class PrintError(Exception):
    """
    Erreur lors de l'impression
    """
    pass
class SettingsError(Exception):
    """
    Erreur de paramètres.
    """
    pass
# ######################################################## #
#                    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 = {
        'agraphes': PAS_D_AGRAPHES,
        'papier': PAPIER_A4,
        'couleurs': IMPRESSION_COULEUR,
        'recto_verso': IMPRESSION_RECTO_VERSO,
        'copies':1,
    }
    # le prix de l'impression
    _prix = 0.0
    _nb_pages = 0
    _details_devis = []
    # 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


    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.
        """
        self._fichier = path_to_pdf
        # 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)
        self._adh = adh
        # calcule le prix de l'encre tout de suite
        try:
            self._base_prix_couleurs, self._nb_pages = cout.base_prix_couleurs(path_to_pdf)
            self._base_prix_nb, self._nb_pages = cout.base_prix_nb(path_to_pdf)
        except ValueError, e:
            raise FichierInvalide, ("PDF bugge, Analyse impossible : %s." % e, path_to_pdf)
        except Exception, e:
            raise Exception, "Erreur dans le calcul du cout : %s " % str(e)
        self._calcule_prix()


    def changeSettings(self, agraphes = None, papier = None, couleurs = None, recto_verso=None, copies=None):
        """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
        if (couleurs):
            if couleurs not in COULEURS_VALEURS_POSSIBLES:
                raise SettingsError, "Valeur de couleurs inconnue : %s" % str(couleurs)
            self._settings['couleurs'] = couleurs
        if (papier):
            if papier not in PAPIER_VALEURS_POSSIBLES:
                raise SettingsError, "Valeur de papier inconnue : %s" % str(papier)
            self._settings['papier'] = papier
        if (copies):
            try:
                int(copies)
                if int(copies) <1:
                    raise Exception
            except:
                raise SettingsError, "Valeur incorecte pour le nombre de copies"
            self._settings['copies'] = copies
        if (recto_verso):
            if recto_verso not in DISPOSITION_VALEURS_POSSIBLES:
                raise SettingsError, "Valeur inconnue : %s" % str(recto_verso)
            if papier==PAPIER_A4_TRANSPARENT and (recto_verso==IMPRESSION_RECTO_VERSO or recto_verso==IMPRESSION_RECTO_VERSO_SHORT):
                raise SettingsError, "Pas de recto-verso sur du papier transparent !!"
            self._settings['recto_verso'] = recto_verso
        if (agraphes):
            if agraphes not in AGRAPHES_VALEURS_POSSIBLES:
                raise SettingsError, "Valeur inconnue pour agrafes : %s" % str(agrafes)
            if papier!=PAPIER_A4 and agraphes!=PAS_D_AGRAPHES:
                raise SettingsError, "Le type de papier choisi ne permet pas d'utiliser l'agrafeuse"
            if recto_verso==IMPRESSION_RECTO_VERSO or recto_verso==IMPRESSION_RECTO_VERSO_SHORT:
                if self._nb_pages > 100 and agraphes!=PAS_D_AGRAPHES:
                    raise SettingsError, "Le document est trop volumineux pour utiliser l'agrafeuse"
            else:
                if self._nb_pages > 50 and agraphes!=PAS_D_AGRAPHES:
                    raise SettingsError, "Le document est trop volumineux pour utiliser l'agrafeuse"
            self._settings['agraphes'] = agraphes
        return self._calcule_prix()

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

        Affiche les paramètres courrants sur la sortie standard
        """
        print "Type impression: " + LABELS[self._settings['couleurs']]
        print "Agraphes: " + LABELS[self._settings['agraphes']]
        print "Papier: " + LABELS[self._settings['papier']]
        print "Disposition: " + LABELS[self._settings['recto_verso']]
        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'accs au fichier pdf.
        """
        return self._fichier


    def devisDetaille(self):
        """devisDetaille()

        renvoie un disctionnaire contenant le devis (intitulé, PU., quantité) pour tous les éléments de l'impression
        (papier, encre, agrafes...)
        """
        return self._details_devis

    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._nb_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)
        """
        # debite l'adhérent si adherent il y a
        if (self._adh != None):
            adh = self._get_adh(self._adh)
            if (self._prix > (adh.solde() - DECOUVERT_AUTHORISE)):
                raise SoldeInsuffisant
            adh.solde(-self._prix, "impression: " + self._fichier)
            adh.save()
            del adh
        # imprime le document
        self._exec_imprime()

    def printDevis(self):
        """printDevis()

        Affiche le devis sur la sortie standard.
        """
        print "titre \t\t |  p.u. \t quandtite \t total"
        for anItem in self._details_devis:
            print anItem[0][:5] + "\t\t | " + str(anItem[1]) + "\t\t | " + str(anItem[2]) + "\t\t | " + str(anItem[1]*anItem[2])

    def _calcule_prix(self):
        prix = 0
        facture = []

        # clacul du prix total pour l'encre
        if (self._settings['couleurs'] == IMPRESSION_COULEUR):
            base_prix_encre = self._base_prix_couleurs
        else:
            base_prix_encre = self._base_prix_nb

        if (self._settings['papier'] == PAPIER_A3):
            pu_encre = base_prix_encre * 2
        else:
            pu_encre = base_prix_encre
        facture.append((
                       LABELS[self._settings['couleurs']] + ' sur papier ' + LABELS[self._settings['papier']],
                       pu_encre, self._settings['copies']))
        prix+= self._settings['copies'] * pu_encre

        #calcul du prix des feuilles
        if (self._settings['recto_verso'] == IMPRESSION_RECTO_VERSO) or (self._settings['recto_verso'] == IMPRESSION_RECTO_VERSO_SHORT):
            nbfeuilles = int(self._nb_pages/2.+0.5) * self._settings['copies']
        else:
            nbfeuilles = self._nb_pages * self._settings['copies']
        facture.append((
                       'feuilles ' + LABELS[self._settings['papier']],
                       PU_FEUILLE[self._settings['papier']],
                       nbfeuilles))
        prix+= PU_FEUILLE[self._settings['papier']] * nbfeuilles


        # calcul prix des Agraphes
        facture.append((
                       'agrafes',
                       PU_AGRAPHE,
                       NB_AGRAPHES[self._settings['agraphes']] * self._settings['copies']))
        prix+= NB_AGRAPHES[self._settings['agraphes']] * self._settings['copies'] * PU_AGRAPHE

        # arrondit
        prix = int((prix*100) + 0.5)/100.
        self._prix = prix
        self._details_devis = facture
        return prix


    def _get_adh(self, adh):
        if type(adh) == str:
            import sys
            sys.path.append("/usr/scripts/gestion/")
            #from ldap_crans_test import crans_ldap
            from 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._adh != None):
            log.info('Impression [%s] : %s' % (self._adh, self._fichier))
        else:
            log.info("Impression : %s" % self._fichier)
        # Envoi du fichier a CUPS
        options =''
        # Creation  de la liste d'options
        # pour le nombre de copies et specifie non assemblee
        #options += '-# %d -o Collate=True' % self.nb_copies

        # Pour specifier l'imprimante
        options += ' -P laserjet'

        #Indique la presence d'un bac de sortie avec agrafeuse
        options += " -o Option20=MBMStaplerStacker -o OutputBin=StackerStapled"

        # Pour specifier la version du language postscript utilise par pdftops
        options += ' -o pdf-level3'

        # Pour donner le titre de l'impression
        options += " -T '%s'" % self._fichier

        # Pour donner le login de l'adherent
        if (self._adh != None):
            options += ' -U %s' % self._adh

        # 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'


        # option agrafes
        options += LPR_OPTIONS[self._settings['agraphes']]

        # option papier
        options += LPR_OPTIONS[self._settings['papier']]

        # option disposition
        options += LPR_OPTIONS[self._settings['recto_verso']]

        # options couleurs
        options += LPR_OPTIONS[self._settings['couleurs']]

        # Début : Ajout log pour réestimer les coûts
        # Commandes snmp recuperation des etats
        val_car_b_pc=int(comm.get(SNMP_CAR_B_PC))
        val_car_c_pc=int(comm.get(SNMP_CAR_C_PC))
        val_car_m_pc=int(comm.get(SNMP_CAR_M_PC))
        val_car_y_pc=int(comm.get(SNMP_CAR_Y_PC))
        val_car_b_pr=int(comm.get(SNMP_CAR_B_PR))
        val_car_c_pr=int(comm.get(SNMP_CAR_C_PR))
        val_car_m_pr=int(comm.get(SNMP_CAR_M_PR))
        val_car_y_pr=int(comm.get(SNMP_CAR_Y_PR))
        val_tam_b_pc=int(comm.get(SNMP_TAM_B_PC))
        val_tam_c_pc=int(comm.get(SNMP_TAM_C_PC))
        val_tam_m_pc=int(comm.get(SNMP_TAM_M_PC))
        val_tam_y_pc=int(comm.get(SNMP_TAM_Y_PC))
        val_tam_b_pr=int(comm.get(SNMP_TAM_B_PR))
        val_tam_c_pr=int(comm.get(SNMP_TAM_C_PR))
        val_tam_m_pr=int(comm.get(SNMP_TAM_M_PR))
        val_tam_y_pr=int(comm.get(SNMP_TAM_Y_PR))

        fichier_log_imp=open(FICHIER_LOG,"a")
        fichier_log_imp.write("%d %3d %2s : %3d %3d %3d %3d : %5d %5d %5d %5d : %3d %3d %3d %3d : %5d %5d %5d %5d : %s\n" %
                (time.time(),self._settings['copies'],self._settings['papier'],
                 val_car_b_pc,
                 val_car_c_pc,
                 val_car_m_pc,
                 val_car_y_pc,
                 val_car_b_pr,
                 val_car_c_pr,
                 val_car_m_pr,
                 val_car_y_pr,
                 val_tam_b_pc,
                 val_tam_c_pc,
                 Val_tam_m_pc,
                 val_tam_y_pc,
                 val_tam_b_pr,
                 val_tam_c_pr,
                 val_tam_m_pr,
                 val_tam_y_pr,
                 self._fichier) )
        fichier_log_imp.close()
        # Fin

        liste_nom_fichier_pdf=(' '+escapeForShell( self._fichier ) )*self._settings['copies']
        import commands
        #(status,rep) = commands.getstatusoutput("lpr %s %s" % (options, self.nom_fichier_pdf))
        (status,rep) = commands.getstatusoutput("lpr %s %s" % (options, liste_nom_fichier_pdf))
        if status != 0:
            log.error("erreur impression")
            log.error("lpr status:%d |  rep: %s" % (status, rep))
            raise PrintError, "lpr %s %s \n   status:%d rep: %s" % (options, liste_nom_fichier_pdf, status, rep)


