#  PMail - GNOME/GTK/Python email client
#  Copyright (C) 2000 Scott Bender <sbender@harmony-ds.com>

#  This file is part of PMail.

#  PMail 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, or (at your option)
#  any later version.

#  PMail 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 GNU Emacs; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.  

import imaplib
import string
import rfc822
import mimetools
import time
import os
import socket
import re
import threading
import popen2
from imaplib import IMAP4
from StringIO import StringIO

from Base import *
from SearchCriteria import SearchCriteria
import Mailbox
import Source
import Message
import config
import util
import Plugin

#flag_fetch_re = re.compile(r'(?P<num>^\d+)\s+\(FLAGS\s* \((?P<flags>(\s*\\\w*)*)\)\s+UID\s+(?P<uid>\d+)')
flag_fetch_re = re.compile(r'(?P<num>^\d+)')
ns_res_re = re.compile(r'\((?P<pers>.*)\) \((?P<others>.*)\) \((?P<pub>.*)\)')
#namespace_re = re.compile(r'\((?P<name>"[^"]*")\s*(?P<sep>"[^"]*")\)')
namespace_re = re.compile(r'\("(?P<name>[^"]*)"\s*"(?P<sep>[^"]*)"\)')
mailbox_re = re.compile(r'\((?P<flags>(\s*\\\w*)*)\)\s+(?P<delim>[^ ]*)\s+(?P<name>.*)')
possibly_quoted_re = re.compile(r'"?([^"]*)"?')
status_re = re.compile(r'[^(]*\s+\(\w*\s+(?P<value>\d*)\)')
fetch_stage1_re = re.compile(r'(?P<num>\d*) \((?P<values>.*)')
uid_re = re.compile(r'UID (?P<uid>\d*)')
internal_date_re = re.compile(r'INTERNALDATE "(?P<date>[^"]*)"')
flags_re = re.compile(r'FLAGS \((?P<flags>[^\)]*)\)')
bodystructure_re = re.compile(r'BODYSTRUCTURE \((?P<structure>.*)')
bodypart_re = re.compile(r'"(?P<type>[^"]+)" "(?P<subtype>[^"]+)" (\((?P<params>[^)]*)\)|NIL) ((?P<id>"[^"]*"|NIL)) ((?P<desc>"[^"]*"|NIL)) (?P<encoding>"[^"]*") ((?P<size>\d+|NIL)) .*')
parameters_re = re.compile(r'"(?P<key>[^"]+)" "(?P<value>[^"]+)"')

