#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#  
#    http://www.apache.org/licenses/LICENSE-2.0
#    
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.

# Load media received from an IMAP inbox
from imaplib2 import IMAP4_SSL
from threading import Thread, Event
from email import message_from_string
import re
from base64 import b64encode, b64decode
from httplib import HTTPConnection, HTTPSConnection
from urlparse import urlparse
from util import *
from imgutil import *
from sys import stderr, argv, exit
from traceback import print_exc

# Debug print
def debug(*args):
    if True:
        print >> stderr, args

# Fetch an email and return the image it contains
def fetchmail(id, imap):

    # Extract address from the To header
    htyp, hdata = imap.fetch(id, '(BODY.PEEK[HEADER])')
    header = message_from_string(hdata[0][1])
    if header['To'] is None:
        return (None, 'Couldn\'t retrieve email address')
    b64aparts = re.findall('[\w=-_]+@[\w.]+', header['To'])
    if len(b64aparts) == 0:
        return (None, 'Couldn\'t parse email address')
    b64address = (b64aparts[len(b64aparts) - 1]).split('@')[0]
    try:
        address = b64decode((b64address.replace('-', '+').replace('_', '/') + '===')[0: len(b64address) + (len(b64address) % 4)])
    except:
        return (None, 'Couldn\'t decode email address')

    # Check if the address targets a picture (p/) or an icon (i/)
    if address[0:2] != 'p/' and address[0:2] != 'i/':
        return (None, 'Email address must start with p/ or i/')
    debug('putimages.py::readimage::address', address)

    # Extract image mime body part
    btyp, bdata = imap.fetch(id, '(BODY.PEEK[TEXT])')
    msg = message_from_string(hdata[0][1] + bdata[0][1])
    debug('putimages.py::readimage::msg', msg)
    parts = map(lambda p: p.get_payload(decode=True), filter(lambda p: p.get_content_type().startswith('image/'), msg.walk()))
    if len(parts) == 0:
        return (None, 'Email doesn\'t contain an image')

    # Convert image to a 50x50 JPEG image
    dataurl = bufto50x50jpeg(parts[0])

    # Return address, image url pair
    return (address, dataurl)

def putimage(address, dataurl, httpurl, httpuser, httppass):

    # Put image into the image database
    id = address.split('/')[1]
    token = address.split('/')[2]
    entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + \
        '<entry xmlns="http://www.w3.org/2005/Atom">\n' + \
        '<title type="text">' + id + '</title>\n' + \
        '<id>' + id + '</id>\n' + \
        '<content type="application/xml">\n' + \
        ('<picture>\n' if address[0:2] == 'p/' else '<icon>\n') + \
        '<image>' + dataurl + '</image>\n' + \
        '<token>' + token + '</token>\n' + \
        ('</picture>\n' if address[0:2] == 'p/' else '</icon>\n') + \
        '</content>\n' + \
        '</entry>'

    url = urlparse(httpurl)
    conn = HTTPSConnection(url.hostname, 443 if url.port is None else url.port) if url.scheme == 'https' else \
            HTTPConnection(url.hostname, 80 if url.port is None else url.port)
    #conn.set_debuglevel(9)
    path = url.path + ('pictures/' if address[0:2] == 'p/' else 'icons/') + id
    puturl = url.scheme + '//' + url.netloc + path
    debug('imapd.py::putimage::url', puturl)
    auth = b64encode("%s:%s" % (httpuser, httppass)).replace('\n', '')
    headers = { 'Authorization' : 'Basic ' + auth, 'X-Forwarded-Server' : url.hostname, 'Content-type': 'application/atom+xml', 'Accept': '*/*' }
    conn.request('PUT', path, entry, headers)
    response = conn.getresponse()
    if response.status != 200:
        debug('imapd.py::putimage::error', response.status, response.reason)
        return (None, 'Put error: ' + repr(response.status) + ' : ' + response.reason)
    conn.close()

    return (puturl, entry)

# Read and process an email
def processmail(id, imap, httpurl, httpuser, httppass):
    if id == '':
        return None

    # Read email and any image in it
    address, dataurl = fetchmail(id, imap)
    if address is None:
        # Mark email as seen if it doesn't contain an image
        debug('imapd.py::processmail::seen', id)
        imap.store(id, '+FLAGS', '\SEEN')
        return None

    # Put image into the database
    put = putimage(address, dataurl, httpurl, httpuser, httppass)
    if put[0] is None:
        return None

    # Mark email as seen if processed successfully
    debug('imapd.py::processmail::seen', id)
    imap.store(id, '+FLAGS', '\SEEN')
    return put[0]
    
# IMAP idle thread
def idle(imap, httpurl, httpuser, httppass, stop, stopped):
    try:
        sync = Event()
        while True:
            # Stop the thread
            if stop.isSet():
                debug('imapd.py::idle::stopped')
                stopped.set()
                return

            # Wait for changes
            def callback(args):
                debug('imapd.py::idle::callback')
                if not stop.isSet():
                    sync.set()
                    stop.set()

            debug('imapd.py::idle::waiting')
            imap.idle(callback = callback)
            stop.wait()

            # Handle email change event
            if sync.isSet():
                stop.clear()
                sync.clear()
                debug('imapd.py::idle::sync')

                # List unseen emails
                typ, data = imap.search(None, 'UNSEEN')
                debug('imapd.py::idle::search', typ, data)

                # Process unseen email
                map(lambda id: processmail(id, imap, httpurl, httpuser, httppass), data[0].split(' '))

    except Exception as e:
        debug('imapd.py::idle::except', e)
        print_exc()
        stopped.set()
        return

# Main processing loop
def main(imapurl, imapuser, imappass, httpurl, httpuser, httppass):
    try:
        # Connect and login
        url = urlparse(imapurl)
        imap = IMAP4_SSL(url.hostname, 993 if url.port is None else url.port)
        imap.login(imapuser, imappass)
        imap.select('Inbox')
        debug('imapd.py::main::connected')

        try:
            # Start imap idle thread
            stop = Event()
            stopped = Event()
            idling = Thread(target=idle, args=(imap, httpurl, httpuser, httppass, stop, stopped))
            idling.start()

            # List unseen emails
            typ, data = imap.search(None, 'UNSEEN')
            debug('imapd.py::main::search', typ, data)

            # Process unseen emails
            map(lambda id: processmail(id, imap, httpurl, httpuser, httppass), data[0].split(' '))

            # Wait 60 seconds
            debug('imapd.py::main::waiting')
            try:
                stopped.wait()
            except KeyboardInterrupt:
                pass

            # Stop the thread
            debug('imapd.py::main::stopping')
            stop.set()
            idling.join()

            # Close and logout
            debug('imapd.py::main::disconnecting')
            imap.close()
            imap.logout()
            return 0

        except Exception as e:
            debug('imapd.py::except', e)
            print_exc()
            # Close and logout
            imap.close()
            imap.logout()
            return 1

    except Exception as e:
        debug('imapd.py::except', e)
        print_exc()
        return 1

if __name__ == '__main__':
    exit(main(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]))

