# -*- coding: utf-8 -*-
#
# (c) Copyright 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: Don Welch
#



# Std Lib
import sys
import os
import threading
import pickle
import time
import struct

# Local
from base.g import *
from base.codes import *
from base.ldif import LDIFParser
from base import device, utils, vcard
from prnt import cups
from base.sixext import BytesIO
from base.sixext import to_bytes_utf8, to_long, to_unicode
try:
    from . import coverpages
except ImportError:
    pass

try:
    import dbus
except ImportError:
    log.error("dbus is required for PC send fax.")

import warnings
# Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters
# (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04)
warnings.simplefilter("ignore", DeprecationWarning)


# Update queue values (Send thread ==> UI)
STATUS_IDLE = 0
STATUS_PROCESSING_FILES = 1
STATUS_SENDING_TO_RECIPIENT = 2
STATUS_DIALING = 3
STATUS_CONNECTING = 4
STATUS_SENDING = 5
STATUS_COMPLETED = 6
STATUS_CREATING_COVER_PAGE = 7
STATUS_ERROR = 8
STATUS_BUSY = 9
STATUS_CLEANUP = 10
STATUS_ERROR_IN_CONNECTING = 11
STATUS_ERROR_IN_TRANSMITTING = 12
STATUS_ERROR_PROBLEM_IN_FAXLINE = 13
STATUS_JOB_CANCEL = 14 

# Event queue values (UI ==> Send thread)
EVENT_FAX_SEND_CANCELED = 1
# Other values in queue are:
#EVENT_FAX_RENDER_COMPLETE_BEGIN = 8010
#EVENT_FAX_RENDER_COMPLETE_SENDDATA = 8011
#EVENT_FAX_RENDER_COMPLETE_END = 8012

# **************************************************************************** #
# HPLIP G3 Fax File Format (big endian)
#
# #==============================================#
# # File Header: Total 28 bytes                  #
# #..............................................#
# # Magic bytes: 8 bytes ("hplip_g3")            #
# # Format version: 8 bits (1)                   #
# # Total pages in file(=p): 32 bits             #
# # Hort DPI: 16 bits (200 or 300)               #
# # Vert DPI: 16 bits (100, 200, or 300)         #
# # Page Size: 8 bits (0=Unk, 1=Letter, 2=A4,    #
# #                    3=Legal)                  #
# # Resolution: 8 bits (0=Unk, 1=Std, 2=Fine,    #
# #                     3=300DPI)                #
# # Encoding: 8 bits (2=MH, 4=MMR, 7=JPEG)       #
# # Reserved1: 32 bits (0)                       #
# # Reserved2: 32 bits (0)                       #
# #----------------------------------------------#
# # Page 1 Header: Total 24 bytes                #
# #..............................................#
# # Page number: 32 bits (1 based)               #
# # Pixels per row: 32 bits                      #
# # Rows this page: 32 bits                      #
# # Image bytes this page(=x): 32 bits           #
# # Thumbnail bytes this page(=y): 32 bits       #
# #  (thumbnail not present if y == 0)           #
# #  (encoding?)                                 #
# #     letter: 134 px wide x 173 px high        #
# #     legal:  134 px wide x 221 px high        #
# #     a4 :    134 px wide x 190 px high        #
# # Reserved3: 32 bits (0)                       #
# #..............................................#
# # Image data: x bytes                          #
# #..............................................#
# # Thumbnail data: y bytes (if present)         #
# #----------------------------------------------#
# # Page 2 Header: Total 24 bytes                #
# #..............................................#
# # Image Data                                   #
# #..............................................#
# # Thumbnail data (if present)                  #
# #----------------------------------------------#
# # ... Pages 3 - (p-1) ...                      #
# #----------------------------------------------#
# # Page p Header: Total 24 bytes                #
# #..............................................#
# # Image Data                                   #
# #..............................................#
# # Thumbnail data (if present)                  #
# #==============================================#
#

RESOLUTION_STD = 1
RESOLUTION_FINE = 2
RESOLUTION_300DPI = 3

FILE_HEADER_SIZE = 28
PAGE_HEADER_SIZE = 24
# **************************************************************************** #

##skip_dn = ["uid=foo,ou=People,dc=example,dc=com",
##    "uid=bar,ou=People,dc=example,dc=com", "dc=example,dc=com"]

