#! /usr/bin/env python
# -*- coding: ISO-8859-15 -*-

# A banner generator for PyKota
#
# PyKota - Print Quotas for CUPS and LPRng
#
# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
# 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# $Id$
#
#

import sys
import os
import time
import cStringIO
import popen2

try :
    from reportlab.pdfgen import canvas
    from reportlab.lib import pagesizes
    from reportlab.lib.units import cm
except ImportError :    
    hasRL = 0
else :    
    hasRL = 1
    
try :
    import PIL.Image 
except ImportError :    
    hasPIL = 0
else :    
    hasPIL = 1
    
from pykota.tool import Tool, PyKotaToolError, PyKotaCommandLineError, crashed, N_

__doc__ = N_("""pkbanner v%(__version__)s (c) %(__years__)s %(__author__)s

Generates banners.

command line usage :

  pkbanner  [options]  [more info]

options :

  -v | --version       Prints pkbanner's version number then exits.
  -h | --help          Prints this message then exits.
  
  -l | --logo img      Use the image as the banner's logo. The logo will
                       be drawn at the center top of the page. The default
                       logo is /usr/share/pykota/logos/pykota.jpeg
                       
  -p | --pagesize sz   Sets sz as the page size. Most well known
                       page sizes are recognized, like 'A4' or 'Letter'
                       to name a few. The default size is A4.
  
  -s | --savetoner s   Sets the text luminosity factor to s%%. This can be 
                       used to save toner. The default value is 0, which
                       means that no toner saving will be done.
  
  -u | --url u         Uses u as an url to be written at the bottom of 
                       the banner page. The default url is :
                       http://www.librelogiciel.com/software/
  
examples :                              

  Using pkbanner directly from the command line is not recommended,
  excepted for testing purposes. You should use pkbanner in the
  'startingbanner' or 'endingbanner' directives in pykota.conf
  
    startingbanner: /usr/bin/pkbanner --logo="" --savetoner=75
  
      With such a setting in pykota.conf, all print jobs will be 
      prefixed with an A4 banner with no logo, and text luminosity will
      be increased by 75%%. The PostScript output will be directly sent
      to your printer.
      
  You'll find more examples in the sample configuration file included    
  in PyKota.
""")
        
