# This python file uses the following encoding: utf-8

import random

import os
import re
import imaplib
import smtplib
import email
import email.errors
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication 
from email.mime.multipart import MIMEMultipart 


import ConfigParser

class KComEmilio():
    class Excepcion(Exception):
        """ Excepción de propósito general y clase base para las demás """
        pass
    
    class Error(Excepcion):
        """ 
        Para errores que implican una imposibilidad de completar 
        la acción a nivel del motor 
        """
        pass

    def __init__(self, FichConfig):
    	cfg = ConfigParser.SafeConfigParser()
        cfg.read(FichConfig)
        seccion = 'Correo'

        self._DireccionServidorIMAP4 = cfg.get(seccion, 
                                                    'DireccionServidorIMAP4')
        self._DireccionServidorSMTP = cfg.get(seccion, 
                                                    'DireccionServidorSMTP')
        self._UserIMAP4 = cfg.get(seccion, 'UserIMAP4')
        self._UserSMTP = cfg.get(seccion, 'UserSMTP')
        self._PassIMAP4 = cfg.get(seccion, 'PassIMAP4')
        self._PassSMTP = cfg.get(seccion, 'PassSMTP')
        self._MailboxDepuracion = cfg.get(seccion, 'MailboxDepuracion')
        self._SoloEnvio = cfg.getboolean(seccion, 'SoloEnvio')
        
        self.IMAP4 = None
        self.SSMTP = None
        
        self.m_MensajesPendientes = []
        self.m_IdxMensajesAEliminar = []
        self._SubjectPorDefecto = cfg.get(seccion, 'Subject')
        
        # compilamos la regex para FROMaIDP
        # Match para NIP@celes.unizar.es ó NIP@unizar.es
        self.m_FaI = re.compile(r'\d\d\d\d\d\d@(celes\.)?unizar\.es')
        self.m_IaF = re.compile(r'\d\d\d\d\d\d')

        # Variable para almacenar el namespace
        self._Namespace = ''

    def Conectado(self):
        Res = self.SSMTP is not None
        if not self._SoloEnvio:
            Res = Res and self.IMAP4 is not None
        return Res       

    def IniciaConexion(self):
        self._IniciaConexionSMTP()
        if not self._SoloEnvio:
            self._IniciaConexionIMAP()

    def _IniciaConexionSMTP(self):
        try:
            Txt  = u'No se ha podido establecer correctament la conexión '
            Txt += u'con el servidor SMTP'
            # El constructor llama a connect
            self.SSMTP = smtplib.SMTP() 
            Res = self.SSMTP.connect(self._DireccionServidorSMTP)
            Res = self.SSMTP.ehlo()
            Res = self.SSMTP.starttls()
            Res = self.SSMTP.ehlo
            Res = self.SSMTP.login(self._UserSMTP, self._PassSMTP)
        except smtplib.SMTPException as e:
            self.SSMTP = None
            Txt += '\n' + e.message
            raise self.Error(Txt)
        except Exception as e:
            self.SSMTP = None
            Txt += '\n' + e.message
            raise self.Error(Txt)

    def _IniciaConexionIMAP(self):
        try:
            Txt  = 'No se ha podido establecer correctamente la conexion '
            Txt += 'con el servidor IMAP'
            self.IMAP4 = imaplib.IMAP4_SSL(self._DireccionServidorIMAP4)
            if self.IMAP4.login(self._UserIMAP4, 
                                              self._PassIMAP4)[0] != 'OK':
                raise self.Error(Txt)
            
            # obtenemos el namespace
            typ, dat = self.IMAP4.namespace()
            if typ == 'OK':
                aux = dat[0][dat[0].find('((')+2:dat[0].find('))')]
                ini = aux.find('"')
                fin = aux.find('"', ini + 1) 
                self._Namespace = aux[ini+1:fin]
            else:    
                raise self.Error(Txt)

            # seleccionamos INBOX
            typ, dat = self.IMAP4.select()
            if typ != 'OK':
                raise self.Error(Txt)
        except imaplib.IMAP4.error as e:
            Txt += '\n' + e.message
            raise self.Error(Txt)
        except Exception as e:
            Txt += '\n' + e.message
            raise self.Error(Txt)

    def TerminaConexion(self):
        if self.SSMTP is not None:
            self.SSMTP.close()
            self.SSMTP = None
        if self.IMAP4 is not None:
            self.IMAP4.expunge()
            self.IMAP4.close()
            self.IMAP4.logout() # cerramos la conexión
            self.IMAP4 = None

    # Funciones para convertir la clase en un context manager        
    def __enter__(self):
        self.IniciaConexion()
        return self

    def __exit__(self, ext, exv, trb):
        self.TerminaConexion()
        return False # para que relance las excepciones generadas

    def _CreaBandejaParaDepuracion(self):
        """
        Comprueba si no existen la bandeja para depuración y en su caso la 
         crea
        """
        if self._SoloEnvio:
            raise self.Error('Instanciado como "Solo Envio"')

        NamesMailBox = self._Namespace + self._MailboxDepuracion
        # comprobamos si ya existe
        typ, dat = self.IMAP4.list()
        if typ == 'OK':
            lfold = []
            for k in dat:
                ult = k.rfind('"')
                lfold.append(k[k[:ult].rfind('"')+1:ult])
            if NamesMailBox not in lfold:                
                typ, dat = self.IMAP4.create(NamesMailBox)
                if typ == 'OK':
                    typ, dat = self.IMAP4.subscribe(NamesMailBox)

    def _ObtenListaDeIdentificadoresDeMensaje(self):
        """
        Devuelve una lista con los UID como strings
         lista vacía en caso de fallo
        """
        if self._SoloEnvio:
            raise self.Error('Instanciado como "Solo Envio"')

        luid = []
        typ, dat = self.IMAP4.uid('SEARCH', 'ALL') 
        if typ == 'OK':
            luid = dat[0].split()
        return luid

    def _ObtenCuerpo(self, Mes):
        if not Mes.is_multipart():
            pl = Mes.get_payload(decode = True)
            # if Mes.get_content_type() == 'text/plain':
            codec = Mes.get_content_charset()
            try:
                if codec is None:
                    aux = pl
                else:
                    aux = pl.decode(codec)
            except:
                    aux = pl
            Salida = (True,  aux)
        else:
            pl = Mes.get_payload()
            Salida = (False, '')
            for submes in pl:
                Salida = self._ObtenCuerpo(submes)
                if Salida[0]:
                    break
        return Salida

    def ObtenMensajes(self, luid = None):
        if self._SoloEnvio:
            raise self.Error('Instanciado como "Solo Envio"')


        if luid is None:
            luid = self._ObtenListaDeIdentificadoresDeMensaje()
            # Los descargamos aleatoriamente para evitar que un mensaje
            #  que genere una excepción (incontrolada) bloquee a todos 
            #  los demás
            random.shuffle(luid)
        
        DMen = {}
        for it in luid:
            typ, dat = self.IMAP4.uid('FETCH', it, '(RFC822)')
            if typ == 'OK':
                Dit = {}
                Mes = email.message_from_string(dat[0][1])
                From = Mes['From']
                Dit['FROM'] = From
                ini, fin = From.find('<'), From.find('>')
                if ini != -1 and fin != -1:
                    Dit['TAG_PAX'] = self._FROMaIDP(From[ini+1:fin])
                else:
                    Dit['TAG_PAX'] = self._FROMaIDP(From)
                
                Salida = self._ObtenCuerpo(Mes)
                Dit['BODY'] = '--NO SE HA INTERPRETADO EL MENSAJE--'
                if Salida[0]:
                    Dit['BODY'] = Salida[1]
                DMen[it] = Dit
        return DMen       


    def MarcaMensajesParaBorrar(self, luid):
        if self._SoloEnvio:
            raise self.Error('Instanciado como "Solo Envio"')

        for it in luid:
            typ, dat = self.IMAP4.uid('STORE', it, '+FLAGS', '(\\Deleted)')

    def MueveMensajesADepuracion(self, luid):
        if self._SoloEnvio:
            raise self.Error('Instanciado como "Solo Envio"')

        luidBorrar = []
        for it in luid:
            MBDepuracion = self._Namespace + self._MailboxDepuracion
            typ, dat = self.IMAP4.uid('COPY', it, MBDepuracion)
            if typ == 'OK': luidBorrar.append(it)
        self.MarcaMensajesParaBorrar(luidBorrar)


    def _FROMaIDP(self, FROM):
        if self.m_FaI.match(FROM) is not None: IDP = FROM[0:6]
        else: IDP = FROM
        return IDP
        
    def _IDPaFROM(self, IDP):
        #if self.m_IaF.match(IDP) is not None: FROM = IDP + '@celes.unizar.es'
        if self.m_IaF.match(IDP) is not None: FROM = IDP + '@unizar.es'
        else: FROM = IDP
        return FROM
        

    def EnviaCorreo(self, Dest, Txt, Adjuntos = (), Subject = None):
        """
        Envia un correo de respuesta
             Dest -> Destinatario (en version corta o larga)
              Txt -> Texto del mensaje principal
         Adjuntos -> Colección de adjuntos en la forma
                          ((NombreFich, (Blow, FMT)), ...)
        """
        Exito, Inf = True, u''
        # si no hay Destinatario, volvemos
        if Dest.strip() == '': 
            Exito, Inf = False, 'No se han establecido destinatarios'
        
        # Construccion del mensaje
        if Exito:
            try:
                OMenTxt = MIMEText(Txt, _subtype = 'plain', _charset = 'utf-8')
                if Adjuntos is None or len(Adjuntos) == 0: # sin adjuntos?
                    OMen = OMenTxt
                else: # el mensaje lleva adjuntos
                    OMen = MIMEMultipart()
                    OMen.attach(OMenTxt)
                    for nom, (blow, fmt) in Adjuntos:
                        if fmt.upper() == 'PDF':
                            OMenPdf = MIMEApplication( blow, _subtype = 'pdf')
                            OMenPdf.add_header('Content-Disposition', 
                                       'attachment', filename = nom + '.pdf')
                            OMen.attach(OMenPdf)
                        elif fmt.upper() == 'ODS':
                            OMenOds = MIMEApplication( blow, _subtype = 'ods')
                            OMenOds.add_header('Content-Disposition', 
                                       'attachment', filename = nom + '.ods')
                            OMen.attach(OMenOds)
                        elif fmt.upper() == 'XLS':
                            OMenXls = MIMEApplication( blow, _subtype = 'xls')
                            OMenXls.add_header('Content-Disposition', 
                                       'attachment', filename = nom + '.xls')
                            OMen.attach(OMenXls)
                        elif fmt.upper() == 'TXT':
                            OMenTxt = MIMEText(blow,
                                                        'plain', 'utf-8')
                            OMenTxt.add_header('Content-Disposition', 
                                       'attachment', filename = nom + '.txt')
                            OMen.attach(OMenTxt)
                        else:
                            Txt = 'no se como hacer un attach del formato'
                            Txt += ' %s'%fmt
                            raise self.Error(Txt)

                OMen['To'] = self._IDPaFROM(Dest)
                OMen['From'] = self._UserSMTP 
                if Subject is None: 
                    Subject = self._SubjectPorDefecto
                OMen['Subject'] = Subject 

            except email.errors.MessageError as e:
                Inf = 'Error en la composición del mensaje: %s'%e
                Exito = False        

        # Envío del mensaje    
        if Exito:
            try:
                self.SSMTP.sendmail(self._UserSMTP, OMen['To'], 
                                                            OMen.as_string())
            except Exception as e:
                Inf = 'Error en el envío del mensaje: %s'%e
                Exito = False

        return Exito, Inf