class FaxLDIFParser(LDIFParser):
    def __init__(self, input, db):
        LDIFParser.__init__(self, input)
        self.db = db

    def handle(self, dn, entry):
        if dn:
            try:
                firstname = entry['givenName'][0]
            except KeyError:
                try:
                    firstname = entry['firstname'][0]
                except KeyError:
                    firstname = ''

            try:
                lastname = entry['sn'][0]
            except KeyError:
                try:
                    lastname = entry['lastname'][0]
                except KeyError:
                    lastname = ''

            try:
                nickname = entry['cn'][0]
            except KeyError:
                nickname = firstname + ' ' + lastname

            try:
                fax = entry['facsimiletelephonenumber'][0] # fax
            except KeyError:
                try:
                    fax = entry['fax'][0]
                except KeyError:
                    try:
                        fax = entry['workphone'][0]
                    except KeyError:
                        fax  = ''
            try:
                title = entry['title'][0]
            except KeyError:
                try:
                    title = entry['department'][0]
                except KeyError:
                    title = ''

            grps = []
            try:
                grps = entry['ou']
            except KeyError:
                pass

            grps.append(to_unicode('All'))
            groups = [g for g in grps if g]

            if nickname:
                log.debug("Import: name=%s, fax=%s, group(s)=%s, notes=%s" % ( nickname, fax, ','.join(groups), dn))
                self.db.set(nickname, title, firstname, lastname, fax, groups, dn)



