#!/@unixroot/usr/bin/python3
# -*- coding: utf-8 -*-
#
# (c) Copyright 2011-2015 HP Development Company, L.P.
#
# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Author: Amarnath Chitumalla
#

# Std Lib
import sys
import os
import re

# Local
from .g import *
from . import utils, tui, password, os_utils, smart_install
from prnt import cups
from installer import core_install
from .sixext import to_string_utf8

try:
    from importlib import import_module
except ImportError as e:
    log.debug(e)
    from .utils import dyn_import_mod as import_module


# ppd type
HPCUPS = 1
HPIJS = 2
HPPS = 3
HPOTHER = 4

DEVICE_URI_PATTERN = re.compile(r"""(.*):/(.*?)/(\S*?)\?(?:serial=(\S*)|device=(\S*)|ip=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}[^&]*)|zc=(\S+))(?:&port=(\d))?""", re.I)
NICKNAME_PATTERN = re.compile(b'''\*NickName:\s*\"(.*)"''', re.MULTILINE)
NET_PATTERN = re.compile(r"""(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})""")
NET_ZC_PATTERN = re.compile(r'''zc=(.*)''',re.IGNORECASE)
NET_OTHER_PATTERN = re.compile(r'''(.*)://(.*)''',re.IGNORECASE)
USB_PATTERN = re.compile(r'''serial=(.*)''',re.IGNORECASE)
LPSTAT_PATTERN = re.compile(r"""(\S*): (.*)""", re.IGNORECASE)
#BACK_END_PATTERN = re.compile(r'''(.*):(.*)''',re.IGNORECASE)



##### Global variables ###
mapofDevices={}
Error_Found = False
####### Device class ########
class DetectedDevice:
    def __init__(self, Printer_Name,Device_URI,Device_Type, ppdType, PPDFileError = False, IsEnabled=True ):
        self.PrinterName =Printer_Name
        self.DeviceURI = Device_URI
        self.DeviceType = Device_Type
        self.PPDFileType = ppdType
        self.PPDFileError = PPDFileError
        self.IsEnabled = IsEnabled




##### METHODS #####

#Add Printer info to dictionary
def addToDeviceList(Key, printer_name, device_uri,back_end, ppd_fileType,PPDFileError, Is_Print_Q_Enabled):
    if ppd_fileType != None:
        device1 =DetectedDevice(printer_name, device_uri,back_end, ppd_fileType,PPDFileError, Is_Print_Q_Enabled)
        if Key in mapofDevices:
            mapofDevices[Key].append(device1)
        else:
            deviceList=[device1]
            mapofDevices[Key]=deviceList
    else:
        log.warn("%s is not HP Device." %(printer_name))

