# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# This program 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
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


"""Emails Module"""


import email
from email.Header import Header
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
import smtplib

import expression.core.elements as elements
import expression.core.environs as environs
import expression.core.html as html
import expression.core.logs as logs
import expression.core.namespaces as namespaces
import expression.core.stations as stations
import expression.core.strings as strings
import expression.core.widgets as widgets
import xforms.xforms as xforms


class WidgetElement(widgets.WidgetMixin, elements.Element):
    pass


class Multipart(WidgetElement):
    def generateEmailContextMessageBody(self, context):
        message = MIMEMultipart(self.subType)
        childNode = self.node.children
        while childNode is not None:
            if childNode.type in ("cdata", "comment", "text"):
                childNode = childNode.next
                continue
            element = self.newElement(childNode)
            message.attach(element.generateEmailContextMessageBody(context))
            childNode = childNode.next
        return message

    def getSubType(self):
        raise NotImplementedError

    subType = property(getSubType)


class Address(WidgetElement):
    def generateEmailContextParsedAddress(self, context):
        name = self.name
        realEmail = self.email
        if name is None and realEmail is None:
            return email.Utils.parseaddr(
                self.generatePlainTextContext(context))
        if name is None:
            nameText = None
        else:
            nameText = name.generatePlainTextContext(context)
        if realEmail is None:
            emailText = None
        else:
            emailText = realEmail.generatePlainTextContext(context)
        return (nameText, emailText)

    def getEmail(self):
        nodes = self.evaluateXpath("mail:email")
        if not nodes:
            return None
        return self.newElement(nodes[0])

    def getName(self):
        nodes = self.evaluateXpath("mail:name")
        if not nodes:
            return None
        return self.newElement(nodes[0])

    email = property(getEmail)
    name = property(getName)


class Alternative(Multipart):
    subType = "alternative"


class Message(xforms.Control):
    def generateEmailContextMessage(self, context):
        body = self.body
        if body is None:
            message = email.Message()
        else:
            message = body.generateEmailContextMessageBody(context)
        address = self.fromAddress
        if address:
            message["From"] = Header(email.Utils.formataddr(
                address.generateEmailContextParsedAddress(context))).encode()
        addresses = self.toAddresses
        if addresses:
            message["To"] = Header(u", ".join([
                email.Utils.formataddr(
                    address.generateEmailContextParsedAddress(context))
                for address in addresses])).encode()
        addresses = self.ccAddresses
        if addresses:
            message["Cc"] = Header(u", ".join([
                email.Utils.formataddr(
                    address.generateEmailContextParsedAddress(context))
                for address in addresses])).encode()
        addresses = self.bccAddresses
        if addresses:
            message["Bcc"] = Header(u", ".join([
                email.Utils.formataddr(
                    address.generateEmailContextParsedAddress(context))
                for address in addresses])).encode()
        message["Subject"] = Header(
            self.subject.generatePlainTextContext(context)
            ).encode()
        return message

    def generatePlainTextContext(self, context):
        message = self.generateEmailContextMessage(context)
        return message.as_string().decode("UTF-8")

    def generateXmlContext(self, context, layout):
        import html
        message = self.generateEmailContextMessage(context)
        layout.append(html.pre(message.as_string()))
        return True

    def getBind(self, context, modelContext):
        return None

    def getBccAddresses(self):
        addressNodes = self.evaluateXpath("mail:bcc")
        return [self.newElement(addressNode) for addressNode in addressNodes]

    def getBody(self):
        nodes = self.evaluateXpath("html:html | mail:alternative | mail:mixed | mail:plain")
        if not nodes:
            return None
        return self.newElement(nodes[0])

    def getCcAddresses(self):
        addressNodes = self.evaluateXpath("mail:cc")
        return [self.newElement(addressNode) for addressNode in addressNodes]

    def getChildInstanceDataXpath(self, context, modelContext):
        return ""

    def getFromAddress(self):
        nodes = self.evaluateXpath("mail:from")
        if not nodes:
            return None
        return self.newElement(nodes[0])

    def getHtmlBody(self):
        nodes = self.evaluateXpath("mail:htmlbody")
        if not nodes:
            return None
        return self.newElement(nodes[0])

    def getModelId(self):
        return None

    def getSubject(self):
        nodes = self.evaluateXpath("mail:subject")
        if not nodes:
            return None
        return self.newElement(nodes[0])

    def getToAddresses(self):
        addressNodes = self.evaluateXpath("mail:to")
        return [self.newElement(addressNode) for addressNode in addressNodes]

    def newContext(self, specimen, *attributes, **keywords):
        return MessageContext(self, specimen, *attributes, **keywords)

    def sendContext(self, context):
        message = self.generateEmailContextMessage(context)
        if not environs.getVar("sendEmails"):
            logs.debug("Not sending email:\n%s" % message.as_string())
            return
        smtpServerHostName = "localhost"
        smtpServerPort = 25
        try:
            smtpServer = smtplib.SMTP()
            smtpServer.connect(smtpServerHostName, smtpServerPort)
            smtpServer.sendmail(
                email.Utils.parseaddr(message["From"])[1],
                [address[1] for address in email.Utils.getaddresses(
                    message.get_all("To", []) + message.get_all("Cc", [])
                    + message.get_all("Bcc", []))],
                message.as_string())
            smtpServer.quit()
        except: # TODO: tighter check
            logs.exception("SMTP Error")
            raise

    bccAddresses = property(getBccAddresses)
    body = property(getBody)
    ccAddresses = property(getCcAddresses)
    fromAddress = property(getFromAddress)
    htmlBody = property(getHtmlBody)
    subject = property(getSubject)
    toAddresses = property(getToAddresses)