# **************************************************************************** #
class FaxAddressBook(object): # Pickle based address book
    def __init__(self):
        self._data = {}
        #
        # { 'name' : {'name': u'',
        #             'firstname' : u'', # NOT USED STARTING IN 2.8.9
        #             'lastname': u', # NOT USED STARTING IN 2.8.9
        #             'title' : u'',  # NOT USED STARTING IN 2.8.9
        #             'fax': u'',
        #             'groups' : [u'', u'', ...],
        #             'notes' : u'', } ...
        # }
        #
        self.load()

    def load(self):
        self._fab = "/dev/null"
        if prop.user_dir != None:
            self._fab = os.path.join(prop.user_dir, "fab.pickle")
            #old_fab = os.path.join(prop.user_dir, "fab.db")

            # Load the existing pickle if present
            if os.path.exists(self._fab):
               pickle_file = open(self._fab, "rb")
               self._data = pickle.load(pickle_file)
               pickle_file.close()
            else:
               self.save() # save the empty file to create the file


    def set(self, name, title, firstname, lastname, fax, groups, notes):
        # try:
        #     grps = [to_unicode(s) for s in groups]
        # except UnicodeDecodeError:
        #     grps = [to_unicode(s.decode('utf-8')) for s in groups]
        grps = [to_unicode(s) for s in groups]

        self._data[to_unicode(name)] = {'name' : to_unicode(name),
                                    'title': to_unicode(title),  # NOT USED STARTING IN 2.8.9
                                    'firstname': to_unicode(firstname), # NOT USED STARTING IN 2.8.9
                                    'lastname': to_unicode(lastname), # NOT USED STARTING IN 2.8.9
                                    'fax': to_unicode(fax),
                                    'notes': to_unicode(notes),
                                    'groups': grps}

        self.save()

    insert = set


    def set_key_value(self, name, key, value):
        self._data[to_unicode(name)][key] = value
        self.save()


    def get(self, name):
        return self._data.get(name, None)

    select = get

    def rename(self, old_name, new_name):
        try:
            self._data[old_name]
        except KeyError:
            return
        else:
            try:
                self._data[new_name]
            except KeyError:
                self._data[new_name] = self._data[old_name].copy()
                self._data[new_name]['name'] = new_name
                del self._data[old_name]
                self.save()


    def get_all_groups(self):
        all_groups = []
        for e, v in list(self._data.items()):
            for g in v['groups']:
                if g not in all_groups:
                    all_groups.append(g)
        return all_groups


    def get_all_records(self):
        return self._data


    def get_all_names(self):
        return list(self._data.keys())


    def save(self):
        try:
            pickle_file = open(self._fab, "wb")
            pickle.dump(self._data, pickle_file, protocol=2)
            pickle_file.close()
        except IOError:
            log.error("I/O error saving fab file.")


    def clear(self):
        self._data = {}
        self.save()


    def delete(self, name):
        if name in self._data:
            del self._data[name]
            self.save()
            return True

        return False


    def last_modification_time(self):
        try:
            return os.stat(self._fab).st_mtime
        except OSError:
            return 0


    def update_groups(self, group, members):
        for e, v in list(self._data.items()):
            if v['name'] in members: # membership indicated
                if not group in v['groups']:
                    v['groups'].append(to_unicode(group))
            else:
                if group in v['groups']:
                    v['groups'].remove(to_unicode(group))
        self.save()


    def delete_group(self, group):
        for e, v in list(self._data.items()):
            if group in v['groups']:
                v['groups'].remove(to_unicode(group))
        self.save()


    def group_members(self, group):
        members = []
        for e, v in list(self._data.items()):
            if group in v['groups']:
                members.append(e)
        return members


    def add_to_group(self, group, members):
        group_members = self.group_members(group)
        new_group_members = []
        for m in members:
            if m not in group_members:
                new_group_members.append(m)

        self.update_groups(group, group_members + new_group_members)


    def remove_from_group(self, group, remove_members):
        group_members = self.group_members(group)
        new_group_members = []
        for m in group_members:
            if m not in remove_members:
                new_group_members.append(m)

        self.update_groups(group, new_group_members)


    def rename_group(self, old_group, new_group):
        members = self.group_members(old_group)
        self.update_groups(old_group, [])
        self.update_groups(new_group, members)


    def import_ldif(self, filename):
        try:
            data = open(filename, 'r').read()
            log.debug_block(filename, data)
            parser = FaxLDIFParser(open(filename, 'r'), self)
            parser.parse()
            self.save()
            return True, ''
        except ValueError as e:
            return False, e.message


    def import_vcard(self, filename):
        data = open(filename, 'r').read()
        log.debug_block(filename, data)

        for card in vcard.VCards(vcard.VFile(vcard.opentextfile(filename))):
            log.debug(card)

            if card['name']:
                fax = ''
                for x in range(1, 9999):
                    if x == 1:
                        s = 'phone'
                    else:
                        s = 'phone%d' % x

                    try:
                        card[s]
                    except KeyError:
                        break
                    else:
                        if 'fax' in card[s]['type']:
                            fax = card[s]['number']
                            break

                org = card.get('organisation', '')
                if org:
                    org = [org]
                else:
                    org = card.get('categories', '').split(';')
                    if not org:
                        org = []

                org.append(to_unicode('All'))
                groups = [o for o in org if o]

                name = card['name']
                notes = card.get('notes', to_unicode(''))
                log.debug("Import: name=%s, fax=%s group(s)=%s notes=%s" % (name, fax, ','.join(groups), notes))
                self.set(name, to_unicode(''), to_unicode(''), to_unicode(''), fax, groups, notes)

        return True, ''


# **************************************************************************** #
class FaxDevice(device.Device):

    def __init__(self, device_uri=None, printer_name=None,
                 callback=None,
                 fax_type=FAX_TYPE_NONE,
                 disable_dbus=False):

        device.Device.__init__(self, device_uri, printer_name,
                               None, callback, disable_dbus)

        self.send_fax_thread = None
        self.upload_log_thread = None
        self.fax_type = fax_type

        if not disable_dbus:
            session_bus = dbus.SessionBus()
            self.service = session_bus.get_object('com.hplip.StatusService', "/com/hplip/StatusService")
        else:
            self.service = None


    def setPhoneNum(self, num):
        raise AttributeError

    def getPhoneNum(self):
        raise AttributeError

    phone_num = property(getPhoneNum, setPhoneNum)


    def setStationName(self, name):
        raise AttributeError

    def getStationName(self):
        raise AttributeError

    station_name = property(getStationName, setStationName)

    def setDateAndTime(self):
        raise AttributeError

    def uploadLog(self):
        raise AttributeError

    def isUploadLogActive(self):
        raise AttributeError

    def waitForUploadLogThread(self):
        raise AttributeError

    def sendFaxes(self, phone_num_list, fax_file_list, cover_message='', cover_re='',
                  cover_func=None, preserve_formatting=False, printer_name='',
                  update_queue=None, event_queue=None):

        raise AttributeError

    def isSendFaxActive(self):
        if self.send_fax_thread is not None:
            return self.send_fax_thread.is_alive()
        else:
            return False

    def waitForSendFaxThread(self):
        if self.send_fax_thread is not None and \
            self.send_fax_thread.is_alive():

            try:
                self.send_fax_thread.join()
            except KeyboardInterrupt:
                pass


