#  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 string
import rfc822
import mimetools
import time
import poplib
import mhlib
import os
from StringIO import StringIO

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

class POPSource(Source.RemoteSource):
  def __init__(self, serverName='', userName='', password='',
               port=poplib.POP3_PORT ):
    Source.RemoteSource.__init__(self, serverName, userName, password, port)
    self._debug = 0
    self._messages = None
    self._logged_in = 0
    self._delete_from_server = 0
    self._delete_on_delete = 0
    self._mailbox = None
    self._deleteOption = None
    self._storage_config = None
    self._last_downloaded_id = None
    self._use_apop = 0
    self._check_with_storage = 1
    self._storage_mailbox = None

    self.add_config_params(['delete_from_server',
                            'delete_on_delete',
                            'storage_config',
                            'last_downloaded_id',
                            'use_apop',
                            'check_with_storage',
                            ])
    
  def __setitem__(self, key, v):
    Source.Source.__setitem__(self, key, v)
    if key == 'storage_config':
      self._storage_mailbox = util.mailbox_from_config(self._storage_config)

  def __getitem__(self, key):
    if key == 'deleteOption':
      return self._storage_mailbox._source['deleteOption']
    return Source.Source.__getitem__(self, key)

  def fixup(self):
    # this gives us a chance to set the _storage_mailbox incase it was
    # not loaded from the condfig when we got loaded from the config
    if self._storage_config and not self._storage_mailbox:
      self['storage_config'] = self._storage_config

  def type_name(self):
    return 'POP3'

  def mailboxes(self):
    if not self._mailboxes:
      if self._storage_mailbox:
        self._mailbox = POPMailbox(self)
        self._mailboxes = {'POPMailbox': self._mailbox}
      else:
        self._ui.alert('The storage mailbox for %s does not exist, please select a new one' % self.name())
    return self._mailboxes and self._mailboxes.values() or None

  def login(self, force=0):
    if not self._logged_in or force:
      keeptrying = 1
      while keeptrying:
        password = self._password
        if not self._rememberPassword or not self._password and self._ui:
          password = self._ui.prompt_password(self)
          if not password:
            return 0
          if self._rememberPassword:
            self._password = password
        
        self._rpop = poplib.POP3(self._serverName, self._port)
        try:
          doplain = 1
          if self._use_apop:
            if self._rpop.timestamp.match(self._rpop.welcome) > 0:
              self._rpop.apop(self._userName, self._password)
              doplain = 0
            else:
              self._ui.alert('POP server does not support APOP, using plain text')
          if doplain:
            self._rpop.user(self._userName)
            self._rpop.pass_(self._password)
          keeptrying = 0
        except poplib.error_proto, x:
          self._ui.alert('Could not login to POP server %s: %s'
                         % (self.name(), x))
          keeptrying = 1
          self._password = None

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

    return 1

  def _pop(self, func, args = ()):
    try:
      data = apply(eval('self._rpop.%s' % func), args)
      return data or 1
    except poplib.error_proto, x:
      self._ui.alert('POP Error: %s' % x)
      return None
    

  def logout(self):
    if self._logged_in == 1:
      self.log('logging out')
      self._pop('quit')
      self._rpop = None
      self._logged_in = 0
    else:
      self._logged_in = self._logged_in-1

  def unseen_count(self, mailbox=None):
    return 0

  def message_count(self, mailbox=None):
    count = 0
    if self.login():
      count, size = self._pop('stat')
      self.logout()
    return count

  def __concat_lines(self, lines):
    newtxt = ''
    for line in lines:
      newtxt = newtxt + line + '\n'
    return newtxt


  def _delete_pop_message(self, id):
    self._pop('dele', (id,))

  def download_messages(self):
    messages = []

    #self._last_downloaded_id = None

    if not self._storage_mailbox:
      self._ui.alert('You must select a folder to store POP messages in')
      return messages
    
    if self.login():
      #list =  self._pop.list()
      cnt = self.message_count()
      bar = self._ui.start_progress('Downloading headers from %s' %
                                    self.name(), cnt)
      self._storage_mailbox.source().start_batch(self._storage_mailbox)
      found_last = not self._last_downloaded_id or 0
      trycount = 0
      while trycount < 2:
        for i in range(1, cnt+1):
          #m = self._pop.top(i, 0)
          #txt = self.__concat_lines(m[1])
          real_uid = self._pop('uidl', (i,))
          if not real_uid or real_uid[:3] != '+OK':
            self._ui.alert('POP server does not support uidl!!')
            return None
          real_uid = string.split(real_uid, ' ')[2]

          self.debug('found message with uid: %s' % real_uid)

          self._ui.update_progress(i, bar)
          if self._ui.progress_aborted(bar):
            self.debug('user abort')
            break

          if not self._delete_from_server \
             and self._last_downloaded_id and not found_last:
            if self._last_downloaded_id == real_uid:
              found_last = 1
            continue

          self.debug('adding message: %s' % real_uid)
          txt = self._get_body(None, i)
          rfc = mhlib.Message(None, 1, StringIO(txt))
          msg = POPMessage(self, self._mailbox, i, rfc, {})
          msg._header_is_full_body = 1
          newmsg = msg.append(self._storage_mailbox)
          if not newmsg:
            return messages
          messages.append(msg)
          
          self._last_downloaded_id = real_uid
          if self._delete_from_server:
            self._delete_pop_message(i)
        if found_last == 0 and cnt:
          # we didn't find the last one, so get them all
          trycount = trycount + 1
          found_last = 1
        else:
          trycount = 2
        
      self.logout()
      self._storage_mailbox.source().end_batch()
      self.debug('download_messages: seeya')
      self._ui.finish_progress(bar)
    return messages

    
  def _get_body(self, mailbox, uid, part_number=''):
    if self.login():
      res = self._pop('retr', (uid,))
      self.logout()
      return self.__concat_lines(res[1])
    return ''

  def delete_message(self, message):
    pass

  def save_message_cache(self):
    pass

  def expunge(self):
    self._source.expunge(self)
    def myfilt(msg):  return not msg.deleted()
    self._messages = filter(myfilt, self._messages)


