#!/usr/bin/env python

import re
import imaplib
from email.parser import Parser
from email.utils import getaddresses
import dateutil.parser
import time


class ImapResultError(Exception):
    pass


def ImapResult(ret):
    result, data = ret
    if result != 'OK':
        raise ImapResultError(result)
    return data


class AddressField(object):
    def __init__(self, headers, fields):
        self.__names = set()
        self.__emails = set()
        self.__addresses = []

        if isinstance(fields, basestring):
            fields = (fields,)
        for field in fields:
            for i in getaddresses(headers.get_all(field, [])):
                if not i[0] and not i[1]:
                    continue
                self.__names.add(i[0].lower())
                self.__emails.add(i[1].lower())
                self.__addresses.append(i)

    def __contains__(self, items):
        if not isinstance(items, (list, tuple)):
            items = (items,)
        for item in items:
            if item.lower() in self.__emails:
                return True
            if item.lower() in self.__names:
                return True
        return False

    def __iter__(self):
        for name, email in self.__addresses:
            yield name, email

    def __len__(self):
        return len(self.__emails)

    def __str__(self):
        return ', '.join(['"%s" <%s>' % (n, e) for n, e in self.__addresses])


class Message(object):
    def __init__(self, imap, uid):
        self.__imap = imap
        self.__uid = uid
        self.__cache = {}

        data = ImapResult(self.__imap.uid('fetch', self.__uid, '(BODY.PEEK[HEADER])'))
        if data is None:
            raise ValueError('Message with UID %s not available' % self.__uid)
        raw_headers = data[0][1]

        self.__header_size = len(raw_headers)
        self.__headers = Parser().parsestr(raw_headers)

    def print_headers(self):
            print '  subject:  %s' % (self.subject,)
            print '  from:     %s' % (self.froms,)
            print '  to:       %s' % (self.tos,)
            if self.ccs:
                print '  cc:       %s' % (self.ccs,)
            print '  date:     %s' % (self.date,)
#            print '  received: %s' % (repr(self.received),)
            print '  uid:      %s' % self.uid

    def raw_email(self):
        data = ImapResult(self.__imap.uid('fetch', self.__uid, '(RFC822.PEEK)'))
        raw_email = data[0][1]
        return raw_email

    def raw_body(self, part=None, skip=0, take=0):
        if part is None:
            skip = self.__header_size + skip
        if take == 0:
            span = '<%d.>' % (skip,)
        else:
            span = '<%d.%d>' % (skip, take)
        if part is None:
            part = ''
        else:
            part = '[%d]' % part
        expr = '(BODY.PEEK%s%s)' % (part, span)
        resp = ImapResult(self.__imap.uid('fetch', self.__uid, expr))
        response, data = resp[0]
        reLen = re.compile('.*<(?P<start>\d+)> \{(?P<len>\d+)\}.*')
        m = reLen.match(response)
        len = int(m.group('len'))
        return data

    #decorator
    def _cache(func):  # pylint: disable=E0213
        def _cache_(self, *args, **kwargs):
            if func.__name__ in self.__cache:
                return self.__cache[func.__name__]
            value = func(self, *args, **kwargs)  # pylint: disable=E1102
            self.__cache[func.__name__] = value
            return value
        return _cache_

    @property
    @_cache
    def subject(self):
        return self.__headers['subject'].replace('\r\n', ' ').replace('\n\r', ' ').replace('\n', ' ').replace('  ', ' ')

    @property
    @_cache
    def date(self):
        return dateutil.parser.parse(self.__headers['date'])

    @property
    def uid(self):
        return self.__uid

    @property
    @_cache
    def froms(self):
        return AddressField(self.__headers, 'from')

    @property
    @_cache
    def tos(self):
        return AddressField(self.__headers, 'to')

    @property
    @_cache
    def ccs(self):
        return AddressField(self.__headers, 'cc')

    @property
    @_cache
    def bccs(self):
        return AddressField(self.__headers, 'bcc')

    @property
    @_cache
    def all_tos(self):
        return AddressField(self.__headers, ('to', 'cc', 'bcc'))

    @property
    @_cache
    def received(self):
        t = self.__headers['received']
        if t:
            t = t.split(";", 1)[1]
            t = t.lstrip()
            d = dateutil.parser.parse(t)
            return time.mktime(d.timetuple())

        t = self.__headers['date']
        if t:
            d = dateutil.parser.parse(t)
            return time.mktime(d.timetuple())

        print self.__headers.as_string()
        raise Exception("Can't establish delivery time")

    def move(self, folder):
        try:
            print "  --> %s" % folder
            data = ImapResult(self.__imap.uid('COPY', self.__uid, folder))
            data = ImapResult(self.__imap.uid('STORE', self.__uid, '+FLAGS', '(\Deleted)'))
            self.__imap.expunge()
            return True
        except ImapResultError, e:
            import sys
            print >> sys.stderr, '! %s' % e


def iter_mailbox(hostname, username, password, mailbox, since=None):
    from datetime import datetime
    since = since or datetime.now()  # Default to today - IMAP does not do hours and minutes

    m = imaplib.IMAP4_SSL(hostname)
    m.login(username, password)
    try:

        ImapResult(m.select(mailbox))
        data = ImapResult(m.uid('search', "SENTSINCE", since.strftime('"%d-%b-%Y"')))
        #data = ImapResult(m.uid('search', None, "ALL"))

        for email_uid in data[0].split():
            try:
                yield Message(m, email_uid)
            except Exception, e:
                import traceback
                import sys
                sys.stderr.write('Exception loading message: %s\n' % email_uid)
                traceback.print_exc()
    finally:
        m.close()
        m.logout()