#Validate all the Queues
def parseQueues(mode):
    is_hpcups_installed = to_bool(sys_conf.get('configure', 'hpcups-install', '0'))
    is_hpijs_installed = to_bool(sys_conf.get('configure', 'hpijs-install', '0'))
    st, output = utils.run('lpstat -v')
    status = True

    cups_printers = []
    if output.find("No destinations added") != -1 or output.find("lpstat:") != -1:
        log.info("No Queue added")
    else:
        for p in output.splitlines():
            try:
                match = LPSTAT_PATTERN.search(p)
                printer_name = match.group(1)
                device_uri = match.group(2)
                cups_printers.append((printer_name, device_uri))
            except AttributeError:
                pass

    log.debug(cups_printers)
    log.debug("HPCups installation=%d  HPIJS installation =%d" %(is_hpcups_installed, is_hpijs_installed))
    if cups_printers:
        for p in cups_printers:
            printer_name, device_uri = p

            if device_uri.startswith("cups-pdf:/"):
                continue
            if device_uri.startswith("ipp:/"):
                continue

            log.debug(log.bold(printer_name))
            log.debug(log.bold('-'*len(printer_name)))

            try:
                back_end, is_hp, bus, model, serial, dev_file, host, zc, port = parseDeviceURI(device_uri)
            except Error:
                back_end, is_hp, bus, model, serial, dev_file, host, zc, port = '', False, '', '', '', '', '', '', 1
                if 'HP' in device_uri:
                    is_hp = True

            log.debug("Device URI: %s" % device_uri)
            ppd_file = os.path.join('/@unixroot/etc/cups/ppd', printer_name + '.ppd')
            ppd_fileType = None
            PPDFileError = False
            if not os.path.exists(ppd_file):
                log.error("PPD %s file not found" % ppd_file)
                addToDeviceList(HPOTHER,printer_name, device_uri,back_end, ppd_fileType, PPDFileError, True)
            else:
                log.debug("PPD: %s" % ppd_file)
            try:
                fileptr = open(ppd_file, 'rb').read()
            except IOError:
                log.warn("Fail to read ppd=%s file"%ppd_file)
                if os.access(ppd_file,os.R_OK):
                    log.debug("File %s has read permissions" %ppd_file)
                else:
                    log.warn("Insufficient permission to access file %s" %ppd_file)
                    status = False
                    return mapofDevices,status
                desc=''
            else:
                try:
                    desc = to_string_utf8( NICKNAME_PATTERN.search(fileptr).group(1) )
                except AttributeError:
                    desc = ''

            log.debug("PPD Description: %s" % desc)
            cmd= 'lpstat -p%s' % printer_name
            st, output = utils.run(cmd)
            log.debug("Printer status: %s" % output.replace("\n", ""))

            #### checking for USb devices ####
            if USB_PATTERN.search(device_uri):
                Key =USB_PATTERN.search(device_uri).group(1)
            #### checking for network devices ####
            elif NET_PATTERN.search(device_uri):
                Key = NET_PATTERN.search(device_uri).group(1)
            elif NET_ZC_PATTERN.search(device_uri):
                Key = NET_ZC_PATTERN.search(device_uri).group(1)
            elif NET_OTHER_PATTERN.search(device_uri):
                part_1 = NET_OTHER_PATTERN.search(device_uri).group(1)
                part_2 = NET_OTHER_PATTERN.search(device_uri).group(2)
                if 'HP' in part_2:
                    Key = part_2
                else:
                    log.info("unknown protocol device_uri=%s" %device_uri)
                    Key=None
            else:
                log.info("unknown protocol device_uri=%s" %device_uri)
                Key=None

            if Key is not None:
                Is_Print_Q_Enabled= True
                if output.find('Paused') !=  -1:
                    Is_Print_Q_Enabled= False
                Key=Key+"_"+back_end
                log.debug("Key'%s': deviceType '%s' is_hp '%s' bus '%s' model '%s' serial '%s' dev_file '%s' host '%s' zc '%s' port '%s' Enabled'%d'"\
                                           %( Key,back_end, is_hp, bus, model, serial, dev_file, host, zc, port,Is_Print_Q_Enabled))

                PPDFileError = False
                if back_end == 'hpfax' and not 'HP Fax' in desc:
                    log.warn("Incorrect PPD file for fax queue '%s'. Fax queue must use 'HP-Fax-hplip.ppd'." % printer_name)
                    PPDFileError = True
                elif back_end == 'hp' and 'HP Fax' in desc:
                    log.warn("Incorrect PPD file for print queue '%s'. Print queue must not use 'HP-Fax-hplip.ppd'." % printer_name)
                    PPDFileError = True
                elif back_end not in ('hp', 'hpfax'):
                    log.warn("Device %s is not HPLIP installed. Device must use the hp: or hpfax: to function in HPLIP."% printer_name)

                ppd_fileType = None
                if 'hpcups' in desc:
                    ppd_fileType = HPCUPS
                    if not is_hpcups_installed:
                        PPDFileError = True
                elif 'hpijs' in desc:
                    ppd_fileType = HPIJS
                    if not is_hpijs_installed:
                        PPDFileError = True
                elif 'Postscript' in desc:
                    ppd_fileType =HPPS
                elif is_hp:
                    ppd_fileType =HPOTHER
                    PPDFileError = True

                addToDeviceList(Key, printer_name, device_uri,back_end, ppd_fileType,PPDFileError, Is_Print_Q_Enabled)
    log.info("")
    return mapofDevices,status