class IMAPSource(Source.RemoteSource):
  def __init__(self, serverName='', userName='', password=None,
               port=imaplib.IMAP4_PORT):
    Source.RemoteSource.__init__(self, serverName, userName, password, port)
    #self._debug = 1
    self._logged_in = 0
    self._selectedMailbox = None

    #self._personalNameSpace = "INBOX."
    #self._othersNameSpace = "user."
    self._personalNameSpace = []
    self._othersNameSpace = []
    self._publicNameSpace = []
    self._showOnlySubscribed = 1
    self._deleteOption = Source.MARK
    self._serverOverrideNameSpace = 1
    
    self._lids = 0

    self.add_config_params(['personalNameSpace',
                            'othersNameSpace',
                            'publicNameSpace',
                            'showOnlySubscribed',
                            'deleteOption',
                            'serverOverrideNameSpace'])
    if self._debug:
      imaplib.Debug = self._debug

  def type_name(self):
    return 'IMAP'

  def fixup(self):
    #old config files could have a string instead of an array of stringsx
    if type(self._personalNameSpace) == type(""):
      self._personalNameSpace = [self._personalNameSpace]
    if type(self._othersNameSpace) == type(""):
      self._othersNameSpace = [self._othersNameSpace]
    if type(self._publicNameSpace) == type(""):
      self._publicNameSpace = [self._publicNameSpace]

  def is_selectable(self):
    return self._allow_select
  
  def check_mail(self):
    self.lock()
    try:
      self.mailboxes()
      box = self.mailbox_with_path('INBOX')
      res = 0
      if box: res = box.check_mail()

      if res and self._new_mail_command and len(self._new_mail_command):
        self._run_new_mail_command()
    finally:
      self.unlock()
    return res

  def can_add_subfolder(self):
    return true

  def message_creator(self, source, mailbox, id):
    return IMAPMessage(source, mailbox, id)

  def imap(self, func, args = (), ignore_errors=0):
    try:
      res, data = apply(eval('self._imap.%s' % func), args)
    except IMAP4.abort, x:
      #print 'Got IMAP4.abort: %s, reconnecting...' % x
      self.__login(1)
      if func != 'select' and self._selectedMailbox:
        self.__select(self._selectedMailbox)
      res, data = apply(eval('self._imap.%s' % func), args)
    except IMAP4.error, x:
      if not ignore_errors:
        self._ui.alert('IMAP Error: %s' % x)
      return None

    if res != 'OK':
      if not ignore_errors:
        self._ui.alert('IMAP Error: %s' % data[0])
      data = None
    
    return data


  def _get_namespace(self):
    if 'NAMESPACE' in self._imap.capabilities \
       and self._serverOverrideNameSpace:
      data =  self.imap('namespace')
      if data:
        res = ns_res_re.match(data[0])
        if res:

          def parse_namespace(str):
            res = []
            match = namespace_re.match(str)
            while match:
              res.append(match.group('name'))
              start, end = match.span()
              str = str[end:]
              match = namespace_re.match(str)
            return res
          
          self._personalNameSpace = parse_namespace(res.group('pers'))
          self._othersNameSpace = parse_namespace(res.group('others'))
          self._publicNameSpace = parse_namespace(res.group('pub'))

  def __login(self, force=0):
    if not self._logged_in or force:
      keeptrying = 1

      if not self._userName or not len(self._userName):
        self._ui.alert('Please enter a user name for "%s" in preferences'
                       % self.name())
        return 0
      
      while keeptrying:
        password = self._password
        if not self._rememberPassword or not self._password:
          password = self._ui.prompt_password(self)
          if not password:
            return 0
          if self._rememberPassword:
            self._password = password
        
        try:
          self._imap = IMAP4(self._serverName, self._port)
          self._imap.login('"' + self._userName + '"',
                           '"' + password + '"')
          keeptrying = 0
        except IMAP4.error, x:
          self._ui.alert('could not login to IMAP server: %s (%s)'
                         % (self.name(), x))
          keeptrying = 1
          self._password = None
        except socket.error, x:
          self._ui.alert('could not login to IMAP server: %s (%s)'
                         % (self.name(), x))
          keeptrying = 1
          self._password = None

      self._logged_in = 1
      self.log('logged in to IMAP server {%s:%d}' %
               (self._serverName, self._port))

      self._get_namespace()
      
    return 1

  def __get_section(self, s, look_for_start = 1):

    self.debug('skipping section: %s' % s)

    res = ''
    if look_for_start:
      idx = string.index(s, '(')
      idx = idx + 1
      res = '('
    else:
      idx = 0

    depth = 1
    while depth != 0 and idx < len(s):
      if s[idx] == ')':
        depth = depth - 1
      elif s[idx] == '(':
        depth = depth + 1
      res = res + s[idx]
      idx = idx + 1
      
    self.debug('skipped section: %s' % (res))
    
    return res, s[idx:]

  def __start_of_section(self, s):
    idx = string.index(s, '(')
    return s[idx:]

  def __get_string(self, s):

    self.debug('looking for string in %s' % s)

    idx = 0
    while s[idx] == ' ':
      idx = idx + 1

    if s[idx:idx+3] == 'NIL':
      self.debug('found string NIL')
      return None, s[idx+4:]

    res = ''
    try:
      idx = string.index(s, '"') #check result
    except ValueError:
      return '', s
    
    idx = idx + 1
    while s[idx] != '"':
      res = res + s[idx]
      idx = idx + 1

    rest = s[idx+1:]
    self.debug("found string \'%s\'" % (res))
    return res, rest

  def __get_string_unquoted(self, s):

    self.debug('looking for unquoted string in %s' % s)

    if s[:3] == 'NIL':
      return None, s[:4]

    idx = 0
    while idx < len(s) and s[idx] not in string.letters and s[idx] not in string.digits:
      idx = idx + 1
      
    res = ''
    while idx < len(s) and s[idx] != ' ':
      res = res + s[idx]
      idx = idx + 1

    rest = s[idx+1:]
    self.debug("found unquoted string \'%s\', (rest: %s)" % (res, rest))
    return res, rest

  def __get_number(self, s):

    self.debug('looking for number in %s' % s)

    if s[:3] == 'NIL':
      return None, s[:4]

    idx = 0
    while s[idx] not in string.digits:
      idx = idx + 1

    res = ''
    while s[idx] in string.digits:
      res = res + s[idx]
      idx = idx + 1

    rest = s[idx+1:]
    self.debug("found number \'%s\'" % (res))
    return string.atoi(res), rest

  def __add_mailboxes(self, data):
    for item in data:
      if item:

        match = mailbox_re.match(item)

        if not match:
          self.log_error('parsing mailbox: %s' % item)
          continue

        delim = possibly_quoted_re.match(match.group('delim')).group(1)
        path = possibly_quoted_re.match(match.group('name')).group(1)
        flags = self.__parse_flags(match.group('flags'))
        allow_inferior = not flags.get('noinferiors')
        
        this_name = path
        idx = -1
        if delim:
          idx = string.find(path, delim)
          if idx != -1:
            this_name = path[:idx]

        box = self._mailboxes.get(this_name)
        if not box:
          box = IMAPMailbox(self, this_name, delim, None)
          self._mailboxes[box.name()] = box
        box._allow_inferiors = allow_inferior
        box._allow_select = not flags.get('noselect')

        if idx != -1:
          def creator(source, path, delim, parent):
            return IMAPMailbox(source, path, delim, parent)
          box.create_mailboxes(path, idx+1, delim, self, creator)

  def mailboxes(self):
    self.lock()
    try:
      if self._mailboxes == None and self.__login():
        self._mailboxes = {}

        data = self.imap('list', ("", 'INBOX'))
        if data:
          self.__add_mailboxes(data)

          if self._showOnlySubscribed:
            func = 'lsub'
          else:
            func = 'list'

          def do_namespace(self, func, ns_list):
            for space in ns_list:
              data = self.imap(func, ("", space + '*'))
              self.__add_mailboxes(data)

          do_namespace(self, func, self._personalNameSpace)
          do_namespace(self, func, self._publicNameSpace)
          do_namespace(self, func, self._othersNameSpace)

      res = None
      if self._mailboxes:
        res = self._mailboxes.values()
    finally:
      self.unlock()
    return res

  
  def __status(self, mailbox, name):
    self.lock()
    res = 0
    try:
      data = self.imap('status', (mailbox.path(), '(%s)' % name),
                       ignore_errors=1)
      if data:
        match = status_re.match(data[0])
        if match:
          res = string.atoi(match.group('value'))
        else:
          self.log_error('parsing status: %s' % data[0])
    finally:
      self.unlock()
    return res

  def message_count(self, mailbox):
    return self.__status(mailbox, 'MESSAGES')

  def unseen_count(self, mailbox):
    return self.__status(mailbox, 'UNSEEN')

  def __select(self, mailbox):
    data = self.imap('select', (mailbox.path(),))
    if data:
      self._selectedMailbox = mailbox
      return string.atoi(data[0])
    else:
      return 0

  def __convertInternalDate(self, datestr):
    #s = 'INTERNALDATE "%s"' % datestr
    res =  imaplib.Internaldate2tuple(datestr)
    return res

  def __parse_fetch(self, s):
    res = {}
    while len(s):
      part, s = self.__get_string_unquoted(s)
      if part == 'FLAGS':
        flagstr, s = self.__get_section(s)
        flagstr = flagstr[1:-1]
        flag, flagstr = self.__get_string_unquoted(flagstr)
        flags = {}
        res['flags'] = flags
        while flag and len(flag):
          flag = string.lower(flag)
          flags[flag] = 1
          flag = None
          if len(flagstr):
            flag, flagstr = self.__get_string_unquoted(flagstr)
      elif part == 'UID':
        uid, s = self.__get_number(s)
        res['uid'] = uid
      elif part == 'INTERNALDATE':
        date, s = self.__get_string(s)
        res['date'] = self.__convertInternalDate(date)
        
      #elif part == 'BODY':
      #  done = 1
    return res
  

  def _get_message(self, mailbox, data):
    #print data
    if len(data) == 2:
      fetchs, headers = data
      match = fetch_stage1_re.match(fetchs)

      msg = match.group('num')

      values = match.group('values')
      uid = uid_re.search(values)
      flags = flags_re.search(values)
      date = internal_date_re.search(values)
      if not values or not flags or not date or not uid:
        self.log_error('parsing message: "%s"' % values)
        return None

      uid = string.atoi(uid.group('uid'))
      flags = self.__parse_flags(flags.group('flags'))
      date = self.__convertInternalDate(date.group())
      
      sio = StringIO(headers)
      rfc = rfc822.Message(sio)

      m = IMAPMessage(self, mailbox, uid, msg, rfc, flags, date)
      return m
    return None

  def _get_messages(self, mailbox, range = None):
    nmsgs = self.__select(mailbox)
    if nmsgs == 0:
      return []
    list = []
    fetchs = '(FLAGS UID INTERNALDATE BODY.PEEK[HEADER.FIELDS (SUBJECT FROM)])'
    if not range:
      range = "1:*"
    else:
      nmsgs = len(string.split(range, ','))
      
    bar = self._ui.start_progress('Downloading headers from %s' % self.name(),
                                  nmsgs)
    self.debug('get_messages: %s' % range)
    data = self.imap('uid', ('FETCH', range, fetchs))
    if data:
      count = 1
      self.debug(data)
      for m in data:
        if self._ui.progress_aborted(bar):
          break
        message = self._get_message(mailbox, m)
        if message:
          list.append(message)
          self._ui.update_progress(count, bar)
          count = count + 1
      self._ui.finish_progress(bar)
    return list

  def __parse_flags(self, flags):
    res = {}
    for f in string.split(flags, ' '):
      res[string.lower(f[1:])] = 1
    return res

  def _sync_messages(self, mailbox):
    plen = self.__select(mailbox)

    if mailbox._messages == None:
      if not mailbox.load_message_cache(self.cache_dir()):
        mailbox._messages = []

    self.debug('sync_messages: me: %d messages, server: %d messages'
               % (len(mailbox._messages), plen))

    plen = plen + len(mailbox._messages)
    pcount = 0

    bar = self._ui.start_progress('Checking mail in %s(%s):' %
                                  (self.name(), mailbox.path()),
                                  plen)

    data = self.imap('uid', ('FETCH', '1:*', '(UID FLAGS)'))

    if data:
      new_nums = ''
      changed = []
      deleted = []
      new = None
      uids = {}
      if data[0] != None:
        for line in data:
          if self._ui.progress_aborted(bar):
            break

          num_m = flag_fetch_re.match(line)
          flags_m = flags_re.search(line)
          uid_m = uid_re.search(line)
          
          if not num_m or not flags_m or not uid_m:
            self.log_error('parsing fetch result: "%s"' % line)
            continue

          num = num_m.group('num')
          flags = self.__parse_flags(flags_m.group('flags'))
          uid = string.atoi(uid_m.group('uid'))
          
          msg = mailbox._uid_map.get(uid)
          uids[uid] = flags
          if not msg:
            new_nums = new_nums + '%d,' % uid
          else:
            if flags != msg._flags:
              msg._flags = flags
              changed.append(msg)
          self._ui.update_progress(pcount, bar)
          pcount = pcount + 1

      self.debug('sync_messages: I parsed %d messages' % pcount)

      rmcount = 0
      msgs = mailbox._messages
      for msg in msgs:
        if self._ui.progress_aborted(bar):
          break
        
        if not uids.has_key(msg.id()):
          deleted.append(msg)
          rmcount = rmcount + 1
        if self._ui:
          self._ui.update_progress(pcount, bar)
          pcount = pcount + 1

      self.debug('sync_messages: I found %d removed messages' % rmcount)

      self._ui.finish_progress(bar)

      if len(new_nums):
        new_nums = new_nums[:-1] #take off the last comma7
        new = self._get_messages(mailbox, new_nums)

      return deleted, changed, new
    return None, None, None

  def _all_headers(self, mailbox, uid):
    len = self.__select(mailbox)
    fetchs = '(BODY.PEEK[HEADER])'
    data = self.imap('uid', ('FETCH', uid, fetchs))
    if data:
      i, headers = data[0]
      return rfc822.Message(StringIO(headers))
    return None

  def __parse_parameters(self, str):
    res = {}
    if not str:
      return res

    match = parameters_re.match(str)
    while match:
      res[string.lower(match.group('key'))] = match.group('value')
      start, end = match.span()
      str = str[end:]
      match = parameters_re.match(str)
      
    return res

  def __get_part(self, message, s, next_number = None):
    part = Message.MimePart(message)
    part._number = next_number

    self.debug('__get_part()...')
    
    if s[0] == '(':
      self.debug('__get_part: found a multipart')
      part._type = 'multipart'
      part._data = []
      num = 1
      while len(s) and s[0] == '(':
        self.debug('try one')
        if not next_number:
          snum = '%d' % num
        else:
          snum = '%s.%d' % (next_number, num)
        sub, s = self.__get_part(message, s[1:], snum)
        self.debug('back from __get_part')
        part._data.append(sub)
        num = num + 1
      if not len(s):
        return None, None
      part._subtype, s = self.__get_string(s)
      skip, s = self.__get_section(s, 0)
      self.debug('done with multipart')
    else:
      self.debug('__get_part: found a single part')      
      if not next_number:
        part._number = '1'
      match = bodypart_re.match(s)

      def _check_nil(val):
        if val: return possibly_quoted_re.match(val).group(1)
        return None
      
      part._type = match.group('type')
      part._subtype = match.group('subtype')
      part._parameters = self.__parse_parameters(match.group('params'))
      part._id = _check_nil(match.group('id'))
      part._description = _check_nil(match.group('desc'))
      part._encoding = possibly_quoted_re.match(match.group('encoding')).group(1)
      part._encoding = string.lower(part._encoding)
      part._size = string.atoi(match.group('size'))
      self.debug('__get_part: found a part(%s/%s)' % (part._type, part._subtype))
      skip, s = self.__get_section(s[match.span('size')[1]:], 0)
    part._type = string.lower(part._type)
    part._subtype = string.lower(part._subtype)      
    return part, s

  def _get_parts(self, mailbox, message):
    self.__select(mailbox)
    data = self.imap('uid', ('FETCH', message.id(), '(BODYSTRUCTURE)'))
    res = None
    if data:
      self.debug('BODYSTRUCTURE:')
      parts = []
      s = data[0]
      if not s:
        self._ui.alert('The selected message no longer exists')
        return None

      if type(s) == type(()):
        s = s[0]

      match = bodystructure_re.search(s)
      if not match:
        self.log_error('parsing BODYSTRUCTURE: "%s"' % s)
        return None
      
      s = match.group('structure')
      self.debug('get_parts: getting parts for %s[%d]' % \
                 (mailbox.path(), message.id()))
      self.debug('get_parts: calling __get_part...')            
      res, s = self.__get_part(message, s)
      self.debug('back from get_parts')

    return res

  def _get_body(self, mailbox, uid, part_number=''):
    self.__select(mailbox)
    data = self.imap('uid', ('FETCH', uid, '(BODY[%s])' % part_number))
    if data:
      try:
        i, t = data[0]
      except ValueError:
        t = ''
      return t
    return None

  def delete_message(self, message):
    self.lock()
    try:
      self.imap('uid', ('STORE', message.id(), '+FLAGS', '(\Deleted)'))
    finally:
      self.unlock()

  def undelete_message(self, message):
    self.lock()
    try:
      self.imap('uid', ('STORE', message.id(), '-FLAGS', '(\Deleted)'))
    finally:
      self.unlock()

  def mark_answered(self, message):
    self.lock()
    try:
      self.imap('uid', ('STORE', message.id(), '+FLAGS', '(\Answered)'))
    finally:
      self.unlock()

  def mark_flagged(self, message, val):
    self.lock()
    try:
      s = val == 1 and '+' or '-'
      self.imap('uid', ('STORE', message.id(), s+'FLAGS', '(\Flagged)'))
    finally:
      self.unlock()

  def mark_seen(self, message, val):
    self.lock()
    try:
      s = val == 1 and '+' or '-'
      self.imap('uid', ('STORE', message.id(), s+'FLAGS', '(\Seen)'))
    finally:
      self.unlock()

  def _expunge(self, mailbox):
    self.__select(mailbox)
    self.imap('expunge')

  def __newlines_to_crnl(self, text):
    newt = ''
    if text:
      for i in range(len(text)):
        c = text[i]
        if c == '\n' and i > 0 and text[i-1] != '\r':
          newt = newt + '\r'
        newt = newt + c
    return newt

  def __flags_string(self, flags, add=None):
    str =  '('
    if add:
      str = str + add
    if flags:
      for key, val in flags.items():
        if val:
          if len(str) > 1:
            str = str + ' '
          str = str + '\\' + string.upper(key[:1]) + key[1:]
    str = str + ')'
    return str

  def append_message(self, mailbox, message):
    Source.Source.append_message(self, mailbox, message)
    self.lock()
    try:
      if message._source == self:
        self.__select(message._mailbox)
        res =  self.imap('uid', ('COPY', message.id(), mailbox.path()))
      else:
        flags = self.__flags_string(message._flags, '\Seen')
        res =  self.imap('append', (mailbox.path(), flags, None,
                                   self.__newlines_to_crnl(message.full_body())))
      return res
      #if res:
      # newmsg = self._get_messages(mailbox, [
    finally:
      self.unlock()

  def create_subfolder(self, name, mailbox=None):
    self.lock()
    try:
      delim = mailbox and mailbox._delimiter or None
      if mailbox:
        name = mailbox.path() + delim + name
      if self.imap('CREATE', (name,)):
        newbox = IMAPMailbox(self, name, delim)
        self.subscribe(newbox.path())
        b = mailbox or self
        if delim:
          try:
            idx = string.rindex(name, delim)
            name = name[idx+1:]
          except ValueError:
            pass
        b._mailboxes[name] = newbox
    finally:
      self.unlock()

  def delete_subfolder(self, mailbox):
    self.lock()
    try:
      self.unsubscribe(mailbox.path())
      if self.imap('DELETE', (mailbox.path(),)):
        if mailbox._parent:
          mailbox._parent.remove(mailbox)
        else:
          del self._mailboxes[mailbox.name()]
    finally:
      self.unlock()

  def subscribe(self, name):
    self.lock()
    try:
      self.imap('SUBSCRIBE', (name,))
    finally:
      self.unlock()

  def unsubscribe(self, name):
    self.lock()
    try:
      self.imap('UNSUBSCRIBE', (name,))
    finally:
      self.unlock()
    

  def rename(self, mailbox, newname):
    self.lock()
    try:
      if self.imap('RENAME', (mailbox.path(), newname)):
        self.subscribe(newname)
        #i know, yuk, but just cause everything to be reloaded
        self.save_message_cache()
        self._mailboxes = None
    finally:
      self.unlock()


  __cnc = [SearchCriteria.CONTAINS,  SearchCriteria.NOT_CONTAINS]
  __gl = [SearchCriteria.GREATER_THAN, SearchCriteria.LESS_THAN]
  __gle = __gl + [SearchCriteria.EQUALS]
  __isnot = [SearchCriteria.IS, SearchCriteria.NOT]
  __none = []
  __any = 'any'
  __string = 'string'
  __date = 'date'
  __number = 'number'
  __search_map = { \
                'BODY': ['BODY', __cnc, __any], \
                'SENDER': ['FROM', __cnc, __any], \
                'BCC': ['BCC', __cnc, __any], \
                'CC': ['CC', __cnc, __any],
                'SUBJECT': ['SUBJECT', __cnc, __any], \
                'DATE': [None, __gle, __date], \
                'INTERNAL DATE': [None, __gle, __date], \
                'SIZE': [None, __gl, __number], \
                'TO': ['TO', __cnc, __any], \
                'DELETED': ['DELETED', __isnot, None], \
                'FLAGGED': ['FLAGGED', __isnot, None], \
                'SEEN': ['SEEN', __isnot, None], \
                'ANSWERED': ['ANSWERED', __isnot, None], \
                'KEYWORD' : ['KEYWORD', __isnot, __any], \
                'DRAFT' : ['DRAFT', __isnot, None], \
                'UID' : ['UID', __isnot, __any],                
                }


  def get_search_posibilities(self):
    return self.__search_map
  
  def search(self, mailbox, criteria):
    self.lock()
    try:
      self.__select(mailbox)

      str = ''
      ops = criteria.operations()
      for i in range(len(ops)):
        if len(str):
          str = str + ' '
        item = ops[i]
        next = i+1 < len(ops) and ops[i+1] or None
        if next and next.get('bool') == SearchCriteria.OR:
          str = str + 'OR '

        op = item.get('operator')
        val = item.get('value')

        if op == SearchCriteria.NOT_CONTAINS \
           or op == SearchCriteria.NOT_EQUALS \
           or op == SearchCriteria.NOT:
          str = str + 'NOT '

        f = item.get('field')

        map = self.__search_map.get(f)
        key = map[0]

        if not key:
          if f == 'INTERNAL DATE' or f == 'DATE':
            if op == SearchCriteria.EQUALS \
               or op == SearchCriteria.NOT_EQUALS:
              s = 'ON'
            elif op == SearchCriteria.LESS_THAN:
              s = 'BEFORE'
            elif op == SearchCriteria.GREATER_THAN:
              s = 'SINCE'

            if f == 'DATE':
              s = 'SENT'+s
            key =  s
          elif f == 'SIZE':
            if op == SearchCriteria.GREATER_THAN:
              key = 'LARGER'
            else:
              key = 'SMALLER'
          else:
            key = 'HEADER ' + f

        str = str + key;

        if map[2]:
          if map[2] == self.__date and type(val) == type(()):
            val = time.strftime('%d-%b-%Y', val)

          if val and len(val):
            str = str + ' ' + val

      #print 'search string:', str

      data = self.imap('UID', ('SEARCH', None, '(' + str + ')'))
      res = []
      if data and len(data) and data[0]:
        res = []
        nums = string.split(data[0], ' ')
        notf = []
        for uid in nums:
          msg = mailbox._uid_map.get(string.atoi(uid))
          #FIXME: load messages we don't have
          if msg:
            res.append(msg)
          else:
            notf.append(uid)
        if len(notf):
          msgs = self._get_messages(mailbox, string.join(notf, ','))
          res = res + msgs
    finally:
      self.unlock()
    return res
      

