'Send pending messages from outbox (usually called from scheduler)
@param contact_method: the output channel (see pr_contact.method)
@todo: contact_method = "ALL"'

def process_outbox(self, contact_method='EMAIL'):

    db = current.db
    s3db = current.s3db
    lookup_org = False
    channels = {}
    outgoing_sms_handler = None
    channel_id = None
    if (contact_method == 'SMS'):
        table = s3db.msg_sms_outbound_gateway
        etable = db.msg_channel
        query = ((table.deleted == False) & (table.channel_id == etable.channel_id))
        rows = db(query).select(table.channel_id, table.organisation_id, etable.instance_type)
        if (not rows):
            raise ValueError('No    SMS    handler    defined!')
        if (len(rows) == 1):
            lookup_org = False
            row = rows.first()
            outgoing_sms_handler = row['msg_channel.instance_type']
            channel_id = row['msg_sms_outbound_gateway.channel_id']
        else:
            lookup_org = True
            org_branches = current.deployment_settings.get_org_branches()
            if org_branches:
                org_parents = s3db.org_parents
            for row in rows:
                channels[row['msg_sms_outbound_gateway.organisation_id']] = dict(outgoing_sms_handler=row['msg_channel.instance_type'], channel_id=row['msg_sms_outbound_gateway.channel_id'])
    elif (contact_method == 'TWITTER'):
        twitter_settings = self.get_twitter_api()
        if (not twitter_settings):
            raise ValueError('No    Twitter    API    available!')
    def dispatch_to_pe_id(pe_id, subject, message, outbox_id, message_id, attachments=[], organisation_id=None, contact_method=contact_method, channel_id=channel_id, from_address=None, outgoing_sms_handler=outgoing_sms_handler, lookup_org=lookup_org, channels=channels):
        '\n                                                                Helper    method    to    send    messages    by    pe_id\n\n                                                                @param    pe_id:    the    pe_id\n                                                                @param    subject:    the    message    subject\n                                                                @param    message:    the    message    body\n                                                                @param    outbox_id:    the    outbox    record    ID\n                                                                @param    message_id:    the    message_id\n                                                                @param    organisation_id:    the    organisation_id    (for    SMS)\n                                                                @param    contact_method:    the    contact    method\n                                                '
        table = s3db.pr_contact
        query = (((table.pe_id == pe_id) & (table.contact_method == contact_method)) & (table.deleted == False))
        contact_info = db(query).select(table.value, orderby=table.priority, limitby=(0, 1)).first()
        if contact_info:
            address = contact_info.value
            if (contact_method == 'EMAIL'):
                return self.send_email(address, subject, message, sender=from_address, attachments=attachments)
            elif (contact_method == 'SMS'):
                if lookup_org:
                    channel = channels.get(organisation_id)
                    if ((not channel) and org_branches):
                        orgs = org_parents(organisation_id)
                        for org in orgs:
                            channel = channels.get(org)
                            if channel:
                                break
                    if (not channel):
                        channel = channels.get(None)
                    if (not channel):
                        return False
                    outgoing_sms_handler = channel['outgoing_sms_handler']
                    channel_id = channel['channel_id']
                if (outgoing_sms_handler == 'msg_sms_webapi_channel'):
                    return self.send_sms_via_api(address, message, message_id, channel_id)
                elif (outgoing_sms_handler == 'msg_sms_smtp_channel'):
                    return self.send_sms_via_smtp(address, message, channel_id)
                elif (outgoing_sms_handler == 'msg_sms_modem_channel'):
                    return self.send_sms_via_modem(address, message, channel_id)
                elif (outgoing_sms_handler == 'msg_sms_tropo_channel'):
                    return self.send_sms_via_tropo(outbox_id, message_id, address, message, channel_id)
            elif (contact_method == 'TWITTER'):
                return self.send_tweet(message, address)
        return False
    outbox = s3db.msg_outbox
    query = (((outbox.contact_method == contact_method) & (outbox.status == 1)) & (outbox.deleted == False))
    petable = s3db.pr_pentity
    left = [petable.on((petable.pe_id == outbox.pe_id))]
    fields = [outbox.id, outbox.message_id, outbox.pe_id, outbox.retries, petable.instance_type]
    if (contact_method == 'EMAIL'):
        mailbox = s3db.msg_email
        fields.extend([mailbox.subject, mailbox.body, mailbox.from_address])
        left.append(mailbox.on((mailbox.message_id == outbox.message_id)))
    elif (contact_method == 'SMS'):
        mailbox = s3db.msg_sms
        fields.append(mailbox.body)
        if lookup_org:
            fields.append(mailbox.organisation_id)
        left.append(mailbox.on((mailbox.message_id == outbox.message_id)))
    elif (contact_method == 'TWITTER'):
        mailbox = s3db.msg_twitter
        fields.append(mailbox.body)
        left.append(mailbox.on((mailbox.message_id == outbox.message_id)))
    else:
        raise
    rows = db(query).select(left=left, orderby=(~ outbox.retries), *fields)
    if (not rows):
        return
    htable = s3db.table('hrm_human_resource')
    otable = s3db.org_organisation
    ptable = s3db.pr_person
    gtable = s3db.pr_group
    mtable = db.pr_group_membership
    gleft = [mtable.on((((mtable.group_id == gtable.id) & (mtable.person_id != None)) & (mtable.deleted != True))), ptable.on(((ptable.id == mtable.person_id) & (ptable.deleted != True)))]
    if htable:
        oleft = [htable.on((((htable.organisation_id == otable.id) & (htable.person_id != None)) & (htable.deleted != True))), ptable.on(((ptable.id == htable.person_id) & (ptable.deleted != True)))]
        etable = s3db.hrm_training_event
        ttable = s3db.hrm_training
        tleft = [ttable.on((((ttable.training_event_id == etable.id) & (ttable.person_id != None)) & (ttable.deleted != True))), ptable.on(((ptable.id == ttable.person_id) & (ptable.deleted != True)))]
        atable = s3db.table('deploy_alert')
        if atable:
            ltable = db.deploy_alert_recipient
            aleft = [ltable.on((ltable.alert_id == atable.id)), htable.on((((htable.id == ltable.human_resource_id) & (htable.person_id != None)) & (htable.deleted != True))), ptable.on(((ptable.id == htable.person_id) & (ptable.deleted != True)))]
    chainrun = False
    organisation_id = None
    attachment_table = s3db.msg_attachment
    document_table = s3db.doc_document
    file_field = document_table.file
    if file_field.custom_retrieve_file_properties:
        retrieve_file_properties = file_field.custom_retrieve_file_properties
    else:
        retrieve_file_properties = file_field.retrieve_file_properties
    mail_attachment = current.mail.Attachment
    for row in rows:
        attachments = []
        status = True
        message_id = row.msg_outbox.message_id
        if (contact_method == 'EMAIL'):
            subject = (row['msg_email.subject'] or '')
            message = (row['msg_email.body'] or '')
            from_address = (row['msg_email.from_address'] or '')
            query = ((((attachment_table.message_id == message_id) & (attachment_table.deleted != True)) & (attachment_table.document_id == document_table.id)) & (document_table.deleted != True))
            arows = db(query).select(file_field)
            for arow in arows:
                file = arow.file
                prop = retrieve_file_properties(file)
                _file_path = os.path.join(prop['path'], file)
                attachments.append(mail_attachment(_file_path))
        elif (contact_method == 'SMS'):
            subject = None
            message = (row['msg_sms.body'] or '')
            from_address = None
            if lookup_org:
                organisation_id = row['msg_sms.organisation_id']
        elif (contact_method == 'TWITTER'):
            subject = None
            message = (row['msg_twitter.body'] or '')
            from_address = None
        else:
            continue
        entity_type = row['pr_pentity'].instance_type
        if (not entity_type):
            current.log.warning('s3msg', 'Entity    type    unknown')
            continue
        row = row['msg_outbox']
        pe_id = row.pe_id
        message_id = row.message_id
        if (entity_type == 'pr_person'):
            try:
                status = dispatch_to_pe_id(pe_id, subject, message, row.id, message_id, organisation_id=organisation_id, from_address=from_address, attachments=attachments)
            except:
                status = False
        elif (entity_type == 'pr_group'):
            gquery = (gtable.pe_id == pe_id)
            recipients = db(gquery).select(ptable.pe_id, left=gleft)
            pe_ids = set((r.pe_id for r in recipients))
            pe_ids.discard(None)
            if pe_ids:
                for pe_id in pe_ids:
                    outbox.insert(message_id=message_id, pe_id=pe_id, contact_method=contact_method, system_generated=True)
                chainrun = True
            status = True
        elif (htable and (entity_type == 'org_organisation')):
            oquery = (otable.pe_id == pe_id)
            recipients = db(oquery).select(ptable.pe_id, left=oleft)
            pe_ids = set((r.pe_id for r in recipients))
            pe_ids.discard(None)
            if pe_ids:
                for pe_id in pe_ids:
                    outbox.insert(message_id=message_id, pe_id=pe_id, contact_method=contact_method, system_generated=True)
                chainrun = True
            status = True
        elif (entity_type == 'hrm_training_event'):
            equery = (etable.pe_id == pe_id)
            recipients = db(equery).select(ptable.pe_id, left=tleft)
            pe_ids = set((r.pe_id for r in recipients))
            pe_ids.discard(None)
            if pe_ids:
                for pe_id in pe_ids:
                    outbox.insert(message_id=message_id, pe_id=pe_id, contact_method=contact_method, system_generated=True)
                chainrun = True
            status = True
        elif (atable and (entity_type == 'deploy_alert')):
            aquery = (atable.pe_id == pe_id)
            recipients = db(aquery).select(ptable.pe_id, left=aleft)
            pe_ids = set((r.pe_id for r in recipients))
            pe_ids.discard(None)
            if pe_ids:
                for pe_id in pe_ids:
                    outbox.insert(message_id=message_id, pe_id=pe_id, contact_method=contact_method, system_generated=True)
                chainrun = True
            status = True
        else:
            row.update_record(status=4)
            db.commit()
            continue
        if status:
            row.update_record(status=2)
            db.commit()
        elif (row.retries > 0):
            row.update_record(retries=(row.retries - 1))
            db.commit()
        elif (row.retries is not None):
            row.update_record(status=5)
    if chainrun:
        self.process_outbox(contact_method)
