#!/usr/bin/env python
# -*- coding: utf-8 -*-

# OP Cloud Connector module for Tea4CUPS : Tee for CUPS
#
# (c) 2019 Fitosoft, Inc. <info@fitosoft.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.
#

import os
import sys
import getopt
import socket
import ssl
import urllib, urllib2, base64
import time
import traceback
import ConfigParser
import re
import cups
import shutil
import json
import uuid
import socket
from subprocess import Popen, PIPE
from io import BytesIO
from PIL import Image

reload(sys)
sys.setdefaultencoding('utf-8')

# global variables
op_version = "4.4.1902"
res_exit = 255
JobId = ''
PrinterName = ''
MachineName = ''
UserName = ''
Document = ''
TotalPages = '1'
Copies = '1'
Size = '1'
Status = ''
Paper = ''
Time = ''
Color = '1'
Duplex = '1'
NotifyName = ''
Datatype = ''
PrintProcessor = 'cups'
Parameters = ''
DriverName = ''
pStatus = ''
Priority = '0'
Position = '0'
PagesPrinted = '1'
spoolerfile = ''
cupsdconf = '/etc/cups/op-cloud-connector.conf'
cupstrace = '/etc/cups/op_trace.txt'
pexception = '1'
trace = '0'
cnv_is = '1'
cnv_type = '2'
cnv_image = '0'
cnv_maxcount = '10'
cnv_quality = '80'
cnv_longpixels = '80'
fformat = 'PNG'
image_type = 'image/png'

# convert CUPS status code to Win32 print spooler code
def printer_status(printer_state, printer_state_reasons):
    PRINTER_STATUS_PAUSED =           0x00000001
    PRINTER_STATUS_ERROR =            0x00000002
    PRINTER_STATUS_PENDING_DELETION = 0x00000004
    PRINTER_STATUS_PAPER_JAM =        0x00000008
    PRINTER_STATUS_PAPER_OUT =        0x00000010
    PRINTER_STATUS_MANUAL_FEED =      0x00000020
    PRINTER_STATUS_PAPER_PROBLEM =    0x00000040
    PRINTER_STATUS_OFFLINE =          0x00000080
    PRINTER_STATUS_IO_ACTIVE =        0x00000100
    PRINTER_STATUS_BUSY =             0x00000200
    PRINTER_STATUS_PRINTING =         0x00000400
    PRINTER_STATUS_OUTPUT_BIN_FULL =  0x00000800
    PRINTER_STATUS_NOT_AVAILABLE =    0x00001000
    PRINTER_STATUS_WAITING =          0x00002000
    PRINTER_STATUS_PROCESSING =       0x00004000
    PRINTER_STATUS_INITIALIZING =     0x00008000
    PRINTER_STATUS_WARMING_UP =       0x00010000
    PRINTER_STATUS_TONER_LOW =        0x00020000
    PRINTER_STATUS_NO_TONER =         0x00040000
    PRINTER_STATUS_PAGE_PUNT =        0x00080000
    PRINTER_STATUS_USER_INTERVENTION= 0x00100000
    PRINTER_STATUS_OUT_OF_MEMORY =    0x00200000
    PRINTER_STATUS_DOOR_OPEN =        0x00400000
    PRINTER_STATUS_SERVER_UNKNOWN =   0x00800000
    PRINTER_STATUS_POWER_SAVE =       0x01000000
    PRINTER_STATUS_SERVER_OFFLINE =   0x02000000
    PRINTER_STATUS_DRIVER_UPDATE_NEEDED =   0x04000000

    res = 0x00000000
    if printer_state == 3: # idle state
        res |= PRINTER_STATUS_WAITING
    if printer_state == 4: # printing state
        res |= PRINTER_STATUS_PRINTING
    if printer_state == 5: # destination is stopped 
        res |= PRINTER_STATUS_OFFLINE

    for state in printer_state_reasons:
        if state.strip() == 'door-open':
            res |= PRINTER_STATUS_DOOR_OPEN
        if state.strip() == 'toner-low':
            res |= PRINTER_STATUS_TONER_LOW
        if state.strip() == 'toner-empty':
            res |= PRINTER_STATUS_NO_TONER
        if state.strip() == 'paused':
            res |= PRINTER_STATUS_PAUSED
        if state.strip() == 'media-jam':
            res |= PRINTER_STATUS_PAPER_JAM
        if state.strip() == 'media-empty':
            res |= PRINTER_STATUS_PAPER_OUT
        if state.strip() == 'output-area-full':
            res |= PRINTER_STATUS_OUTPUT_BIN_FULL
        if state.strip() == 'output-area-full':
            res |= PRINTER_STATUS_OUTPUT_BIN_FULL
        if state.strip() == 'marker-supply-empty':
            res |= PRINTER_STATUS_PAPER_OUT
        if state.strip() == 'marker-supply-low':
            res |= PRINTER_STATUS_PAPER_OUT
        if state.strip() == 'shutdown':
            res |= PRINTER_STATUS_OFFLINE
        if state.strip() == 'stopping':
            res |= PRINTER_STATUS_BUSY

    return res

