#! /usr/bin/python3
'''
Created on Mar 1, 2016

@author: Matthew Yoshio Morikawa
'''
import sys
from io import BytesIO
import tempfile
import time
import datetime
from PyPDF3 import PdfFileWriter, PdfFileReader
import re
import os
import ntpath
import reportlab
from reportlab.pdfgen import canvas
import reportlab.lib.pagesizes as pagesizes
from reportlab.pdfbase import pdfmetrics, ttfonts 
from reportlab.lib.colors import Color
from sys import stderr, argv
from math import sqrt
from math import ceil
from math import floor

import pkg_resources

#Constants
###############################################################################

KYOVERSION      = 1.0

ARG_DESTINATION = 0
ARG_JOB         = 1
ARG_USER        = 2
ARG_TITLE       = 3
ARG_NUM_COPIES  = 4
ARG_OPTIONS     = 5
ARG_PRINTABLE   = 6

PAGE_SIZE       = "PageSize"
MA_ENABLED      = "Madj"
WM_RENDERING    = "Wren"
WM_TEXT         = "Wtxt"
WM_FONT         = "Wfnt"
WM_COLOR        = "Wclr"
WM_TYPE         = "Wtyp"
WM_ANGLE        = "Wang"
WM_SIZE         = "Wsze"
WM_TRANSPARENCY = "Wtrans"
WM_REPEAT       = "Wflg"

CONFIG_DIR = "/usr/share/kyocera/"
TMP        = "/tmp/"
USER       = ""

#Debug functions
###############################################################################

DEBUG = False

def debug_write(input_string):
    if DEBUG:
        try:
            ts = time.time()
            st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
            debug_file= open("/tmp/print_debug", 'a+')
            debug_file.write(st + ": " +input_string + "\n")
            debug_file.flush()
            debug_file.close()
        except:
            pass

#Get watermark options
###############################################################################

def get_watermark_options(all_options):
    options = dict()
    debug_write(all_options)
    
    defaults = get_ppd_defaults()
    debug_write("Defaults: " + str(defaults))
    get_option(options, PAGE_SIZE, all_options, defaults[PAGE_SIZE])
    get_checkbox_option(options, MA_ENABLED, all_options, "False")
    get_checkbox_option(options, WM_RENDERING, all_options, defaults[WM_RENDERING])
    get_text_option(options, all_options)
    get_option(options, WM_FONT, all_options, defaults[WM_FONT])
    get_option(options, WM_COLOR, all_options, defaults[WM_COLOR])
    get_option(options, WM_TYPE, all_options, defaults[WM_TYPE])
    get_option(options, WM_ANGLE, all_options, defaults[WM_ANGLE])
    get_option(options, WM_SIZE, all_options, defaults[WM_SIZE])
    get_option(options, WM_TRANSPARENCY, all_options, defaults[WM_TRANSPARENCY])
    get_checkbox_option(options, WM_REPEAT, all_options, defaults[WM_REPEAT])
    
    return options

def get_ppd_defaults():
    ppd = os.environ.get('PPD')
    option_defaults = dict()
    with open(ppd, 'r') as file_in:
        for line in file_in:
            if  ("*Default" + PAGE_SIZE) in line:
                option_defaults[PAGE_SIZE] = get_line_default(line)
            elif("*Default" + WM_RENDERING) in line:
                option_defaults[WM_RENDERING] = get_line_default(line)
            elif("*Default" + WM_FONT) in line:
                option_defaults[WM_FONT] = get_line_default(line)
            elif("*Default" + WM_COLOR) in line:
                option_defaults[WM_COLOR] = get_line_default(line)
            elif("*Default" + WM_TYPE) in line:
                option_defaults[WM_TYPE] = get_line_default(line)
            elif("*Default" + WM_ANGLE) in line:
                option_defaults[WM_ANGLE] = get_line_default(line)
            elif("*Default" + WM_SIZE) in line:
                option_defaults[WM_SIZE] = get_line_default(line)
            elif("*Default" + WM_TRANSPARENCY) in line:
                option_defaults[WM_TRANSPARENCY] = get_line_default(line)
            elif("*Default" + WM_REPEAT) in line:
                option_defaults[WM_REPEAT] = get_line_default(line)
    return option_defaults

def get_line_default(line):
    return line.strip().split(": ")[1]

