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

import Base
import util
import Source

class Message(Base.PMailObject):
  def __init__(self, source=None, mailbox=None, id=None,
               hdrs=None, flags={}):
    Base.PMailObject.__init__(self)
    #self._debug = 1
    self._body = None
    self._attachments = None
    self._source = source
    self._id = id
    self._mailbox = mailbox
    self._parts = None
    self._headers = hdrs
    self._flags = flags
    self._float_date = None
    self._date = None
    self._temp_files = []
    self._full_body = None

  #subclass can override the locking if the mailbox does not need to be locked
  def can_lock(self):
    return self._mailbox.can_lock()

  def lock(self):
    self._mailbox.lock()

  def unlock(self):
    self._mailbox.unlock()

  def id(self):
    return self._id

  def float_date(self):
    return self._float_date

  def date(self):
    return self._date
  
  def get_header(self, name):
    return mimify.mime_decode_header(self._headers.get(name, ""))

  def headers(self):
    return self._headers

  def load_all_headers(self):
    pass

  def mailbox(self):
    return self._mailbox

  def seen(self):
    return self._flags.get('seen')

  def _set_seen(self, val):
    self._flags['seen'] = val

  def set_seen(self, val):
    self._set_seen(val)

  def _set_deleted(self, val):
    self._flags['deleted'] = val

  def set_deleted(self, val):
    self._set_deleted(val)

  def answered(self):
    return self._flags.get('answered')

  def _set_answered(self):
    self._flags['answered'] = 1

  def set_answered(self):
    self._set_answered()

  def deleted(self):
    return self._flags.get('deleted')

  def flagged(self):
    return self._flags.get('flagged')

  def _set_flagged(self, val):
    self._flags['flagged'] = val

  def set_flagged(self, val):
    self._set_flagged(val)

  def draft(self):
    return self._flags.get('draft')

  def set_draft(self):
    self._flags['draft'] = 1

  def getHeaderText(self, wants_html=1):
    headers = self.load_all_headers()

    if util.get_config('headerView') == util.NORMAL_HEADERS:
      keys = util.normalDisplayHeaders
      #util.get_config('displayHeaders')
    elif util.get_config('headerView') == util.BRIEF_HEADERS:
      keys = util.briefDisplayHeaders
    else:
      keys = headers.keys()

    res = ''
    for key in keys:
      val = headers.get(key, None)
      if val:
        keyn = string.upper(key[:1]) + key[1:]
        val = mimify.mime_decode_header(val)
        if wants_html:
          res = res + '<code>'
          hval = util.plainTextToHtml(val)
          res = res + '<strong>%s</strong>: ' % keyn

          if key == 'subject':
             res = res + '<strong>%s</strong><br>' % hval
          elif key == 'from':
            res = res + util.mailtoHeaderHtml(val) + '<br>'
          elif key == 'to':
            res = res + util.mailtoHeaderHtml(val) + '<br>'
          else:
            res = res + '%s<br>' % hval
        else:
          res = res + '%s: %s\n' % (keyn, val)
          

    if wants_html:
      res = res + '</code><br><br><hr>'
    else:
      res = res + '\n\n'
    return res

  def delete(self):
    pass
  
  def append(self, mailbox):
    if mailbox != self._mailbox:
      return mailbox._source.append_message(mailbox, self)
    return 0

  def unique_name(self):
    return '%d,%s,%d' % (self._mailbox.source()['unique_id'],
                         self._mailbox.path(),
                         self._id)
  

  def clean_up_temp_files(self):
    for file in self._temp_files:
      os.remove(file)
    self._temp_files = []

  def make_temp_file(self, part):
    filename = part.write_to_file()
    return filename

  def full_body(self):
    if not self._full_body:
      self._full_body = self._get_body(self._mailbox, self._id)
    return self._full_body

  def imagePartHtml(self, part):
    self.debug('looking for image type: %s' % part.subtype())
    self.debug('type is %s' % type)


    filename = part.write_to_file()

    html = '<center>'
    html = html + '<IMG SRC=\"%s\">' % filename
    html = html + '</center>'

    return html
    
  def textPartHtml(self, part):
    data = part.data()
    if part.subtype() == 'html':
      return data
    else:
      return '\n\n' + util.plainTextToHtml(data)
    
  def externalPartHtml(self, part):
    rs = '<tr border=><td>'
    re = '</td></tr>'
    html = '<br><br><table width=30% border=1>'+ rs \
           + '<strong>Type:</strong> %s/%s' \
           % (part.type(), part.subtype()) + re
    fileName = part.parameters().get('name')
    if not fileName:
      fileName = 'attachment'
    url = 'attachment:%s' % string.replace(part.number(), '.', '/')
    html = html + rs + '<strong>Name:</strong> <a href=\"%s\">%s</a>' \
           % (url, util.plainTextToHtml(fileName)) + re
    html = html + rs + '<strong>Encoding:</strong> %s</td>' \
           % (part.encoding()) + re
    html = html + '</table>'
    return html

  def htmlForPart(self, part, attachments, add_to_attachments=0):
    self.debug('getting html for part: %s' % part.number())
    if part.is_multipart():
      self.debug('got multipart: %s' % part.subtype())
      if part.subtype() == 'alternative':
        p = part.find_part('text', 'html')
        if not p:
          p = part.find_part('multipart')
          if not part:
            p = part.data()[0]
        self.debug( 'chose (%s/%s)' % (p.type(), p.subtype()))
        return self.htmlForPart(p, attachments)
      elif part.subtype() == 'related':
        p = part.find_part('text', 'html')
        if not p:
          p = part.find_part('text', 'plain')
          if not p:
            p = part.data()[0] #FIXME: maybe show them all??
        return self.htmlForPart(p, attachments)
      elif part.subtype() == 'message':
        attachments[part.number()] = part        
        return ''
      else:
        html = ''
        idx = 0
        for p in part.data():
          if p.type() == 'text' \
             and (p.subtype() == 'plain' or p.subtype() == 'html') \
             and idx == 0:
            add_to_attachments = 0
          else:
            add_to_attachments = 1
          s = self.htmlForPart(p, attachments, add_to_attachments)
          html = html + s # + '<hr size=4>'
          idx = idx + 1
        return html
    elif part.type() == 'message':
      self.debug('got message')
      if add_to_attachments:
        attachments[part.number()] = part
      return '' #util.plainTextToHtml(part.data())
    elif part.type() == 'image':
      self.debug('got image')
      if add_to_attachments:
        attachments[part.number()] = part
      return self.imagePartHtml(part)
    elif part.type() == 'text' \
         and part.subtype() == 'plain' or part.subtype() == 'html':
      self.debug('got text')
      if add_to_attachments:
        attachments[part.number()] = part

      if part.subtype() == 'html':
        return util.remove_nulls(self.textPartHtml(part))
      else:
        html = "<code><big>" + \
               util.remove_nulls(self.textPartHtml(part)) + \
               "</big></code><hr>"
        return html
    else:
      self.debug('got externa;')
      attachments[part.number()] = part      
      #return self.externalPartHtml(part)
      return ''

  def textForPart(self, part, attachments, add_to_attachments=0):
    self.debug('getting text for part: %s' % part.number())
    if part.is_multipart():
      self.debug('got multipart: %s' % part.subtype())
      if part.subtype() == 'alternative':
        p = part.find_part('text', 'plain')
        if not p:
          p = part.find_part('multipart')
          if not part:
            p = part.data()[0]
        self.debug( 'chose (%s/%s)' % (p.type(), p.subtype()))
        return self.textForPart(p, attachments, 0)