# write debug string
def write_debug(st):
    if trace == '1':
        statinfo = os.stat(cupstrace)
        if statinfo.st_size > 100000:
            f = open(cupstrace,'w')
        else:
            f = open(cupstrace,'a')
        f.write('\n' + st)
        f.close()

# convert spooler file to images
def getimages() :
    res = []
    path = "/tmp/" + os.path.basename(spoolerfile)
    os.mkdir( path );
    
    if cnv_image == '0':
        fformat = 'JPEG'
        image_type = 'image/jpeg'
    if cnv_image == '1':
        fformat = 'GIF'
        image_type = 'image/gif'
    if cnv_image == '2':
        fformat = 'PNG'
        image_type = 'image/png'
    if cnv_image == '3':
        fformat = 'TIFF'
        image_type = 'image/tiff'

    params = []
    params.append('-dNOPAUSE')
    params.append('-dBATCH')
    params.append('-dSAFER')
    params.append('-r100x100')
    params.append('-sDEVICE=png16m')
    params.append('-sOutputFile=' + path + '/%d.png')
    params.append('-dFirstPage=1')
    params.append('-dLastPage=' + cnv_maxcount)
    params.append('-sstdout=' + path + '/error.log');
    params.append(spoolerfile)

    params_gs = list(params)
    params_gs.insert(0, '/usr/local/bin/gs')
    write_debug(str(params_gs)) 
    process = Popen(params_gs, stdout=PIPE, stderr=PIPE)
    (output, err) = process.communicate()
    exit_code = process.wait()
    if exit_code != 0:
        params_pcl = list(params)
        params_pcl.insert(0, '/usr/local/bin/gpcl6') 
        write_debug(str(params_pcl))
        process = Popen(params_pcl, stdout=PIPE, stderr=PIPE)
        (output, err) = process.communicate()
        exit_code = process.wait()
        if exit_code != 0:
            params_xps = list(params)
            params_xps.insert(0, '/usr/local/bin/gxps')
            write_debug(str(params_xps)) 
            process = Popen(params_xps, stdout=PIPE, stderr=PIPE)
            (output, err) = process.communicate()
            exit_code = process.wait()
            if exit_code != 0:
                shutil.rmtree(path)
                return([])
    i = 1
    while 1==1:
        path_file = path + '/' + str(i) + '.png'
        if not os.path.isfile(path_file):
            break
        try:
            img = Image.open(path_file)
            lWidth, lHeight = img.size
            lNewHeight = 0
            lNewWidth = 0
	    if lHeight > lWidth:
                lNewHeight = int(cnv_longpixels)
                lNewWidth = int(float(lWidth)/float(lHeight)*float(lNewHeight))
	    else:
                lNewWidth = int(cnv_longpixels)
	        lNewHeight = int(float(lHeight)/float(lWidth)*float(lNewWidth))
            img_resized = img.resize((lNewWidth, lNewHeight))
            buff = BytesIO()
            img_resized.save(buff, format=fformat)
            img_str = base64.b64encode(buff.getvalue())
            img_array = [i, lNewWidth, lNewHeight, image_type, img_str]
            res.append(img_array)
        except IOError:
            i=i
        i += 1
    shutil.rmtree(path)
    return(res)

def server_info():
    res = "<info><version>"
    res += op_version
    res += "</version><host>"
    res += socket.gethostname()
    res += "</host></info>"
    return res
 
# ------------------- script starting -------------------------