class POPMailbox(Mailbox.Mailbox):
  def __init__(self, source):
    Mailbox.Mailbox.__init__(self, source, None)
    self._path = source._storage_mailbox.path()
    self._name = source._storage_mailbox.name()
    
    self.init_done()

  def config_name(self):
    return "POPMailbox" #only one per source anyway

  def check_mail(self, changes=None):
    Mailbox.Mailbox.check_mail(self, changes)
    msgs = self._source.download_messages()
    if changes:
      changes['new'] = msgs
    #if msgs and len(msgs):
    #  Plugin.call_plugins('new_mail_arrived', (msgs,))
    return len(msgs)
    
  def path(self):
    return self._path

  def expunge(self):
    self._source._storage_mailbox.expunge()

  def can_add_subfolder(self):
    return false

  def can_delete(self):
    return false

  def can_rename(self):
    return false

  def can_expunge(self):
    return self._source._storage_mailbox.can_expunge()

  def messages(self, is_selection=0):
    if is_selection and self._source['check_when_selected']:
      self.check_mail()
    return self._source._storage_mailbox.messages()

  def message_count(self):
    return self._source._storage_mailbox.message_count()

  def unseen_count(self):
    return self._source._storage_mailbox.unseen_count()

  def messages_loaded(self):
    return self._source._storage_mailbox.messages_loaded()

  def mailboxes(self):
    return []

  def message_with_id(self, id):
    return self._source._storage_mailbox.message_with_id(id)

  def save_message_cache(self, dir):
    pass

  def load_message_cache(self, dir):
    return 0



class POPMessage(Message.Message):
  def __init__(self, source, mailbox, id, hdrs=None, flags=None):
    Message.Message.__init__(self, source, mailbox, id, hdrs, flags)
    self._debug = 1
    #mailbox._uid_map[id] = self
    self._date = hdrs.getdate('date')
    self._flags = {}
    self.set_seen(0)
    if self._date:
      self._float_date = time.mktime(self._date)
    else:
      self._float_date = 0

  def get_parts(self):
    if self._parts == None:
      if not self._header_is_full_body:
        mhmsg = mhlib.Message(None, 1, StringIO(self.body()))
      else:
        mhmsg = self._headers
      self._parts = Message.make_mimeparts(mhmsg, self)
      if self._debug:
        self._parts._dump()
    return self._parts

  def body(self):
    if not self._body:
      self._body = self._source._get_body(self._mailbox, self._id)
    return self._body

  def load_all_headers(self):
    return self._headers