def get_checkbox_option(options, option, all_options, default):
    debug_write("cOption " + option)

    debug_write(all_options)
    debug_write(str(" " + option + " " in all_options))
    debug_write(str(all_options.startswith(option + " ")))
    debug_write(str(default == "False"))

    if (" " + option + " " in all_options or all_options.startswith(option + " ")) and default == "False":
        options[option] = "True"
    elif (" no" + option + " " in all_options or all_options.startswith("no" + option + " ")) and default == "True":
        options[option] = "False"
    else:
        get_option(options, option, all_options, default)
    
def get_option(options, option, all_options, default):
    debug_write("Option " + option)
    try:
        result = re.search( r"%s=(.*?)( |$)" % option, all_options).group(1)
        options[option] = result
        debug_write("    Result " + result)
    except AttributeError:
        options[option] = default
        debug_write("    Default " + default)

def get_text_option(options, all_options):
    debug_write("Option " + WM_TEXT)
    try:
        result = re.search( r"%s='(.*?)'" % WM_TEXT, all_options).group(1)
        options[WM_TEXT] = result.replace("\\", "")
        debug_write("    Result1 " + result)
    except AttributeError:
        result = get_text_from_config_file()
        debug_write("    Config Text: " + result)
        if result != "":
            options[WM_TEXT] = result.replace("\\", "")
            debug_write("    Result3 " + result)
        else:
            try:
                result = re.search( r"%s=(.*?)((?<!\\) |$)" % WM_TEXT, all_options).group(1)
                debug_write("    Result1 " + result)
                options[WM_TEXT] = get_text_equivalence(result)
                debug_write("    Result2 " + options[WM_TEXT])
            except AttributeError:
                options[WM_TEXT] = "CONFIDENTIAL"
                debug_write("    Default " + "CONFIDENTIAL")

def get_text_from_config_file():
    debug_write("    Checking config file")
    ppd  = ntpath.basename(os.environ.get('PPD')).replace(".ppd",".cfg")
    debug_write("        Got USER: " + USER)
    debug_write("        Got PPD: " + ppd)
    configFile = CONFIG_DIR + USER + "/" + ppd
    debug_write("        Config file: " + configFile)
    if os.path.isfile(configFile): 
        with open(configFile, 'r') as file_in:
            for line in file_in:
                if "Wtxt" in line:
                    text = line.partition("=")[2].strip()
                    if (";" in text):
                        text = text.rpartition(";")[0].strip()
                    debug_write("        Wtxt: " + text)
                    if text and is_custom(text):
                        return text[1:-1]
                    else:
                        return ""
    return ""

def is_custom(option):
    if option == "Confidential":
        return False
    elif option == "Original":
        return False
    elif option == "Copy":
        return False
    elif option == "DoNotPhotoCopy":
        return False
    elif option == "Draft":
        return False
    elif option == "Final":
        return False
    elif option == "Proof":
        return False
    else:
        return True


def get_text_equivalence(option):
    debug_write("    Get text equivalence " + option)
    if option == "Confidential":
        return "CONFIDENTIAL"
    elif option == "Original":
        return "ORIGINAL"
    elif option == "Copy":
        return "COPY"
    elif option == "DoNotPhotoCopy":
        return "DO NOT PHOTOCOPY"
    elif option == "Draft":
        return "DRAFT"
    elif option == "Final":
        return "FINAL"
    elif option == "Proof":
        return "PROOF"
    elif "Custom." in option:
        debug_write("    Found custom option")
        return option.replace("Custom.", "").replace("\\", "")
    else:
        return option.replace("\\", "")

    
#Get base PDF
###############################################################################

def get_base_pdf():
    
    fileout = tempfile.NamedTemporaryFile(dir="/tmp")

    if len(argv) == 7:
        kyo_in = open(argv[ARG_PRINTABLE], 'rb')
    else:
        kyo_in = open(0, 'rb')

    fileout.write(kyo_in.read())
    fileout.seek(0)
    
    in_pdf = PdfFileReader(fileout)

    kyo_in.close()

    return in_pdf
    
#Get watermark PDF
###############################################################################

def get_watermark_pdf(watermark_arguments):
    packet = BytesIO()
    draw_watermark(packet, watermark_arguments)
    packet.seek(0)
    watermark_pdf = PdfFileReader(packet)
    return watermark_pdf

def get_base_watermark_position(page_size):
    width, height = page_size
    return (width/2, height/2)

def get_canvas(watermark_arguments, page_size, packet):
    #wm_font     = "Helvetica-Bold"
    #wm_size     = int(watermark_arguments[WM_SIZE])
    wm_angle    = int(watermark_arguments[WM_ANGLE])
    
    can = canvas.Canvas(packet, page_size)
    #can.setFont(wm_font, wm_size)
    set_font(can, watermark_arguments)
    set_can_color(can, watermark_arguments)
    set_can_style(can, watermark_arguments)
    
    x, y = get_base_watermark_position(page_size)
    can.translate(x, y)
    
    can.rotate(wm_angle)
    
    return can