try:

    # open a configuration file
    config = ConfigParser.ConfigParser()
    config.read(cupsdconf)
    host = 'checkprint.herokuapp.com'
    #host = '192.168.0.21'
    port = '443'
    #port = '5000'
    name_uid = config.get('global', 'name_uid')
    version = config.get('global', 'version')
    trace = config.get('global', 'trace')
    pexception = config.get('global', 'exception')
    cnv_is = config.get('global', 'cnv_is')
    cnv_type = config.get('global', 'cnv_type')
    cnv_image = config.get('global', 'cnv_image')
    cnv_maxcount = config.get('global', 'cnv_maxcount')
    cnv_quality = config.get('global', 'cnv_quality')
    cnv_longpixels = config.get('global', 'cnv_longpixels')

    # parse command line options
    opts, args = getopt.getopt(sys.argv[1:], '', ['JobId=', 'PrinterName=', 'MachineName=', 'UserName=', 'Document=', 'Copies=', 'Size=', 'SpoolerFile='])

    # process options
    write_debug('\n-------------------------------\n' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '\n') 
    write_debug(str(opts))  
    write_debug(str(args))     

    for o, a in opts:
        if o == '--JobId':
            JobId = a
        elif o == '--PrinterName':
            PrinterName = a
        elif o == '--MachineName':
            if a == 'localhost' or a == '127.0.0.1' or len(a)==0:
                MachineName = socket.gethostname()
            else:
                MachineName = a
        elif o == '--UserName':
            UserName = a
        elif o == '--Document':
            Document = a #unicode(a.decode('string_escape'), "utf-8")
        elif o == '--Copies':
            Copies = a
        elif o == '--Size':
            Size = a
        elif o == '--SpoolerFile':
            spoolerfile = a

    for p in args:
        ar = p.split('=')
        if len(ar)==2 :
            if ar[0] == 'PageSize':
                if ar[1] == 'Letter':
                    Paper = '1'
                elif ar[1] == 'Legal':
                    Paper = '5 '
                elif ar[1] == 'A4':
                    Paper = '9'
                elif ar[1] == 'A5':
                    Paper = '11'
                elif ar[1] == 'Tabloid':
                    Paper = '3'
                elif ar[1] == 'Monarch Envelope':
                    Paper = '37'
                elif ar[1] == 'DL Envelope' or a == 'DL':
                    Paper = '27'
                elif ar[1] == 'C5 Envelope':
                    Paper = '28'
                elif ar[1] == 'COM-10 Envelope' or a == 'COM10':
                    Paper = '20'
                elif ar[1] == 'B5 Envelope':
                    Paper = '34'
                elif ar[1] == 'Executive':
                    Paper = '7'
                elif ar[1] == 'A3':
                    Paper = '8'
                else:
                    Paper = '0'
            elif ar[0] == 'Duplex':
                if ar[1] == 'None':
                    Duplex = '1'
                else:
                    Duplex = '2'

    StartTime = UntilTime = Submitted = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime())

    # get pages count
    process = Popen(["/usr/local/bin/pkpgcounter", spoolerfile], stdout=PIPE, stderr=PIPE)
    (output, err) = process.communicate()
    exit_code = process.wait()
    # write_debug("/usr/local/bin/pkpgcounter" + "  " + spoolerfile + "\nresult: " + output)
    if exit_code == 0:
        TotalPages = str(output.strip())
        write_debug("---- TotalPages = " + str(TotalPages))
    
    # compute the percent of ink coverage
    if cnv_is == '1' and cnv_type == '2':
        process = Popen(["/usr/local/bin/pkpgcounter", "--colorspace", "bw", spoolerfile], stdout=PIPE, stderr=PIPE)
        (output, err) = process.communicate()
        exit_code = process.wait()
        # write_debug("/usr/local/bin/pkpgcounter --colorspace bw" + "  " + spoolerfile + "\nresult: " + output)
        if exit_code == 0:
            ar_res = re.sub(' +', ' ',output).replace("\n", " ").split(" ")
            imax = len(ar_res)
            i = 0
            bw_color = []
            empty_pages = []
            while i < imax-2: 
                if ar_res[i] == 'B':
                    bw = float(ar_res[i+2].replace('%', ''))            
                    bw_color.append(bw)
                    if bw == 0.0:
                        empty_pages.append(len(bw_color))
                i += 1
        process = Popen(["/usr/local/bin/pkpgcounter", "--colorspace", "cmyk", spoolerfile], stdout=PIPE, stderr=PIPE)
        (output, err) = process.communicate()
        exit_code = process.wait()
        # write_debug("/usr/local/bin/pkpgcounter --colorspace cmyk" + "  " + spoolerfile + "\nresult: " + output)
        if exit_code == 0:
            ar_res = re.sub(' +', ' ',output).replace("\n", " ").split(" ")
            imax = len(ar_res)
            i = 0
            c_color = []
            m_color = []
            y_color = []
            k_color = []
            while i < imax-2: 
                if ar_res[i] == 'C':
                    c_color.append(float(ar_res[i+2].replace('%', '')))
                if ar_res[i] == 'M':
                    m_color.append(float(ar_res[i+2].replace('%', '')))
                if ar_res[i] == 'Y':
                    y_color.append(float(ar_res[i+2].replace('%', '')))
                if ar_res[i] == 'K':
                    k_color.append(float(ar_res[i+2].replace('%', '')))
                i += 1

    Images = "<Images>"
    if cnv_is == '1' and cnv_type == '2':
        if len(bw_color) > 0 and len(c_color) > 0 and len(m_color) > 0 and len(y_color) > 0 and len(k_color) > 0:
	    Images = "<Images black='%.3f' cyan='%.3f' magenta='%.3f' yellow='%.3f' gray='%.3f' emptyPages='%s'>" %   ( (sum(bw_color)/len(bw_color)), (sum(c_color)/len(c_color)), (sum(m_color)/len(m_color)), (sum(y_color)/len(y_color)), (sum(k_color)/len(k_color)), ','.join([str(x) for x in empty_pages]) )
    if cnv_is == '1' and ( cnv_type == '1' or cnv_type == '2'):
        images = getimages()
        for image in images:
            Images += "<Image>data:"
            Images += image_type
            Images += ";base64,"
            Images += str(image[4])
            Images += "</Image>"
    Images += "</Images>"

    # get printers status
    Statuses = []
    conn = cups.Connection()
    printers = conn.getPrinters()
    for printer in printers:
        status =  printer_status(printers[printer]["printer-state"], printers[printer]["printer-state-reasons"])
        Status = [[printer, str(status), "0"]]
        Statuses += Status

    # Return the JSON as a string.
    json_res = {
        "name_uid": name_uid,
        "server_info": server_info(),
        "submitted": Submitted,
        "printerName": PrinterName,
        "printerNameGroup": "",
	"userName": UserName,
	"userNameGroup": "",
	"computerName": MachineName,
	"computerNameGroup": "",
	"pagesPrinted": str(int(TotalPages)*int(Copies)),
        "totalPages": str(int(TotalPages)*int(Copies)),
        "copies": Copies,
        "isColor": Color,
        "isDuplex": Duplex,
        "isPaperSize": "1",
        "paperSize": Paper,
        "isPaperLength": "0",
        "paperLength": "0",
        "isPaperWidth": "0",
        "paperWidth": "0",
        "document": Document,
        "datatype": Datatype,
        "jobSize": Size,
        "jobId": JobId,
        "info": "<Info></Info>",
        "images": Images,
        "printers_status": Statuses,
        "guid": str(uuid.uuid4())
    }
 
    st_query = json.dumps(json_res)

    write_debug(st_query)

    query = {'process' : st_query }

    #place POST data
    req = urllib2.Request('https://' + host + ':' + port + '/process');
    #req = urllib2.Request('http://' + host + ':' + port + '/process');
    req.add_header('Content-Type','application/json')
    req.add_header('User-Agent','Mozilla/4.0')
    req.add_data(st_query)

    # send query
    context = ssl._create_unverified_context()	
    
    r = urllib2.urlopen(req, context=context, timeout=15)
    #r = urllib2.urlopen(req, timeout=15)

    # read query
    data = r.read()
    json_res = json.loads(data)
    write_debug(data)

    res = 'cancel'
    if 'status' in json_res:
        if json_res['status']=="1":
            res_exit = 0


    # save connector settings
    config = ConfigParser.ConfigParser()
    config.read('/etc/cups/op-cloud-connector.conf')
    if "ImagesIsSave" in json_res:
        config.set('global', 'cnv_is', json_res["ImagesIsSave"])
    if "ImagesType" in json_res:
        config.set('global', 'cnv_type', json_res["ImagesType"])
    if "ImagesSaveType" in json_res:
        config.set('global', 'cnv_image', json_res["ImagesSaveType"])
    if "ImagesSavePagesMaxCount" in json_res:
        config.set('global', 'cnv_maxcount', json_res["ImagesSavePagesMaxCount"])
    if "ImagesSaveQuality" in json_res:
        config.set('global', 'cnv_quality', json_res["ImagesSaveQuality"])
    if "ImagesSaveLongPixelLength" in json_res:
        config.set('global', 'cnv_longpixels', json_res["ImagesSaveLongPixelLength"])
    with open('/etc/cups/op-cloud-connector.conf', 'wb') as configfile:
        config.write(configfile)

except:
    if trace == '1':
        traceback.print_exc(file=open(cupstrace,"a"))
    if pexception == "1":
        res_exit = 0;

sys.exit(res_exit)



