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

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

class MHSource(Source.LocalStorageSource):
  def __init__(self, directory=None):
    Source.LocalStorageSource.__init__(self)
    self._debug = 0

    self._directory = directory
    self._deleteOption = Source.MARK
    self._deleted_sequence_key = config.app_name + 'deleted'
    self._check_when_selected = 1

    self.add_config_params(['directory', 'deleteOption'])

  def can_add_subfolder(self):
    return true

  def can_delete(self):
    return false

  def can_rename(self):
    return false

  def can_expunge(self):
    return false

  def name(self):
    return not self._display_name and \
           '{MH}%s' % self._directory \
           or self._display_name

  def _load(self):
    try:
      p = os.path.expanduser(self._directory)      
      if not os.path.exists(p):
        os.mkdir(p)
        os.mkdir(os.path.join(p, 'inbox'))
      
      self._rmh = MH(self._ui, self._directory)
    except mhlib.Error, x:
      self._ui.alert('MH Mailbox Error: %s' % x)
      return 0

    self._unseen_seq_key = self._mh('getprofile', ('unseen-sequence',))
    if not self._unseen_seq_key or type(self._unseen_seq_key) != type(""):
      self._unseen_seq_key = 'unseen'
    return 1
    

  def _mh(self, func, args = ()):
    try:
      data = apply(eval('self._rmh.%s' % func), args)
      return data or 1
    except mhlib.Error, x:
      self._ui.alert('MH Mailbox Error: %s' % x)
      return None

  def mailboxes(self):
    if not self._mailboxes and self._load():
      self._mailboxes = {}
      self._mailbox_delimiter = os.sep
      folders = self._mh('listallfolders')
      if folders and type(folders) == type([]): 
        for path in folders:
          idx = string.find(path, self._mailbox_delimiter)
          this_name = path
          if idx != -1:
            this_name = path[:idx]

          box = self._mailboxes.get(this_name)
          if not box:
            box = MHMailbox(self, path, self._mailbox_delimiter)
            self._mailboxes[box.name()] = box

          if idx != -1:
            def creator(source, path, delim, parent):
              return MHMailbox(source, path, delim, parent)
            box.create_mailboxes(path, idx+1, self._mailbox_delimiter,
                                 self, creator)
            
    return self._mailboxes and self._mailboxes.values() or None

  def append_message(self, mailbox, message):
    Source.Source.append_message(self, mailbox, message)
    last = mailbox._get_last()
    
    if message._source == self:
      if message._mailbox != mailbox:
        res, data = message._mailbox._folder('copymessage', (message.id(),
                                                       mailbox._rfolder,
                                                       last+1))
    else:
      sio = StringIO(message.full_body())
      res, data = mailbox._folder('createmessage', (last+1, sio))

    if res:
      newmessage = MHMessage(self, mailbox, last+1, None, message._flags)
      newmessage._flags['deleted'] = 0 #ignore deleted flag
      #self.add_message(mailbox, newmessage)

      if self._inbatch:
        self._new_messages.append(newmessage)
      
      if mailbox._get_last() == last:
        #bug in mhlib??, cause to recalc
        mailbox._folder('setlast', (None,))

    if not self._inbatch:
      self._write_flags([newmessage])
      
    return res
      
  def add_message(self, mailbox, message):
    if not mailbox._messages:
      mailbox._messages = []
    mailbox._messages.append(message)
    if self._inbatch:
      self._new_messages.append(message)

  def _write_flags(self, messages):
    messages_bybox = {}
    for message in messages:
      box = message.mailbox()        
      seq = messages_bybox.get(box) or []
      messages_bybox[box] = seq
      seq.append(message)

    for box, messages in messages_bybox.items():
      res, sequences = box._folder('getsequences', ())
      
      unseen_seq = sequences.get(self._unseen_seq_key) or []
      sequences[self._unseen_seq_key] = unseen_seq

      deleted_seq = sequences.get(self._deleted_sequence_key) or []
      sequences[self._deleted_sequence_key] = deleted_seq

      #remove any seen messages that are in the unseen_seq
      seen_seq = filter_map(lambda x: x.seen() and x.id(), messages)
      subtract_lists(unseen_seq, seen_seq)

      #add any unseen messages that are not in the unseen
      new_unseen = filter_map(lambda x: not x.seen() and x.id(), messages)
      add_lists(unseen_seq, new_unseen)

      #add any deleted messages that are not in the deleted_seq
      deleted = filter_map(lambda x: x.deleted() and x.id(), messages)
      add_lists(deleted_seq, deleted)

      #remove any not deleted messages that are in the deleted_seq
      not_deleted = filter_map(lambda x: not x.deleted() and x.id(), messages)
      subtract_lists(deleted_seq, not_deleted)

      box._folder('putsequences', (sequences,))

  def start_batch(self, mailbox=None):
    Source.Source.start_batch(self, mailbox)
    self._new_messages = []
    
  def end_batch(self):
    if self._batch_mailbox:
      if self._new_messages:
        self._write_flags(self._new_messages)
        
    Source.Source.end_batch(self)
    self._new_messages = None

  def expunge(self, mailbox):
    pass

  def _really_delete_message(self, message):
    message._mailbox._folder('removemessages', ([message.id()],))
    message._mailbox._messages.remove(message)
    del message._mailbox._uid_map[message.id()]
    
      
  def delete_message(self, message):
    if self['deleteOption'] == Source.REMOVE:
      self._really_delete_message(message)
    else:
      self._write_flags([message])

  def undelete_message(self, message):
    self._write_flags([message])

  def unseen_count(self, mailbox):
    res, seq = mailbox._folder('parsesequence', (self._unseen_seq_key,), 1)
    return res and len(seq) or 0

  def message_count(self, mailbox):
    res, data = mailbox._folder('listmessages')
    return res and len(data) or 0

  def create_subfolder(self, name, mailbox=None):
    if mailbox:
      name = mailbox.path() + self._mailbox_delimiter + name
    if self._mh('makefolder', (name,)):
      newbox = MHMailbox(self, name, self._mailbox_delimiter)
      b = mailbox or self
      try:
        idx = string.rindex(name, self._mailbox_delimiter)
        name = name[idx+1:]
      except ValueError:
        pass
      b._mailboxes[name] = newbox

  def _get_messages(self, mailbox):
    mailbox._messages = []
    res, msgs = mailbox._folder('listmessages')

    bar = self._ui.start_progress('Getting messages from %s'
                                  % mailbox.name(), len(msgs))
    cnt = 0
    messages = []
    for id in msgs:
      msg = MHMessage(self, mailbox, id)
      mailbox._uid_map[id] = msg
      messages.append(msg)
      self._ui.update_progress(cnt, bar)
      cnt = cnt + 1
    self._ui.finish_progress(bar)
    return messages

  def sync_messages(self, mailbox):
    res, msgs = mailbox._folder('listmessages')

    self.debug('sync_messages: %s' % msgs)

    if mailbox._messages == None:
      mailbox._messages = []
    
    msglen = mailbox._messages and len(mailbox._messages) or 0
    msglist = mailbox._messages and list(mailbox._messages) or None
    bar = self._ui.start_progress('Getting messages from %s'
                                  % mailbox.name(), len(msgs)+msglen+1)

    new = []
    deleted = []
    
    cnt = 0
    for id in msgs:
      if not mailbox._uid_map.get(id):
        self.debug('adding message: %d' % id)
        msg = MHMessage(self, mailbox, id)
        mailbox._uid_map[id] = msg
        mailbox._messages.append(msg)
        new.append(msg)
      self._ui.update_progress(cnt, bar)
      cnt = cnt + 1

    if msglist:
      for msg in msglist:
        if msg.id() not in msgs:
          mailbox._messages.remove(msg)
          deleted.append(msg)
          del mailbox._uid_map[msg.id()]
        self._ui.update_progress(cnt, bar)
        cnt = cnt + 1

    mailbox._update_flags()
    self._ui.update_progress(cnt+1, bar)
    self._ui.finish_progress(bar)

    return new, deleted
    
  def save_message_cache(self): #no need
    pass

  def _get_body(self, mailbox, uid, part_number=''):
    res, fname = mailbox._folder('getmessagefilename',(uid,))
    f = open(fname, 'r')
    body = f.read()
    f.close()
    return body