#        elif part.subtype() == 'related':
#          p = part.find_part('text', 'plain')
#          if not p:
#            p = part.data()[0] #FIXME: maybe show them all??
#          return self.textForPart(p, attachments, 1)
      elif part.subtype() == 'message':
        attachments[part.number()] = part
        return ''
      else:
        txt = ''
        idx = 0
        for p in part.data():
          if p.type() == 'text' \
             and (p.subtype() == 'plain') \
             and idx == 0:
            add_to_attachments = 0
          else:
            add_to_attachments = 1
          s = self.textForPart(p, attachments, add_to_attachments)
          txt = txt + s
          idx = idx + 1
        return txt
          
    elif part.type() == 'text' \
         and (part.subtype() == 'plain' or part.subtype() == 'html'):    
      if add_to_attachments or part.subtype() == "html":
        attachments[part.number()] = part
      #return part.data()
      oio = StringIO()
      mimify.unmimify_part(mimify.File(StringIO(part.data()), None), oio, 0)
      return util.remove_nulls(oio.getvalue())
    else:
      attachments[part.number()] = part
      return ''

  def get_text(self, wants_html=1, include_headers=1):
    part = self.get_parts()
    txt = ''
    if include_headers:
      txt = self.getHeaderText(wants_html)
    attachments = {}
    if wants_html:
      txt = txt + self.htmlForPart(part, attachments)
      return txt, attachments
    else:
      txt = txt + self.textForPart(part, attachments)
      return txt, attachments

  def cache_value(self):
    return { 'id': self._id,
             'headers': self._headers.dict,
             'flags': self._flags,
             }

  def set_cache_value(self, c):
    self._flags = c['flags']
    hdrs = c['headers']
    sio = StringIO()
    for hdr in hdrs.items():
      key, val = hdr
      sio.write('%s: %s\n' % (key, val))
    sio.seek(0)
    self._headers = rfc822.Message(sio)

  def _get_body(self, mailbox, uid, part_number=''):
    return self._source._get_body(mailbox, uid, part_number)