def set_font(can, watermark_arguments):
    supported_fonts = ('Courier', 'Courier-Bold', 'Courier-Oblique', 
                       'Courier-BoldOblique', 'Helvetica', 'Helvetica-Bold', 
                       'Helvetica-Oblique', 'Helvetica-BoldOblique',
                       'Times-Roman', 'Times-Bold', 'Times-Italic', 
                       'Times-BoldItalic', 'Symbol','ZapfDingbats')
    
    font = watermark_arguments[WM_FONT]
    size = int(watermark_arguments[WM_SIZE])
    
    for support in supported_fonts:
        if font == support:
            can.setFont(font, size)
            return
        
    ttf = "LiberationMono-Bold.ttf"
    #debug_write(ttf + "\n")
    for root, dirs, files in os.walk("/usr/share/fonts/truetype"):
        if ttf in files:
            pdfmetrics.registerFont(ttfonts.TTFont(font, os.path.join(root, ttf)))
            can.setFont(font, size)
            return
            
    debug_write("Font not Found")
    can.setFont("Helvetica-Bold", size) 
            

def set_can_style(can, watermark_arguments):
    fill = watermark_arguments[WM_TYPE]
    
    if fill == "Hollow":
        can.setFillColor(Color(0,0,0, alpha=0.0))
        can.setLineWidth(1)
        t=can.beginText()
        t.setTextRenderMode(2)
        can._code.append(t.getCode())

def set_can_color(can, watermark_arguments):
    color = watermark_arguments[WM_COLOR]
    fill  = watermark_arguments[WM_TYPE]
    trans = 1.0
    if watermark_arguments[WM_TRANSPARENCY] == "watermark":
        trans = 0.5
    
    if color == "Red" and fill != "Hollow":
        can.setFillColor(Color(1,0,0, alpha=trans))
    elif color == "Green" and fill != "Hollow":
        can.setFillColor(Color(0,1,0, alpha=trans))
    elif color == "Blue" and fill != "Hollow":
        can.setFillColor(Color(0,0,1, alpha=trans))
    elif color == "Black" and fill != "Hollow":
        can.setFillColor(Color(0,0,0, alpha=trans))
    elif color == "Red" and fill == "Hollow":
        can.setStrokeColor(Color(1,0,0, alpha=1.0))
    elif color == "Green" and fill == "Hollow":
        can.setStrokeColor(Color(0,1,0, alpha=1.0))
    elif color == "Blue" and fill == "Hollow":
        can.setStrokeColor(Color(0,0,1, alpha=1.0))
    elif color == "Black" and fill == "Hollow":
        can.setStrokeColor(Color(0,0,0, alpha=1.0))

def get_page_size(watermark_arguments):
    size = watermark_arguments[PAGE_SIZE]
    if size == "A3":
        return pagesizes.A3
    elif size == "A4":
        return pagesizes.A4 
    elif size == "A5":
        return pagesizes.A5 
    elif size == "A6":
        return pagesizes.A6 
    elif size == "B4":
        return pagesizes.B4 
    elif size == "B5":
        return pagesizes.B5
    elif size == "ISOB5":
        return (499, 708)
    elif size == "Letter":
        return pagesizes.letter
    elif size == "Legal":
        return pagesizes.legal
    elif size == "Executive":
        return (522, 756)
    elif size == "Tabloid":
        return pagesizes.elevenSeventeen
    elif size == "EnvPersonal":
        return (261, 468)
    elif size == "Env9":
        return (279, 639)
    elif size == "Env10":
        return (297, 684)
    elif size == "EnvMonarch":
        return (279, 540)
    elif size == "EnvDL":
        return (312, 624)
    elif size == "EnvC5":
        return (459, 649)
    elif size == "OficioII":
        return (612, 936)
    elif size == "Folio":
        return (595, 935)
    elif size == "EnvC4":
        return (649, 919)
    elif size == "Statement":
        return (396, 612)
    elif size == "P8K":
        return (774, 1116)
    elif size == "P16K":
        return (558, 774)
    elif size == "OficioMX":
        return (612, 964)
    elif size == "P12x18":
        return (864, 1296)
    else:
        return pagesizes.letter