class MHMailbox(Mailbox.Mailbox):
  def __init__(self, source, path, delim, parent = None):
    Mailbox.Mailbox.__init__(self, source, parent)
    self._path = path
    self._uid_map = {}
    try:
      idx = string.rindex(path, delim)
      self._name = path[idx+1:]
    except ValueError:
      self._name = path

    self._rfolder = self._source._mh('openfolder', (self._path,))

    self.init_done()

  def _folder(self, func, args = (), ignore_errors=0):
    try:
      data = apply(eval('self._rfolder.%s' % func), args)
      return 1, data
    except mhlib.Error, x:
      # parsesequence throws and exception when there are no messages
      # so just ignore it, return an empty sequence
      #nomsg = 'no messages'
      #if func == 'parsesequence' and x[:len(nomsg)] == nomsg:
      #  return []
      if not ignore_errors:
        self._source._ui.alert('MH Mailbox Error: %s' % x)
      return 0, None
    except os.error, x:
      self._source._ui.alert('MH Mailbox OSError: %s' % x)
      return 0, None

  def _get_last(self):
    res, data =  self._folder('getlast') or 0
    return data
    
  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)
    c = self._messages and len(self._messages) or 0
    new, deleted = self._source.sync_messages(self)

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

    if new and len(new):
      Plugin.call_plugins('new_mail_arrived', (new,))
    
    return len(new)

  def expunge(self):
    deleted = filter(lambda x: x.deleted(), self._messages)
    foreach(self._source._really_delete_message, deleted)
    subtract_lists(self._messages, deleted)

  def can_add_subfolder(self):
    return true

  def can_delete(self):
    return false

  def can_rename(self):
    return false

  def can_expunge(self):
    return true

  def _update_flags(self):
    res, unseen = self._folder('parsesequence', (self._source._unseen_seq_key,), 1)

    foreach(lambda x: x._set_seen(1), self._messages)
    if res and unseen:
      for id in unseen:
        msg = self._uid_map.get(id)
        if msg:
          msg._flags['seen'] = 0

    foreach(lambda x: x._set_deleted(0), self._messages)
    res, deleted = self._folder('parsesequence', (self._source._deleted_sequence_key,), 1)
    if res and deleted:
      for id in deleted:
        msg = self._uid_map.get(id)
        if msg:
          msg._flags['deleted'] = 1
  

  def save_message_cache(self, dir):
    pass

  def load_message_cache(self, dir):
    return 0