# Validate and remove Queue
def reconfigure_Queue(que, mode, dialog= None,app=None):
    global Error_Found
    Error_msg =None
    if mode == INTERACTIVE_MODE:
        if 'hp' in que.DeviceType or 'hpfax' in que.DeviceType:
            if que.PPDFileError ==  False:
                log.debug("'%s' is configured correctly." %(que.PrinterName))
            else:
                Error_msg = "PPD file for '%s' is not correct. Need to choose correct PPD file." %(que.PrinterName)
        else:
            Error_msg ="'%s' is not configured using HPLIP. Need to remove and re-cofigure using hp-setup." %(que.PrinterName)


        if Error_msg != None:
            Error_Found = True
            log.error(Error_msg)
            response, value = tui.enter_yes_no("Do you want to remove and re-configure?")
            if response == False:
                log.debug("User Exit")
                sys.exit(1)
            elif value == True:
                status, status_str = cups.cups_operation(cups.delPrinter, INTERACTIVE_MODE, '', None, que.PrinterName)

                if status != cups.IPP_OK:
                    log.error("Failed to remove '%s' queue.\nRemove using hp-toolbox."%que.PrinterName)
                else:
                    log.info("' %s' removed successfully.\nRe-configuring this printer by hp-setup..."%que.PrinterName)
                    if utils.which('hp-setup'):
                        cmd = 'hp-setup -i'
                        os_utils.execute(cmd)

        elif que.IsEnabled == False:
            Error_Found = True
            responce, value =tui.enter_yes_no("'%s Queue is paused. Do you want to enable queue?"%(que.PrinterName))
            if responce == False:
                log.debug("User Exit")
                sys.exit(1)
            elif value == True:
                cups.enablePrinter(que.PrinterName)
                log.info("'%s' is enabled successfully"%que.PrinterName)
            else:
                log.info("Manually enable '%s'."%que.PrinterName)
    else:
        if 'hp' in que.DeviceType or 'hpfax' in que.DeviceType:
            if que.PPDFileError ==  False:
                log.debug("'%s' is configured correctly." %(que.PrinterName))
            else:
                log.error("PPD file for '%s' is not correct. Need to choose correct PPD file." %(que.PrinterName))
                Error_msg = QUEUES_INCORRECT_PPD
        else:
            log.error("'%s' is not configured using HPLIP. Need to remove and re-cofigure using hp-setup." %(que.PrinterName))
            Error_msg =QUEUES_CONFIG_ERROR

        if Error_msg == None and que.IsEnabled == False:
            Error_msg = QUEUES_PAUSED

        if Error_msg and dialog and app:
            Error_Found = True
            dialog.init(que.PrinterName, que.DeviceURI, Error_msg)
            dialog.show()
            log.debug("Starting GUI loop...")
            app.exec_()


# This parse the given Device URI. and provides the details.
def parseDeviceURI(device_uri):
    m = DEVICE_URI_PATTERN.match(device_uri)
    if m is None:
        raise Error(ERROR_INVALID_DEVICE_URI)

    back_end = m.group(1).lower() or ''
    is_hp = (back_end in ('hp', 'hpfax', 'hpaio'))
    bus = m.group(2).lower() or ''

    if bus not in ('usb', 'net', 'bt', 'fw', 'par'):
        raise Error(ERROR_INVALID_DEVICE_URI)

    model =m.group(3) or ''
    serial = m.group(4) or ''
    dev_file = m.group(5) or ''
    host = m.group(6) or ''
    zc = ''
    if not host:
        zc = host = m.group(7) or ''
    port = m.group(8) or 1

    if bus == 'net':
        try:
            port = int(port)
        except (ValueError, TypeError):
            port = 1

        if port == 0:
            port = 1