def draw_watermark(packet, watermark_arguments):
    wm_repeat = (watermark_arguments[WM_REPEAT] == "True")
    
    page_size=get_page_size(watermark_arguments)
    can = get_canvas(watermark_arguments, page_size,packet)
    
    if wm_repeat:
        draw_repeat_watermark(can, watermark_arguments, page_size)
    else:
        draw_single_watermark(can, watermark_arguments)
    can.save()

def draw_single_watermark(can, watermark_arguments):
    text = watermark_arguments[WM_TEXT]
    can.drawCentredString(0, 0, text)
    
def draw_repeat_watermark(can, watermark_arguments, page_size):
    wm_text     = watermark_arguments[WM_TEXT]
    wm_font     = "Helvetica-Bold"
    wm_size     = int(watermark_arguments[WM_SIZE])
    wm_length   = can.stringWidth(wm_text, wm_font, wm_size)
    whitespace  = wm_size * 2
    
    x, y = page_size
    
    #We fill the diagonal by diagonal space, as that will
    #guarantee coverage over the page
    diagonal = sqrt(x*x + y*y) #Pythagorean diagonal
    
    vertical_space = whitespace * 1.5
    horizontal_space = (wm_length + whitespace/2)
    
    collumns = diagonal / horizontal_space
    rows = diagonal / vertical_space
    
    min_x = int(floor(-collumns/2))
    max_x = int(ceil(collumns/2))
    
    min_y = int(floor(-rows/2))
    max_y = int(ceil(rows/2))
    
    for i in range(min_x, max_x + 1, 1):
        for j in range(min_y, max_y + 1, 1):
            draw_x = i * horizontal_space
            draw_y = j * vertical_space
            can.drawCentredString(draw_x, draw_y, wm_text)

#Apply watermark to pdf
###############################################################################

def apply_watermark_to_pdf(base_pdf, watermark_pdf):
    
    new_pdf = PdfFileWriter()
    
    num_of_pages = base_pdf.getNumPages()
    watermark = watermark_pdf.getPage(0)
    for p in range(num_of_pages):
        page = base_pdf.getPage(p)
        page.mergePage(watermark)
        new_pdf.addPage(page)

    return new_pdf
    
#Output pdf
###############################################################################

def output_pdf(new_pdf):
    output = BytesIO()
    new_pdf.write(output)
    #print("Content-type: application/pdf\n")
    sys.stdout.buffer.write(output.getvalue())
    output.close()

#Check Version
###############################################################################

def check_version():
    try:
        current_version = reportlab.Version
        minimum_version = "2.4.0"
        debug_write("Version: " + current_version)
        return pkg_resources.parse_version(minimum_version) <= pkg_resources.parse_version(current_version)
    except: 
        return False

#Main and Execution
###############################################################################

def main():

    debug_write("Starting Driver")


    index = 0
    for arg in sys.argv:
        debug_write(str(index) + " " + str(arg))
        index = index + 1
    debug_write("\n")
    
    global USER
    USER = sys.argv[ARG_USER]
    debug_write("User: " + USER)
    watermark_arguments = get_watermark_options(sys.argv[ARG_OPTIONS])
    

    #DEBUG TAG
    for arg in watermark_arguments:
        debug_write(str(arg) + ": " + str(watermark_arguments[arg]))
    debug_write("\n")
    
    render = watermark_arguments[WM_RENDERING]
    
    #Monochrome Adjustment
    adjustment = watermark_arguments[MA_ENABLED]

    has_minimum_version = check_version()
    
    if render == "True" and has_minimum_version and adjustment == "False":
        debug_write("Getting base pdf")
        base_pdf = get_base_pdf()
        debug_write("Generating watermark")
        watermark_pdf = get_watermark_pdf(watermark_arguments)
        debug_write("Applying watermark")
        new_pdf = apply_watermark_to_pdf(base_pdf, watermark_pdf)
        debug_write("Outputting pdf")
        output_pdf(new_pdf)
    
        tmpdir = "/tmp/"
        debug_write("Temp Directory: " + tmpdir)

        prefilter_flag = tmpdir + str(argv[ARG_JOB]) + "_" + str(argv[ARG_TITLE])
        file = open(prefilter_flag, 'w+')
        file.close()
    else:
        debug_write("Watermark Filter run but not acting")
        if len(argv) == 7:
            kyo_in = open(argv[ARG_PRINTABLE], 'rb')
        else:
            kyo_in = open(0, 'rb')
        
        sys.stdout.buffer.write(kyo_in.read())
        kyo_in.close()

    debug_write("Stopping Driver")
    return 0
    
if __name__ == "__main__":
    main()