# **************************************************************************** #


def getFaxDevice(device_uri=None, printer_name=None,
                 callback=None,
                 fax_type=FAX_TYPE_NONE,
                 disable_dbus=False):

    if fax_type == FAX_TYPE_NONE:
        if device_uri is None and printer_name is not None:
            printers = cups.getPrinters()

            for p in printers:
                if p.name.lower() == printer_name.lower():
                    device_uri = p.device_uri
                    break
            else:
                raise Error(ERROR_DEVICE_NOT_FOUND)

        if device_uri is not None:
            mq = device.queryModelByURI(device_uri)
            fax_type = mq['fax-type']

    log.debug("fax-type=%d" % fax_type)

    if fax_type in (FAX_TYPE_BLACK_SEND_EARLY_OPEN, FAX_TYPE_BLACK_SEND_LATE_OPEN):
        from .pmlfax import PMLFaxDevice
        return PMLFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)

    elif fax_type == FAX_TYPE_SOAP:
        from .soapfax import SOAPFaxDevice
        return SOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)

    elif fax_type == FAX_TYPE_LEDMSOAP:
        from .ledmsoapfax import LEDMSOAPFaxDevice
        return LEDMSOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)

    elif fax_type == FAX_TYPE_MARVELL:
        from .marvellfax import MarvellFaxDevice
        return MarvellFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)

    elif fax_type == FAX_TYPE_LEDM:
        from .ledmfax import LEDMFaxDevice
        return LEDMFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
        
    elif fax_type == FAX_TYPE_CDM:
        from .cdmfax import CDMFaxDevice
        return CDMFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)        

    else:
        raise Error(ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION)

# **************************************************************************** #




# TODO: Define these in only 1 place!
STATE_DONE = 0
STATE_ABORTED = 10
STATE_SUCCESS = 20
STATE_BUSY = 25
STATE_READ_SENDER_INFO = 30
STATE_PRERENDER = 40
STATE_COUNT_PAGES = 50
STATE_NEXT_RECIPIENT = 60
STATE_COVER_PAGE = 70
STATE_SINGLE_FILE = 80
STATE_MERGE_FILES = 90
STATE_SINGLE_FILE = 100
STATE_SEND_FAX = 110
STATE_CLEANUP = 120
STATE_ERROR = 130