#    log.warning("++++: back_end '%s' is_hp '%s' bus '%s' model '%s' serial '%s' dev_file '%s' host '%s' zc '%s' port '%s' " %
#       ( back_end, is_hp, bus, model, serial, dev_file, host, zc, port))

    return back_end, is_hp, bus, model, serial, dev_file, host, zc, port


def main_function(passwordObj = None, mode = GUI_MODE, ui_toolkit= UI_TOOLKIT_QT4, quiet_mode = False, DEVICE_URI=None):
    global Error_Found
    try:
        from . import device, pml
        # This can fail due to hpmudext not being present
    except ImportError:
        log.error("Device library is not avail.")
        sys.exit(1)

    if mode == INTERACTIVE_MODE:
        try:
            from . import password
        except ImportError:
            log.warn("Failed to import password object")
        else:
            cups.setPasswordCallback(password.showPasswordPrompt)

        mapofDevices,status = parseQueues(mode)
        if status:
            if list(mapofDevices.items()) == 0:
                log.debug("No queues found.")

            for key,val in list(mapofDevices.items()):
                if len(val) >1:
                    if not quiet_mode:
                        Error_Found = True
                        log.warn("%d queues of same device %s is configured.\nRemove unwanted queues."%(len(val),val[0].PrinterName))

                    for que in val:
                        reconfigure_Queue(que, mode)
                else:
                    log.debug("")
                    log.debug("Single print queue is configured for '%s'. " %val[0].PrinterName)
                    reconfigure_Queue(val[0], mode)

            SI_sts, error_str = smart_install.disable(mode, '', None, None, passwordObj)
            if SI_sts != ERROR_NO_SI_DEVICE:
                Error_Found = True

            if Error_Found is False:
                if not quiet_mode:
                    if len(mapofDevices) == 0:
                        log.warn("No Queue(s) configured.")
                    else:
                        log.info("Queue(s) configured correctly using HPLIP.")
        else:
            log.warn("Could not complete Queue(s) configuration check")

        cups.releaseCupsInstance()

    elif mode == GUI_MODE:
        # Only Qt4 is supported.
        if ui_toolkit == 'qt3':
            log.error("This is not supported in Qt3, requires GUI support (try running with --qt4). Also, try using interactive (-i) mode.")
            sys.exit(1)

        QApplication, ui_package = utils.import_dialog(ui_toolkit)
        ui = import_module(ui_package + ".queuesconf")
        setupdialog = import_module(ui_package + ".setupdialog")

        app = QApplication(sys.argv)
        dialog = ui.QueuesDiagnose(None, "","",QUEUES_MSG_SENDING,passwordObj)

        cups.setPasswordCallback(setupdialog.showPasswordUI)
        mapofDevices,status = parseQueues(mode)
        if status:
            if list(mapofDevices.items()) == 0:
                log.debug("No queues found.")

            for key,val in list(mapofDevices.items()):
                if len(val) >1:
                    log.warn('%d queues of same device %s is configured.  Remove unwanted queues.' %(len(val),val[0].PrinterName))
                    if not quiet_mode:
                        Error_Found = True
                        dialog.showMessage("%d queues of same device %s is configured.\nRemove unwanted queues."%(len(val),val[0].PrinterName))
                    for que in val:
                        reconfigure_Queue(que, mode, dialog,app)

                else:
                    log.debug("")
                    log.debug("Single print queue is configured for '%s'. " %val[0].PrinterName)
                    reconfigure_Queue(val[0], mode, dialog, app)

            SI_sts, error_str = smart_install.disable(mode, ui_toolkit, dialog, app, passwordObj)
            if SI_sts != ERROR_NO_SI_DEVICE:
                Error_Found = True

            if Error_Found is False:
                if not quiet_mode:
                    if len(mapofDevices) == 0:
                        msg= "No Queue(s) configured."
                    else:
                        msg= "Queue(s) configured correctly using HPLIP."
                    dialog.showSuccessMessage(msg)
        else:
            log.warn("Could not complete Queue(s) configuration check")

        cups.releaseCupsInstance()
