import logging

import sys
import os

import threading
import SocketServer

import ssl
import socket

from . import *

EMAIL = """From: "Bob Example" <bob@example.org>
To: Alice Example <alice@example.com>
Cc: theboss@example.com
Date: Tue, 15 January 2008 16:02:43 -0500
Subject: Test message

Hello Alice.
This is a test message with 5 header fields and 4 lines in the message body.
Your friend,
Bob\r\n"""

class POPListener(object):

    # Once the TCP connection has been established, the POP server initiates 
    # the conversation with +OK message. However, if the client connects
    # to a port that is not 110, there is no way for the proxy to know that
    # POP is the protocol until the client sends a message.
    def taste(self, data, dport):

        commands = [ 'QUIT', 'STAT', 'LIST', 'RETR', 'DELE', 'NOOP', 'RSET', 
                'TOP', 'UIDL', 'USER', 'PASS', 'APOP' ]

        confidence = 1 if dport == 110 else 0

        data = data.lstrip()
        for command in commands:
            if data.startswith(command):
                confidence += 2

        return confidence

    def __init__(self, 
            config, 
            name='POPListener', 
            logging_level=logging.INFO, 
            ):

        self.logger = logging.getLogger(name)
        self.logger.setLevel(logging_level)

        self.config = config
        self.name = name
        self.local_ip = config.get('ipaddr')
        self.server = None
        self.name = 'POP'
        self.port = self.config.get('port', 110)

        self.logger.debug('Starting...')

        self.logger.debug('Initialized with config:')
        for key, value in config.iteritems():
            self.logger.debug('  %10s: %s', key, value)

    def start(self):
        self.logger.debug('Starting...')
        
        self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler)

        if self.config.get('usessl') == 'Yes':
            self.logger.debug('Using SSL socket')

            keyfile_path = 'listeners/ssl_utils/privkey.pem'
            keyfile_path = ListenerBase.abs_config_path(keyfile_path)
            if keyfile_path is None:
                self.logger.error('Could not locate %s', keyfile_path)
                sys.exit(1)

            certfile_path = 'listeners/ssl_utils/server.pem'
            certfile_path = ListenerBase.abs_config_path(certfile_path)
            if certfile_path is None:
                self.logger.error('Could not locate %s', certfile_path)
                sys.exit(1)

            self.server.socket = ssl.wrap_socket(self.server.socket, keyfile='privkey.pem', certfile='server.pem', server_side=True, ciphers='RSA')

        self.server.logger = self.logger
        self.server.config = self.config

        self.server_thread = threading.Thread(target=self.server.serve_forever)
        self.server_thread.daemon = True
        self.server_thread.start()

    def stop(self):
        self.logger.debug('Stopping...')
        if self.server:
            self.server.shutdown()
            self.server.server_close()

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):

    def handle(self):

        # Timeout connection to prevent hanging
        self.request.settimeout(int(self.server.config.get('timeout', 10)))

        try:

            self.request.sendall("+OK FakeNet POP3 Server Ready\r\n")
            while True:

                data = self.request.recv(1024)

                if not data:
                    break

                elif len(data) > 0:

                    for line in data.split("\r\n"):

                        if line and len(line) > 0:

                            if ' ' in line:
                                cmd, params = line.split(' ', 1)
                            else:
                                cmd, params = line, ''

                            handler = getattr(self, 'pop_%s' % (cmd.upper()), self.pop_DEFAULT)
                            handler(cmd, params)

        except socket.timeout:
            self.server.logger.warning('Connection timeout')

        except socket.error as msg:
            self.server.logger.error('Error: %s', msg.strerror or msg)

        except Exception, e:
            self.server.logger.error('Error: %s', e)

    def pop_DEFAULT(self, cmd, params):
        self.server.logger.info('Client issued an unknown command %s %s', cmd, params)
        self.request.sendall("-ERR Unknown command\r\n")

    def pop_APOP(self, cmd, params):

        if ' ' in params:
            mailbox_name, digest = params.split(' ', 1)
            self.server.logger.info('Client requests access to mailbox %s', mailbox_name)

            self.request.sendall("+OK %s's maildrop has 2 messages (320 octets)\r\n" % mailbox_name)

        else:
            self.server.logger.info('Client sent invalid APOP command: APOP %s', params)
            self.request.sendall("-ERR\r\n")

    def pop_RPOP(self, cmd, params):

        mailbox_name = params
        self.server.logger.info('Client requests access to mailbox %s', mailbox_name)

        self.request.sendall("+OK %s's maildrop has 2 messages (320 octets)\r\n" % mailbox_name)

    def pop_USER(self, cmd, params):

        self.server.logger.info('Client user: %s', params)

        self.request.sendall("+OK User accepted\r\n")

    def pop_PASS(self, cmd, params):

        self.server.logger.info('Client password: %s', params)

        self.request.sendall("+OK Pass accepted\r\n")

    def pop_STAT(self, cmd, params):

        self.request.sendall("+OK 2 320\r\n")

    def pop_LIST(self, cmd, params):

        # List all messages
        if params == '':

            self.request.sendall("+OK 2 messages (320 octets)\r\n")
            self.request.sendall("1 120\r\n")
            self.request.sendall("2 200\r\n")
            self.request.sendall(".\r\n")

        # List individual message
        else:
            self.request.sendall("+OK %d 200\r\n" % params)
            self.request.sendall(".\r\n")


    def pop_RETR(self, cmd, params):

        self.server.logger.info('Client requests message %s', params)

        self.request.sendall("+OK 120 octets\r\n")
        self.request.sendall(EMAIL + "\r\n")
        self.request.sendall(".\r\n")

    def pop_DELE(self, cmd, params):

        self.server.logger.info('Client requests message %s to be deleted', params)

        self.request.sendall("+OK message %s deleted\r\n", params)

    def pop_NOOP(self, cmd, params):
        self.request.sendall("+OK\r\n")

    def pop_RSET(self, cmd, params):
        self.request.sendall("+OK maildrop has 2 messages (320 octets)\r\n")

    def pop_TOP(self, cmd, params):
        self.request.sendall("+OK\r\n")
        self.request.sendall("1 120\r\n")
        self.request.sendall("2 200\r\n")
        self.request.sendall(".\r\n")

    def pop_UIDL(self, cmd, params):

        if params == '':
            self.request.sendall("+OK\r\n")
            self.request.sendall("1 whqtswO00WBw418f9t5JxYwZa\r\n")
            self.request.sendall("2 QhdPYR:00WBw1Ph7x7a\r\n")
            self.request.sendall(".\r\n")

        else:
            self.request.sendall("+OK %s QhdPYR:00WBw1Ph7x7\r\n", params)

    def pop_QUIT(self, cmd, params):

        self.request.sendall("+OK FakeNet POP3 server signing off\r\n")


class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

###############################################################################
# Testing code
def test(config):

    import poplib

    logger = logging.getLogger('POPListenerTest')

    server = poplib.POP3_SSL('localhost', config.get('port', 110))

    logger.info('Authenticating.')
    server.user('username')
    server.pass_('password')

    logger.info('Listing and retrieving messages.')
    print server.list()
    print server.retr(1)
    server.quit()

def main():
    logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG)

    config = {'port': '110', 'usessl': 'Yes', 'timeout': 30 }

    listener = POPListener(config)
    listener.start()

    ###########################################################################
    # Run processing
    import time

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass

    ###########################################################################
    # Run tests
    test(config)

if __name__ == '__main__':
    main()