class FaxSendThread(threading.Thread):
    def __init__(self, dev, service, phone_num_list, fax_file_list,
                 cover_message='', cover_re='', cover_func=None, preserve_formatting=False,
                 printer_name='', update_queue=None, event_queue=None):

        threading.Thread.__init__(self)

        self.dev = dev # device.Device
        self.service = service # dbus proxy to status server object
        self.phone_num_list = phone_num_list
        self.fax_file_list = fax_file_list
        self.update_queue = update_queue
        self.event_queue = event_queue
        self.cover_message = cover_message
        self.cover_re = cover_re
        self.cover_func = cover_func
        self.current_printer = printer_name
        self.stream = BytesIO()
        self.prev_update = ''
        self.remove_temp_file = False
        self.preserve_formatting = preserve_formatting
        self.results = {} # {'file' : error_code,...}
        self.cover_page_present = False
        self.recipient_file_list = []
        self.f = None # final file of fax data to send (pages merged)
        self.job_hort_dpi = 0
        self.job_hort_dpi = 0
        self.job_vert_dpi = 0
        self.job_page_size = 0
        self.job_resolution = 0
        self.job_encoding = 0


    def pre_render(self, state):
        # pre-render each page that needs rendering
        # except for the cover page
        self.cover_page_present = False
        log.debug(self.fax_file_list)
        
        # sample fax_file_list
        #[(dbus.String('/tmp/hpfax-xw43tq9v'), 'application/hplip-fax', 'HPLIP Fax', dbus.String('test.pdf'), 4169580547)]
        #[('basic', 'application/hplip-fax-coverpage', 'HP Fax Coverpage: "basic"', 'Cover Page', 1)]

        for fax_file in self.fax_file_list: # (file, type, desc, title)
            fax_file_name, fax_file_type, fax_file_desc, \
                fax_file_title, fax_file_pages = fax_file

            if fax_file_type == "application/hplip-fax-coverpage": # render later
                self.cover_page_present = True
                log.debug("Skipping coverpage")

            #if fax_file_type == "application/hplip-fax": # already rendered
            else:
                self.rendered_file_list.append((fax_file_name, "application/hplip-fax",
                    "HP Fax", fax_file_title))

                log.debug("Processing pre-rendered file: %s (%d pages)" %
                    (fax_file_name, fax_file_pages))

            if self.check_for_cancel():
                state = STATE_ABORTED

        log.debug(self.rendered_file_list)

        if self.check_for_cancel():
            state = STATE_ABORTED

        return state


    def count_pages(self, state):
        self.recipient_file_list = self.rendered_file_list[:]
        log.debug("Counting total pages...")
        self.job_total_pages = 0
        log.debug(self.recipient_file_list)

        i = 0
        for fax_file in self.recipient_file_list: # (file, type, desc, title)
            fax_file_name = fax_file[0]
            log.debug("Processing file (counting pages): %s..." % fax_file_name)

            #self.write_queue((STATUS_PROCESSING_FILES, self.job_total_pages, ''))

            if os.path.exists(fax_file_name):
                self.results[fax_file_name] = ERROR_SUCCESS
                fax_file_fd = open(fax_file_name, 'rb')
                header = fax_file_fd.read(FILE_HEADER_SIZE)

                magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
                    resolution, encoding, reserved1, reserved2 = \
                        self.decode_fax_header(header)

                if magic != b'hplip_g3':
                    log.error("Invalid file header. Bad magic.")
                    self.results[fax_file_name] = ERROR_FAX_INVALID_FAX_FILE
                    state = STATE_ERROR
                    continue

                if not i:
                    self.job_hort_dpi, self.job_vert_dpi, self.job_page_size, \
                        self.job_resolution, self.job_encoding = \
                        hort_dpi, vert_dpi, page_size, resolution, encoding

                    i += 1
                else:
                    if self.job_hort_dpi != hort_dpi or \
                        self.job_vert_dpi != vert_dpi or \
                        self.job_page_size != page_size or \
                        self.job_resolution != resolution or \
                        self.job_encoding != encoding:

                        log.error("Incompatible options for file: %s" % fax_file_name)
                        self.results[fax_file_name] = ERROR_FAX_INCOMPATIBLE_OPTIONS
                        state = STATE_ERROR


                log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
                          (magic, version, total_pages, hort_dpi,
                           vert_dpi, page_size, resolution, encoding))

                self.job_total_pages += total_pages

                fax_file_fd.close()

            else:
                log.error("Unable to find HP Fax file: %s" % fax_file_name)
                self.results[fax_file_name] = ERROR_FAX_FILE_NOT_FOUND
                state = STATE_ERROR
                break

            if self.check_for_cancel():
                state = STATE_ABORTED
                break


        if self.cover_page_present:
            self.job_total_pages += 1 # Cover pages are truncated to 1 page

        log.debug("Total fax pages=%d" % self.job_total_pages)

        return state

    def decode_fax_header(self, header):
        try:
            return struct.unpack(">8sBIHHBBBII", header)
        except struct.error:
            return -1, -1, -1, -1, -1, -1, -1, -1, -1, -1

    def decode_page_header(self, header):
        try:
            return struct.unpack(">IIIIII", header)
        except struct.error:
            return -1, -1, -1, -1, -1, -1

    def cover_page(self,  recipient):
        if self.job_total_pages > 1:
            state = STATE_MERGE_FILES
        else:
            state = STATE_SINGLE_FILE

        if self.cover_page_present:
            log.debug("Creating cover page for recipient: %s" % recipient['name'])
            fax_file, canceled = self.render_cover_page(recipient)

            if canceled:
                state = STATE_ABORTED
            elif not fax_file:
                state = STATE_ERROR # timeout
            else:
                self.recipient_file_list.insert(0, (fax_file, "application/hplip-fax",
                                                    "HP Fax", 'Cover Page'))

                log.debug("Cover page G3 file: %s" % fax_file)

                self.results[fax_file] = ERROR_SUCCESS

        return state

    def single_file(self, state):
        state = STATE_SEND_FAX

        log.debug("Processing single file...")
        self.f = self.recipient_file_list[0][0]

        try:
            f_fd = open(self.f, 'rb')
        except IOError:
            log.error("Unable to open fax file: %s" % self.f)
            state = STATE_ERROR
        else:
            header = f_fd.read(FILE_HEADER_SIZE)

            magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
                resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header)

            self.results[self.f] = ERROR_SUCCESS

            if magic != b'hplip_g3':
                log.error("Invalid file header. Bad magic.")
                self.results[self.f] = ERROR_FAX_INVALID_FAX_FILE
                state = STATE_ERROR

            log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
                      (magic, version, total_pages, hort_dpi, vert_dpi,
                       page_size, resolution, encoding))

            f_fd.close()

        return state

    def merge_cdm_fax_files(self,state):
        log.debug("%s State: Merge multiple CDM files" % ("*"*20))
        log.debug(self.recipient_file_list)
        self.remove_temp_file = True
        HeaderSrtSessionAndOpenDataSrc = b'\xD1\xC8\x00\xC8\x00\xF8\x89\xC0\x00\xF8\x86\xC0\x03\xF8\x8F\xC0\x03\xF8\xB2\x41'
        FooterEndSessionAndCloseDataSrc = b'\x49\x42'
        job_page_num = 1
        if self.job_total_pages:
            try:
                f_fd, self.f = utils.make_temp_file()
                os.write(f_fd, bytearray(HeaderSrtSessionAndOpenDataSrc))
                for fax_file in self.recipient_file_list:
                    fax_file_name = fax_file[0]
                    log.debug("Processing file: %s..." % fax_file_name)
                    self.job_total_pages = self.job_total_pages + 1
                    with open(fax_file_name,'rb') as infile:
                        try:
                            os.write(f_fd,infile.read())
                        except Exception as inst:
                            log.debug(inst)
                    if self.check_for_cancel():
                        state = STATE_ABORTED
                        break
                    log.debug("Writting to queues...")
                    self.write_queue((STATUS_PROCESSING_FILES, job_page_num, ''))
                    job_page_num = job_page_num + 1
                log.debug("adding footer...")        
                os.write(f_fd, bytearray(FooterEndSessionAndCloseDataSrc))                
                os.close(f_fd)
                log.debug("Total pages=%d" % self.job_total_pages)
            except:
                state = STATE_ERROR  
        return state

    def merge_files(self, state):
        log.debug("%s State: Merge multiple files" % ("*"*20))
        log.debug(self.recipient_file_list)
        log.debug("Merging g3 files...")
        self.remove_temp_file = True

        if self.job_total_pages:
            f_fd, self.f = utils.make_temp_file()
            log.debug("Temp file=%s" % self.f)

            data = struct.pack(">8sBIHHBBBII", b"hplip_g3", to_long(1), self.job_total_pages,
                self.job_hort_dpi, self.job_vert_dpi, self.job_page_size,
                self.job_resolution, self.job_encoding,
                to_long(0), to_long(0))

            os.write(f_fd, data)

            job_page_num = 1

            for fax_file in self.recipient_file_list:
                fax_file_name = fax_file[0]
                log.debug("Processing file: %s..." % fax_file_name)

                if self.results[fax_file_name] == ERROR_SUCCESS:
                    fax_file_fd = open(fax_file_name, 'rb')
                    header = fax_file_fd.read(FILE_HEADER_SIZE)

                    magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
                        resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header)

                    if magic != b'hplip_g3':
                        log.error("Invalid file header. Bad magic.")
                        state = STATE_ERROR
                        break

                    log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
                              (magic, version, total_pages, hort_dpi, vert_dpi, page_size, resolution, encoding))

                    for p in range(total_pages):
                        header = fax_file_fd.read(PAGE_HEADER_SIZE)

                        page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, reserved2 = \
                            self.decode_page_header(header)

                        if page_num == -1:
                            log.error("Page header error")
                            state - STATE_ERROR
                            break

                        header = struct.pack(">IIIIII", job_page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, to_long(0))
                        os.write(f_fd, header)

                        self.write_queue((STATUS_PROCESSING_FILES, job_page_num, ''))

                        log.debug("Page=%d PPR=%d RPP=%d BPP=%d Thumb=%s" %
                                  (page_num, ppr, rpp, bytes_to_read, thumbnail_bytes))

                        os.write(f_fd, fax_file_fd.read(bytes_to_read))
                        job_page_num += 1

                    fax_file_fd.close()

                    if self.check_for_cancel():
                        state = STATE_ABORTED
                        break

                else:
                    log.error("Skipping file: %s" % fax_file_name)
                    continue

            os.close(f_fd)
            log.debug("Total pages=%d" % self.job_total_pages)

        return state


    def next_recipient_gen(self):
        for a in self.phone_num_list:
            yield a

    def next_file_gen(self):
        for a in self.recipient_file_list:
            yield a


    def render_file(self, path, title, mime_type, force_single_page=False):
        all_pages = True
        page_range = ''
        page_set = 0
        nup = 1

        cups.resetOptions()

        if mime_type in ["application/x-cshell",
                         "application/x-perl",
                         "application/x-python",
                         "application/x-shell",
                         "application/x-sh",
                         "text/plain",]:

            cups.addOption('prettyprint')

        if nup > 1:
            cups.addOption('number-up=%d' % nup)

        if force_single_page:
            cups.addOption('page-ranges=1') # Force coverpage to 1 page

        sent_job_id = cups.printFile(self.current_printer, path, title)
        cups.resetOptions()

        log.debug("Job ID=%d" % sent_job_id)
        job_id = 0

        time.sleep(1)

        fax_file = ''
        complete = False

        end_time = time.time() + 300.0 # wait for 5 min. max
        while time.time() < end_time:
            log.debug("Waiting for fax... type =%s"%type(self.dev.device_uri))

            result = list(self.service.CheckForWaitingFax(self.dev.device_uri, prop.username, sent_job_id))

            fax_file = str(result[7])
            log.debug("Fax file=%s" % fax_file)

            if fax_file:
                break

            if self.check_for_cancel():
                log.error("Render canceled. Canceling job #%d..." % sent_job_id)
                cups.cancelJob(sent_job_id)
                return '', True

            time.sleep(1)

        else:
            log.error("Timeout waiting for rendering. Canceling job #%d..." % sent_job_id)
            cups.cancelJob(sent_job_id)
            return '', False

        return fax_file, False


    def check_for_cancel(self):
        canceled = False
        while self.event_queue.qsize():
            try:
                event = self.event_queue.get(0)
                if event[0] == EVENT_FAX_SEND_CANCELED:
                    canceled = True
                    log.debug("Cancel pressed!")
            except Queue.Empty:
                break

        return canceled

    def render_cover_page(self, a):
        log.debug("Creating cover page...")

        #Read file again just before creating the coverpage, so that we get updated voice_phone and email_address from /hplip.conf file
        #hplip.conf file get updated, whenever user changes coverpage info from hp-faxsetup window.
        user_conf.read()

        pdf = self.cover_func(page_size=coverpages.PAGE_SIZE_LETTER,
                              total_pages=self.job_total_pages,

                              recipient_name=a['name'],
                              recipient_phone='', # ???
                              recipient_fax=a['fax'],

                              sender_name=self.sender_name,
                              sender_phone=user_conf.get('fax', 'voice_phone'),
                              sender_fax=self.sender_fax,
                              sender_email=user_conf.get('fax', 'email_address'),

                              regarding=self.cover_re,
                              message=self.cover_message,
                              preserve_formatting=self.preserve_formatting)

        log.debug("PDF File=%s" % pdf)
        fax_file, canceled = self.render_file(pdf, 'Cover Page', "application/pdf",
            force_single_page=True)

        try:
            os.remove(pdf)
        except IOError:
            pass

        return fax_file, canceled


    def write_queue(self, message):
        if self.update_queue is not None and message != self.prev_update:
            self.update_queue.put(message)
            time.sleep(0)
            self.prev_update = message


    def run(self):
        pass



