import asyncore
import socket
import errno
import re
import circularlog
import logging


class LineHandler(asyncore.dispatcher_with_send):

    def __init__(self, sock):
        self.buffer = b""
        asyncore.dispatcher_with_send.__init__(self, sock)
        self.crlf = 0

    def handle_read(self):
        data = self.recv(8192)
        if data:
            parts = data.split(b"\n")
            if len(parts) == 1:
                self.buffer += data
            else:
                self.handle_line((self.buffer + parts[0]).decode('ascii'))
                for part in parts[1:-1]:
                    if part[-1] == b"\r":
                        self.crlf = True
                        part = part[:-1]
                    else:
                        self.crlf = False
                    self.handle_line(part.decode('ascii'))
                self.buffer = parts[-1]

    def send_line(self, line):
        self.send(line.encode('ascii') + (b"\r\n" if self.crlf else b"\n"))
        
    def handle_line(self, line):
        '''
        test: simple echo handler
        '''
        self.send_line("> " + line)


class LineServer(asyncore.dispatcher):

    def __init__(self, host, port, lineHandlerClass):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)
        self.lineHandlerClass = lineHandlerClass

    def handle_accept(self):
        pair = self.accept()
        if pair is not None:
            sock, addr = pair
            print("Incoming connection from %s" % repr(addr))
            handler = self.lineHandlerClass(sock)

    def loop(self):
        asyncore.loop()


class Disconnected(Exception):
    pass


class LineClient(object):

    def __init__(self, host_port, announcement=None, filter_ascii=False, ridername="r"):
        self.host_port = host_port
        self.filter_ascii = filter_ascii
        self.announcement = announcement
        self.circular = circularlog.Rider(ridername)
        self.connected = False
        
    def connect(self):
        logging.info("connect to %s %s", "%s:%d" % self.host_port, getattr(self, 'name', '?'))
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(self.host_port)
        self.connected = True
        self.buffer = [b""]
        if self.announcement:
            self.send_line('\n'.join(self.announcement))
         
    def get_line(self):
        if not self.connected:
            logging.info("connect for get_line")
            self.connect()
        while len(self.buffer) <= 1:
            self.socket.setblocking(0)
            try:
                data = self.socket.recv(1024)
            except socket.error as e:
                err = e.args[0]
                if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
                    return None
                raise e
            if data == "":
                print(self.buffer, '<')
                self.close()
                raise Disconnected("disconnected")
            self.socket.setblocking(1)
            data = data.split(b'\n')
            self.buffer[0] += data[0]
            for p in data[1:]:
                self.buffer.append(p)
        line = self.buffer.pop(0).decode('ascii')
        if len(line) > 0 and line[-1] == '\r':
            line = line[0:-1]
        self.circular.put("<", line)
        # print '<', line
        if self.filter_ascii:
            # replace non ascii characters
            line = re.sub(r'[^\x00-\x7E]+','?', line)
        return line

    def send_line(self, line):
        if not self.connected:
            logging.info("connect for cmd: %s", line)
            self.connect()
        # print '>', line
        self.circular.put(">", line)
        self.socket.sendall(line.encode('ascii') + b'\n')
        
    def close(self):
        self.socket.close()
        self.connected = False


if __name__ == "__main__":
    server = LineServer("localhost", 9999, LineHandler)
    server.loop()