if __name__ == "__main__":
    MEN = u"""
    ACCION = CORRECCION
    TAREA = TAR_01
    PIG = 3.2
    """

    

    with KComEmilio() as KCE:
        #KCE.EnviaMensajeTexto('kbc@unizar.es', MEN)
        #KCE.EnviaMensajeTexto('kbc@unizar.es', MEN)
        #KCE.EnviaMensajeTexto('kbc@unizar.es', MEN)
        
        
        #KCE._CreaBandejaParaDepuracion()
        luid = KCE._ObtenListaDeIdentificadoresDeMensaje()
        #Dit = KCE._ObtenMensajes(luid)
        #print Dit
        KCE._MarcaMensajesParaBorrar(luid)
        #KCE._MueveMensajesADepuracion(luid)

        """
        Dest = 'msamplon@unizar.es'
        Txt = 'yeeepa'
        Subj = 'probando yepa'
        with open('DatosParaPruebas/PE01_ENU.pdf', 'rb') as fp:
            blw = fp.read()
        Adj = (('yepa1.pdf', (blw, 'PDF')), 
               ('yepa2.pdf', (blw, 'PDF')),
               ('yepa3.pdf', (blw, 'PDF')),
               ('yepa4.pdf', (blw, 'PDF')),
               ('yepa5.pdf', (blw, 'PDF')),
               ('yepa6.pdf', (blw, 'PDF')))
        print KCE.EnviaCorreoRespuesta(Dest, Txt, Subject = Subj, Adjuntos = Adj)
        """

        