class IMAPMessage(Message.Message):
  def __init__(self, source, mailbox, id, seq_num=None,
               hdrs=None, flags=None, date=None):
    Message.Message.__init__(self, source, mailbox, id, hdrs, flags)
    #self._debug = 1
    #self._seq_num = seq_num
    mailbox._uid_map[id] = self
    #mailbox._seq_num_map[seq_num] = self
    self._date = date
    if date:
      try:
        self._float_date = time.mktime(date)
      except ValueError,x:
        self.log_error('ValueError in date \'%s\': %s' % (self._date, x))
        self._float_date = 0
        
    self._headers_loaded = 0

  def __copy_to_trash(self):
    if self._source['deleteOption'] == Source.TRASH:
      box = util.get_mailbox_config_value('trash_box')
      if box != self._mailbox:
        self.append(box)

  def delete(self):
    self.lock()
    try:
      if not self.deleted():
        self.__copy_to_trash()
        self._source.delete_message(self)
        self._flags['deleted'] = 1
        if self._source['deleteOption'] == Source.REMOVE:
          self._mailbox.expunge()
      else:
        self._source.undelete_message(self)
        self._flags['deleted'] = 0
    finally:
      self.unlock()

  def set_answered(self):
    self.lock()
    try:
      if not self.answered():
        Message.Message.set_answered(self)
        self._source.mark_answered(self)
    finally:
      self.unlock()

  def set_flagged(self, val):
    self.lock()
    try:
      Message.Message.set_flagged(self, val)
      self._source.mark_flagged(self, val)
    finally:
      self.unlock()

  def set_seen(self, val):
    self.lock()
    try:
      Message.Message.set_seen(self, val)
      self._source.mark_seen(self, val)
    finally:
      self.unlock()
    
  def get_parts(self):
    self.lock()
    try:
      if self._parts == None:
        self._parts = self._source._get_parts(self._mailbox, self)
        if self._debug:
          self._parts._dump()
    finally:
      self.unlock()
    return self._parts
    

  def body(self):
    self.lock()
    try:
      if not self._body:
        self._body = self._source._get_body(self._mailbox, self._id, 1)
    finally:
      self.unlock()
    return self._body

  def load_all_headers(self):
    self.lock()
    try:
      if not self._headers_loaded:
        self._headers = self._source._all_headers(self._mailbox, self._id)
    finally:
      self.unlock()
    return self._headers

  def cache_value(self):
    c = Message.Message.cache_value(self)
    c['date'] = self._date
    return c

  def set_cache_value(self, c):
    Message.Message.set_cache_value(self, c)
    self._date = c['date']
    self._float_date = time.mktime(self._date)    