class MimePart(Base.PMailObject):
  def __init__(self, message):
    Base.PMailObject.__init__(self)
    self._type = None
    self._subtype = None
    self._data = None
    self._parameters = None
    self._encoding = None
    self._number = None
    self._message = message
    self._id = None
    self._description = None

  def number(self):
    return self._number

  def type(self):
    return self._type

  def subtype(self):
    return self._subtype

  def mime_type(self):
    return '%s/%s' % (self._type, self._subtype)

  def parameters(self):
    return self._parameters

  def encoding(self):
    return self._encoding

  def id(self):
    return self._id

  def description(self):
    return self._description

  def is_multipart(self):
    return self._type == 'multipart'

  def find_part(self, type, subtype = None, deep=0):
    if self.is_multipart():
      for part in self._data:
        if type == part._type \
           and (subtype == None or subtype == part._subtype):
          return part
        elif deep:
          p = part.find_part(type, subtype, deep)
          if p:
            return p
    return None

  def find_part_with_id(self, id):
    if self._id == id:
      return self
    elif self.is_multipart():
      for part in self._data:
        p = part.find_part_with_id(id)
        if p:
          return p
    return None

  def default_body_part(self):
    if self.is_multipart():
      return self._data[0]
    else:
      return self

  def data(self):
    if not self.is_multipart() and not self._data:
      self._data = self._message._get_body(self._message.mailbox(),
                                           self._message.id(),
                                           self._number)
      if self._encoding:
        if not self._encoding in ('', '7bit', '8bit', 'binary'):          
          try:
            s = StringIO()
            mimetools.decode(StringIO(self._data), s, self.encoding())
            self._data = s.getvalue()
          except:
            self._message._source._ui.alert('MimePart: could not decode image attachment')
    self._message.set_seen(1)

    return self._data
  
  def _dump(self, ind = ''):
    print ind + 'MimePart: '
    print ind + ' number = ', self._number
    print ind + ' type = ', self._type
    print ind + ' subtype = ', self._subtype
    print ind + ' parameters = ', self._parameters
    print ind + ' id = ', self._id
    print ind + ' description = ', self._description    
    print ind + ' encoding = ', self._encoding
    if self._type == 'multipart':
      print ind + ' data = ('
      for p in self._data:
        p._dump(ind + '    ')
      print ind + ' )'

  def __add_parts(self, dict):
    if self.is_multipart():
      for p in self._data:
        p.__add_parts(dict)
    if self._number:
      dict[self._number] = self

  def partsDictionary(self):
    dict = {}
    self.__add_parts(dict)
    return dict

  def write_to_file(self, filename=None, remove_when_done=1):
    if not filename:
      filename = tempfile.mktemp()
    f = open(filename, "w")
    data = self.data()
    f.write(data)
    f.close()
    if remove_when_done:
      self._message._temp_files.append(filename)
    return filename

          
def make_mimeparts(mhmsg, message):
  part = MimePart(message)
  part._type = mhmsg.getmaintype()
  part._subtype = mhmsg.getsubtype()
  part._parameters = {}
  part._number = mhmsg.number
  part._encoding = mhmsg.getencoding()

  pnames = mhmsg.getparamnames()
  for name in pnames:
    part._parameters[name] = mhmsg.getparam(name)

  if part._type == 'multipart':
    num = 1
    part._data = []
    for msg in mhmsg.getbodyparts():
      p = make_mimeparts(msg, message)
      part._data.append(p)
  else:
    part._data = mhmsg.getbodytext()

  return part
  

class TextMessage(Message):
  def __init__(self, fileio):
    Message.__init__(self, flags={})
    #self._debug = 0

    self._headers = mhlib.Message(None, None, fileio)
    self._date = self._headers.getdate('date')
    self._float_date = time.mktime(self._date)

  def get_parts(self):
    if self._parts == None:
      self._parts = make_mimeparts(self._headers, self)
      if self._debug:
        self._parts._dump()
    return self._parts
  
  def body(self):
    return None

  def load_all_headers(self):
    return self._headers