class MessageContext(html.WidgetElementContext):
    formCreationNeeded = False # No HTML form in emails.

    def send(self):
        """Handles HTTP GET."""

        self.prototype.sendContext(self)
        station = environs.getVar("currentStation")
        if station is not None and isinstance(station, stations.EasyFunctionStation) \
               and station.actionName == "send" and environs.getVar("httpCommand") == "GET":
            # If this method is called as a HTTP handler, alerts the user that the email has been
            # sent.
            environs.getVar("httpRequestHandler").outputAlert(
                N_("The email has been successfully sent."), title = N_("Email Sent"))


class Mixed(Multipart):
    subType = "mixed"


class Plain(WidgetElement):
    def generateEmailContextMessageBody(self, context):
        return MIMEText(
            self.generatePlainTextContext(context).encode(
                "UTF-8"),
            "plain", "UTF-8")


def sendMail(mailFrom, mailTo, mailSubject, mailMessage, moreHeaders = None):
    if isinstance(mailTo, basestring):
        mailTo = [mailTo]
    if len(mailTo) == 0:
        return
    mailToStr = ', '.join(mailTo)

    mailHeader = """\
From: %s
To: %s
Subject: %s
Mime-Type: 1.0
""" % (
        mailFrom, mailToStr, mailSubject)
    if moreHeaders:
        mailHeader += "\n".join(["%s: %s" % x for x in moreHeaders.items()]) \
            + "\n"
    mailMessage = "%s\n%s" % (mailHeader, mailMessage)
    mailMessage = mailMessage.replace("\n", "\r\n")
    mailMessage = strings.latin1_to_ascii(mailMessage)

    if not environs.getVar("sendEmails"):
        logs.debug("Email\n%s" % mailMessage)
        return

    smtpServerHostName = "localhost"
    smtpServerPort = 25

    try:
        smtpServer = smtplib.SMTP()
        smtpServer.connect(smtpServerHostName, smtpServerPort)
        smtpServer.sendmail(mailFrom, mailTo, mailMessage)
        smtpServer.quit()
    except: # TODO: tighter check
        logs.exception("SMTP Error")
        raise


elements.registerElement(namespaces.mail.uri, "alternative", Alternative)
elements.registerElement(namespaces.mail.uri, "bcc", Address)
elements.registerElement(namespaces.mail.uri, "cc", Address)
elements.registerElement(namespaces.mail.uri, "email", WidgetElement)
elements.registerElement(namespaces.mail.uri, "from", Address)
elements.registerElement(namespaces.mail.uri, "message", Message)
elements.registerElement(namespaces.mail.uri, "mixed", Mixed)
elements.registerElement(namespaces.mail.uri, "name", WidgetElement)
elements.registerElement(namespaces.mail.uri, "plain", Plain)
elements.registerElement(namespaces.mail.uri, "subject", WidgetElement)
elements.registerElement(namespaces.mail.uri, "to", Address)