class PyKotaBanner(Tool) :        
    """A class for pkbanner."""
    def getPageSize(self, pgsize) :
        """Returns the correct page size or None if not found."""
        try :
            return getattr(pagesizes, pgsize.upper())
        except AttributeError :    
            try :
                return getattr(pagesizes, pgsize.lower())
            except AttributeError :
                pass
                
    def getVar(self, varname) :            
        """Extracts a variable from the environment and returns its value or 'Unknown' in the current locale."""
        return os.environ.get(varname) or _("Unknown")
        
    def printVar(self, canvas, x, y, label, value, size, savetoner) :
        """Outputs a variable onto the PDF canvas.
        
           Returns the number of points to substract to current Y coordinate.
        """   
        canvas.saveState()
        canvas.setFont("Helvetica-Bold", size)
        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 0) ] # Black * savetoner
        canvas.setFillColorRGB(r, g, b)
        message = "%s :" % _(label)
        canvas.drawRightString(x, y, message)
        canvas.setFont("Courier-Bold", size)
        (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (1, 0, 0) ] # Red * savetoner
        canvas.setFillColorRGB(r, g, b)
        canvas.drawString(x + 0.5*cm, y, value)
        canvas.restoreState()
        return (size + 4)
    
    def genPDF(self, pagesize, logo, url, text, savetoner) :
        """Generates the banner in PDF format, return the PDF document as a string."""
        document = cStringIO.StringIO()
        c = canvas.Canvas(document, pagesize=pagesize, pageCompression=1)
        
        c.setAuthor("Jerome Alet")
        c.setTitle("PyKota generated Banner")
        c.setSubject("This is a print banner generated with PyKota")
        
        xcenter = pagesize[0] / 2.0
        ycenter = pagesize[1] / 2.0
                    
        ypos = pagesize[1] - (2 * cm)            
        
        if logo :
            try :    
                imglogo = PIL.Image.open(logo)
            except :    
                self.printInfo("Unable to open image %s" % logo, "warn")
            else :
                (width, height) = imglogo.size
                multi = float(width) / (8 * cm) 
                width = float(width) / multi
                height = float(height) / multi
                xpos = xcenter - (width / 2.0)
                ypos -= height
                c.drawImage(logo, xpos, ypos, width, height)
        
        # New top
        xpos = pagesize[0] / 5.0
        ypos -= (1 * cm) + 20
        
        printername = self.getVar("PYKOTAPRINTERNAME")
        username = self.getVar("PYKOTAUSERNAME")
        accountbanner = self.config.getAccountBanner(printername)
        
        # Outputs the username
        ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner) 
        
        # Text    
        if text :
            ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner) 
        
        # Printer and Job Id
        job = "%s - %s" % (printername, self.getVar("PYKOTAJOBID"))
        ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner) 
        
        # Current date (TODO : at the time the banner was printed ! Change this to job's submission date)
        datetime = time.strftime("%c", time.localtime())
        ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner) 
        
        # Result of the print job
        action = self.getVar("PYKOTAACTION")
        if action == "ALLOW" :
            action = _("Allowed")
        elif action == "DENY" :    
            action = _("Denied")
        elif action == "WARN" :    
            action = _("Allowed with Warning")
        elif action == "PROBLEM" :    
            # should never occur
            action = _("Problem")
        elif action == "CANCEL" :    
            # should never occur
            action = _("Cancelled")
        ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 
        
        # skip some space
        ypos -= 20
        
        # Outputs title and filename
        # We put them at x=0.25*pagewidth so that the line is long enough to hold them
        title = self.getVar("PYKOTATITLE")
        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner) 
        
        filename = self.getVar("PYKOTAFILENAME")
        ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner) 
        
        # skip some space
        ypos -= 20
        
        # Now outputs the user's account balance or page counter
        ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner) 
        limitby = self.getVar("PYKOTALIMITBY")
        if limitby == "balance" :  
            ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner) 
        elif limitby == "quota" :
            ypos -= self.printVar(c, xcenter, ypos, _("Soft Limit"), self.getVar("PYKOTASOFTLIMIT"), 14, savetoner) 
            ypos -= self.printVar(c, xcenter, ypos, _("Hard Limit"), self.getVar("PYKOTAHARDLIMIT"), 14, savetoner) 
            ypos -= self.printVar(c, xcenter, ypos, _("Date Limit"), self.getVar("PYKOTADATELIMIT"), 14, savetoner) 
        else :
            if limitby == "noquota" :
                msg = _("No Limit")
            elif limitby == "nochange" :    
                msg = _("No Accounting")
            elif limitby == "noprint" :    
                msg = _("Forbidden")
            else :    
                msg = _("Unknown")
            ypos -= self.printVar(c, xcenter, ypos, _("Printing Mode"), msg, 14, savetoner)
            
        # URL
        if url :
            c.saveState()
            c.setFont("Courier-Bold", 16)
            (r, g, b) =  [ color + (savetoner * (1.0 - color)) for color in (0, 0, 1) ] # Blue * savetoner
            c.setFillColorRGB(r, g, b)
            c.drawCentredString(xcenter, 2 * cm, url)
            c.restoreState()
        
        c.showPage()
        c.save()
        return document.getvalue()
        
    def main(self, arguments, options) :
        """Generates a banner."""
        if not hasRL :
            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
        if not hasPIL :
            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
            
        try :
            savetoner = int(options["savetoner"])
            if (savetoner < 0) or (savetoner > 99) :
                raise ValueError, _("Allowed range is (0..99)")
            savetoner /= 100.0    
        except (TypeError, ValueError), msg :
            self.printInfo(_("Invalid 'savetoner' option %s : %s") % (options["savetoner"], msg), "warn")
            savetoner = 0.0
            
        pagesize = self.getPageSize(options["pagesize"])
        if pagesize is None :
            pagesize = self.getPageSize("a4")
            self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn")
            
        self.logdebug("Generating the banner in PDF format...")    
        doc = self.genPDF(pagesize, 
                          options["logo"].strip(), 
                          options["url"].strip(), 
                          " ".join(arguments).strip(), 
                          savetoner)
        
        self.logdebug("Converting the banner to PostScript...")    
        os.environ["PATH"] = "%s:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin" % os.environ.get("PATH", "")
        child = popen2.Popen3("gs -q -dNOPAUSE -dBATCH -dPARANOIDSAFER -sDEVICE=pswrite -sOutputFile=- -")
        try :
            child.tochild.write(doc)
            child.tochild.close()
            sys.stdout.write(child.fromchild.read())
            sys.stdout.flush()
            child.fromchild.close()
        except IOError, msg :    
            self.printInfo("I/O Error %s" % msg, "error")
        status = child.wait()
        if os.WIFEXITED(status) :
            status = os.WEXITSTATUS(status)
        self.logdebug("PDF to PostScript converter exit code is %s" % str(status))
        self.logdebug("Banner completed.")
        return status

if __name__ == "__main__" :
    # TODO : --papertray : to print banners on a different paper (colored for example)
    retcode = 0
    try :
        defaults = { \
                     "savetoner" : "0", \
                     "pagesize" : "a4", \
                     "logo" : "/usr/share/pykota/logos/pykota.jpeg",
                     "url" : "http://www.librelogiciel.com/software/",
                   }
        short_options = "vhs:l:p:u:"
        long_options = ["help", "version", "savetoner=", "pagesize=", "logo=", "url="]
        
        # Initializes the command line tool
        banner = PyKotaBanner(doc=__doc__)
        banner.deferredInit()
        
        # parse and checks the command line
        (options, args) = banner.parseCommandline(sys.argv[1:], short_options, long_options, allownothing=1)
        
        # sets long options
        options["help"] = options["h"] or options["help"]
        options["version"] = options["v"] or options["version"]
        options["savetoner"] = options["s"] or options["savetoner"] or defaults["savetoner"]
        options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"]
        options["url"] = options["u"] or options["url"] or defaults["url"]
        
        options["logo"] = options["l"] or options["logo"]
        if options["logo"] is None : # Allows --logo="" to disable the logo entirely
            options["logo"] = defaults["logo"]  
        
        if options["help"] :
            banner.display_usage_and_quit()
        elif options["version"] :
            banner.display_version_and_quit()
        else :
            retcode = banner.main(args, options)
    except KeyboardInterrupt :        
        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
        retcode = -3
    except PyKotaCommandLineError, msg :    
        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
        retcode = -2
    except SystemExit :        
        pass
    except :
        try :
            banner.crashed("pkbanner failed")
        except :    
            crashed("pkbanner failed")
        retcode = -1
        
    sys.exit(retcode)