#!/usr/bin/python3

import threading
import asyncio
import os
import sys

LOG_MESSAGE = 0
LOG_ERROR = 1
LOG_WARNING = 2
LOG_DEBUG = 3

class LOG:
    
    class singleton(object):
        loop = asyncio.new_event_loop()
        lock = threading.Lock()
        def __init__(self):
            t = threading.Thread(daemon=True, target=self.loop.run_forever)
            t.start()
            
        def __call__(self):
            return self
    singleton = singleton()
    loop = singleton().loop
    lock = singleton().lock

    def __init__(self, level=None, prefix=None, parent=None):
        if level != None:
            self.level = level
        elif parent:
            self.level = parent.level
        else:
            level = LOG_WARNING
        self.prefix = ''.join(filter(None, [parent and parent.prefix,
                                            prefix]))

    def MESSAGE(self, *args, level=LOG_MESSAGE):
        if self.level >= level:
            with self.lock:
                buf = ' '.join(map(str, args))
                while '\n' in buf:
                    line, buf = buf.split('\n', 1)
                    if self.prefix != None:
                        print(self.prefix, end='', file=sys.stderr)
                    print(line, file=sys.stderr, flush=True)
                if len(buf):
                    if self.prefix != None:
                        print(self.prefix, end='', file=sys.stderr)
                    print(buf, file=sys.stderr, flush=True)
        
    def ERROR(self, *args):
        self.MESSAGE(*args, level=LOG_ERROR)

    def WARNING(self, *args):
        self.MESSAGE(*args, level=LOG_WARNING)

    def DEBUG(self, *args):
        self.MESSAGE(*args, level=LOG_DEBUG)

    def makefile(self, level=LOG_WARNING, encoding=None):
        loop = self.singleton().loop
        class Reader:

            def __init__(self, log, fd):
                self.log = log
                self.fd = fd
                self.buf = bytearray()
                self.mutex = threading.Lock()
                def decode(b):
                    if encoding == None:
                        return b
                    else:
                        return b.decode(encoding)
                self.decode = decode
                    

            def __call__(self):
                with self.mutex:
                    buf = os.read(self.fd, 4096)
                    self.buf.extend(buf)
                    while True:
                        i = self.buf.find(b'\n')
                        if i < 0: break
                        self.log.MESSAGE(self.decode(self.buf[0:i]),
                                         level=level)
                        self.buf = self.buf[i+1:]

            def flush(self):
                with self.mutex:
                    while True:
         
                        buf = os.read(self.fd, 4096)
                        if len(buf) == 0: break
                        self.buf.extend(buf)
                        while True:
                            i = self.buf.find(b'\n')
                            if i < 0: break
                            self.log.MESSAGE(self.decode(self.buf[0:i]),
                                             level=level)
                            self.buf = self.buf[i+1:]
                if len(self.buf) > 0:
                    self.log.MESSAGE(self.decode(self.buf), level=level)
    
        class MakeFile:
            def __init__(self, log):
                self.rpipe, self.wpipe = os.pipe()
                self.reader = Reader(log, self.rpipe)
                loop.add_reader(self.rpipe, self.reader)

            def fileno(self):
                return self.wpipe

            def __del__(self):
                loop.remove_reader(self.rpipe)
                os.close(self.wpipe)
                self.reader.flush()
                
        return MakeFile(self)