class IMAPMailbox(Mailbox.Mailbox):
  def __init__(self, source, path, delim, parent = None):
    Mailbox.Mailbox.__init__(self, source, parent)
    self._path = path
    self._uid_map = {}
    self._allow_inferiors = 1
    self._delimiter = delim

    self._name = path
    if delim:
      try:
        idx = string.rindex(path, delim)
        self._name = path[idx+1:]
      except ValueError:
        pass

    self.init_done()

  def path(self):
    return self._path

  def max_uid(self):
    return max(self._uid_map.keys())

  def check_mail(self, changes=None):
    Mailbox.Mailbox.check_mail(self, changes)
    res = 0

    self.lock()
    try:
      deleted, changed, new = self._source._sync_messages(self)

      if deleted and len(deleted):
        for msg in deleted:
            self._messages.remove(msg)
            del self._uid_map[msg.id()]
        res = 1

      if new and len(new):
        self._messages = new + self._messages
        res = 1

      if changed and len(changed):
        res = 1

      if changes:
        changes['new'] = new
        changes['deleted'] = deleted
        changes['changed'] = changed

      if new and len(new):
        Plugin.call_plugins('new_mail_arrived', (new,))
    finally:
      self.unlock()
    return res

  def expunge(self):
    self.lock()
    try:
      self._source._expunge(self)
      def myfilt(msg):  return not msg.deleted()
      self._messages = filter(myfilt, self._messages)
    finally:
      self.unlock()

  def is_inbox(self):
    return self._path == "INBOX"

  def can_add_subfolder(self):
    return self._allow_inferiors

  def can_delete(self):
    return not self.is_inbox()

  def can_rename(self):
    return not self.is_inbox()

  def can_expunge(self):
    return true