class MHMessage(Message.Message):
  def __init__(self, source, mailbox, id, hdrs=None, flags=None):
    Message.Message.__init__(self, source, mailbox, id, hdrs, flags)
    self._debug = 0
    #mailbox._uid_map[id] = self
    if not flags:
      self._flags = {'seen': 1}

    res, fname = mailbox._folder('getmessagefilename', (id,))
    f = open(fname, 'r')
    rfc = rfc822.Message(f)
    self._headers = rfc.dict
    
    self._date = rfc.getdate('date')

    try:
      self._float_date = time.mktime(self._date)
    except ValueError,x:
      self.log_error('ValueError in date \'%s\': %s' % (self._date, x))
      self._float_date = 0

    f.close()
    del rfc

  def delete(self):
    if not self.deleted():
      self._flags['deleted'] = 1
      self._source.delete_message(self)
    else:
      self._flags['deleted'] = 0
      self._source.undelete_message(self)

  def set_seen(self, val):
    if val != self.seen():
      Message.Message.set_seen(self, val)
      self._source._write_flags([self])

  def get_header(self, name):
    return self._headers.get(string.lower(name), "")

  def get_parts(self):
    if self._parts == None:
      res, mhmsg = self._mailbox._folder('openmessage', (self._id,))
      self._parts = Message.make_mimeparts(mhmsg, self)
      del mhmsg
      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, 1)
    return self._body

  def load_all_headers(self):
    return self._headers



class MH(mhlib.MH):
  def __init__(self, ui, path = None, profile = None):
    mhlib.MH.__init__(self, path, profile)
    self._ui = ui

  def error(self, msg, *args):
    self._ui.alert('MH Mailbox Error: %s' % (msg % args))
  
