import socket
import select
from functools import partial
import errno

from pysec.core import memory
from pysec.xsplit import xbounds
from pysec.net.error import TooBigReply, TooManyFlushData

EOL = '\r\n'

SMTP_PORT = 25

CMDS = 'QUIT', 'HELO', 'EHLO', 'HELP', 'NOOP', 'RSET'


def is_1xx(reply):
    return reply[:3].isdigit() and reply[0] == '1'


def is_2xx(reply):
    return reply[:3].isdigit() and reply[0] == '2'


def is_3xx(reply):
    return reply[:3].isdigit() and reply[0] == '3'


def is_4xx(reply):
    return reply[:3].isdigit() and reply[0] == '4'


def is_5xx(reply):
    return reply[:3].isdigit() and reply[0] == '5'


def smtplines(fd, bufsize, timeout):
    mem = memory.Memory(bufsize)
    timeout = int(timeout)
    rem = mstart = mend = 0
    while 1:
        # move
        if mstart > 0:
            mem[0:mend-mstart] = mem[mstart:mend]
        # fill
        if not select.select((fd,), (), (), timeout)[0]:
            raise socket.timeout
        mend += mem.read(fd, mend, mem.size - mend)
        if not mend:
            yield ''
            break
        #
        for start, end in xbounds(mem, EOL, 1, 0, mend):
            if mem[end-len(EOL):end] == EOL:
                yield mem[start:end]
                rem = end
            else:
                if (end-start) == mem.size:
                    raise TooBigReply()
        mem[0:mend-rem] = mem[rem:mend]
        mend -= rem


class SMTP_Session(object):

    def __init__(self, host, port=SMTP_PORT, timeout=60, bufsize=4096, maxflush=4096):
        self.host = str(host)
        self.port = int(port)
        self.bufsize = int(bufsize)
        self.timeout = int(timeout)
        socket.setdefaulttimeout(self.timeout)
        self.sock = socket.socket()
        self.sock.settimeout(self.timeout)
        self.lines = smtplines(self.sock.fileno(), self.bufsize, self.timeout)
        self.maxflush = int(maxflush)

    def get_reply(self):
        line = self.lines.next()
        code = line[:3]
        yield line
        if len(code) == 3 and line[3:4] == '-' and code.isdigit():
            for line in self.lines:
                yield line
                if line[3:4] == ' ' or not line:
                    break
                   

    @property
    def is_open(self):
        return self.sock.fileno() >= 0;

    @property
    def can_read(self):
        return bool(select.select((self.sock.fileno(),), (), (), 0)[0])
                
    def connect(self):
        self.sock.connect((self.host, self.port))
        return self.get_reply()

    def close(self):
        sock = self.sock
        try:
            sock.shutdown(socket.SHUT_RDWR)
        except IOError, ex:
            if ex.args[0] != errno.ENOTCONN:
                raise
        finally:
            sock.close()

    def flush(self):
        size  = 0
        while self.can_read:
            chunk = self.sock.recv(self.bufsize)
            size += len(chunk)
            if size > self.maxflush:
                raise TooManyFlushData()
            yield self.sock.recv(self.bufsize)

    def flush_all(self):
        for _ in self.flush():
            pass

    def putcmd(self, cmd, args=None):
        self.sock.sendall('%s%s%s' % (cmd, ' ' if args is None
                                               else ' %s ' % args, EOL))

    def cmd(self, cmd, args=None):
        self.flush_all()
        cmd = str(cmd).upper()
        if cmd not in CMDS:
            raise ValueError("command %r unknown" % cmd)
        self.putcmd(cmd, args)
        return self.get_reply()

    def full_cmd(self, cmd, args=None):
        return ''.join(self.cmd(cmd, args))

    def __enter__(self):
        return self, self.connect()

    def __exit__(self, exc_type, exc_val, exc_trace):
        self.close()
        return 0
@@ -185,16 +185,340 @@
 lushData
+%0Afrom pysec import log%0A%0A__name__ = 'pysec.net.smtp'%0A%0A# set actions%0Alog.register_actions('SMTP_NEW_SESSION',%0A                      'SMTP_CONNECT',%0A                      'SMTP_CLOSE',%0A                      'SMTP_FLUSH',%0A                      'SMTP_CMD',%0A                      'SMTP_PUTCMD',%0A                      'SMTP_REPLY')
 %0A%0AEOL = 
@@ -1768,16 +1768,161 @@
 ject):%0A%0A
+    @log.wrap(log.actions.SMTP_NEW_SESSION,%0A              fields=('host', 'port', 'timeout', 'bufsize', 'maxflush'),%0A              lib=__name__)%0A
     def 
@@ -1969,16 +1969,33 @@
 eout=60,
+%0A                
  bufsize
@@ -2393,16 +2393,84 @@
 flush)%0A%0A
+    @log.wrap(log.actions.SMTP_REPLY, result='reply', lib=__name__)%0A
     def 
@@ -2766,36 +2766,16 @@
    break
-%0A                   
 %0A%0A    @p
@@ -2956,32 +2956,70 @@
  0)%5B0%5D)%0A
+%0A
     
-            
+@log.wrap(log.actions.SMTP_CONNECT, lib=__name__)
 %0A    def
@@ -3109,32 +3109,84 @@
 lf.get_reply()%0A%0A
+    @log.wrap(log.actions.SMTP_CLOSE, lib=__name__)%0A
     def close(se
@@ -3684,16 +3684,68 @@
 fsize)%0A%0A
+    @log.wrap(log.actions.SMTP_FLUSH, lib=__name__)%0A
     def 
@@ -3810,16 +3810,93 @@
   pass%0A%0A
+    @log.wrap(log.actions.SMTP_PUTCMD, fields=('cmd', 'args'), lib=__name__)%0A
     def 
@@ -4058,24 +4058,98 @@
 rgs, EOL))%0A%0A
+    @log.wrap(log.actions.SMTP_CMD, fields=('cmd', 'args'), lib=__name__)%0A
     def cmd(

add log in smtp lib