
import time
import sys
import traceback
import os
import platform
import json
from queue import Queue
from basepy.common.log import LoggerLevel, LogRecord, BaseHandler
from basepy.network.connection import BlockingConnectionPool
import socket

class StdoutHandler(BaseHandler):
    terminator = '\n'
    def __init__(self, stream=None, format=None, level="DEBUG", **kwargs):
        if stream is None:
            stream = sys.stdout
        self.stream = stream
        self.format_str = "[{created}] [{hostname}.{process}] [{level}] [{message}]"
        self.level = level
        self.levelno = LoggerLevel.get_levelno(self.level, 0)


    def flush(self):
        if self.stream and hasattr(self.stream, "flush"):
            self.stream.flush()

    def emit(self, record):
        try:
            msg = self.make_message(record)
            stream = self.stream
            stream.write(msg + self.terminator)
            self.flush()
        except Exception:
            self.handle_error(record)

    def make_message(self, record):
        data = record.to_dict()
        data['created'] = time.strftime("%Y-%m-%d %H:%M:%S %z", time.localtime(data['created']))
        extra_data = data.pop('data')
        msg = self.format_str.format(**data)
        extra = ' '.join(map(lambda x: "[{} = {}]".format(x[0], json.dumps(x[1])), extra_data.items()))
        if extra:
            msg = ' '.join([msg, extra])
        return msg

    def __repr__(self):
        level = ""
        name = getattr(self.stream, 'name', '')
        name = str(name)
        if name:
            name += ' '
        return '<%s %s(%s)>' % (self.__class__.__name__, name, level)

class SocketHandler(BaseHandler):
    terminator = '\n'
    def __init__(self, host="127.0.0.1", port=514, connection_type="TCP", level="DEBUG", **kwargs):
        self.host = host
        self.port = port
        self.level = level
        if connection_type.upper() not in ("TCP", "UDP"):
            raise ValueError("connection_type must be one of ['TCP', 'UDP'].")
        self.connection_type = connection_type.upper()
        self.levelno = LoggerLevel.get_levelno(self.level, 0)
        if connection_type.upper() in ['TCP', 'UNIXSOCKET']:
            self.tcp_pool = BlockingConnectionPool(max_connections=4, timeout=6, host=self.host,
                port=self.port)
            self.udp_socket = None
        elif connection_type.upper() in ['UDP']:
            self.tcp_pool = None
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)


    def flush(self):
        pass

    def _write_tcp(self, data):
        conn = self.tcp_pool.get_connection()
        conn.write(data)


    def _write_udp(self, data):
        if len(data) > 65500:
            raise ValueError('data is to large for udp, {} must < 65500.'%(len(data)))
        self.udp_socket.sendto(data, (self.host, self.port))

    def _write(self, data):
        if self.connection_type.upper() == "TCP":
            self._write_tcp(data)
        elif self.connection_type.upper() == "UDP":
            self._write_udp(data)

    def emit(self, record):
        try:
            msg = "{}{}".format(json.dumps(record.to_dict()), self.terminator)
            self._write(msg.encode("utf-8"))
        except Exception:
            self.handle_error(record)


    def __repr__(self):
        return '<%s [%s:%s(%s)]>' % (self.__class__.__name__, self.host, self.port, self.level)


class Logger(object):
    handler_class_map = {
        'stdout': StdoutHandler,
        'socket': SocketHandler
    }
    def __init__(self, name="", **kwargs):
        self.name = name
        self.handlers = []
        self.queue = None
        self.queued_handlers = []
        self.dev_mode = True
        self.hostname = platform.node()

    def init(self, config):
        self.init_queue()
        handlers = config['handlers']
        for handler in handlers:
            conf = config.get(handler, {}).to_dict()
            handler_type = conf.pop('handler_type', '')
            if handler_type not in self.handler_class_map:
                continue
            self.add(handler_type, **conf)

    def init_queue(self):
        self.queue = Queue()

    def add(self, handler, level="DEBUG", log_format=None, queue=False, **kwargs):
        h_cls = self.handler_class_map.get(handler)
        if not h_cls:
            raise Exception('no handler class for {}'.format(handler))
        h = h_cls(format=log_format, queue=queue, level=level, **kwargs)
        if queue:
            self.queued_handlers.append(h)
        else:
            self.handlers.append(h)

    def clear(self):
        self.handlers = []
        self.queued_handlers = []

    def _filter_handlers(self, level):
        levelno = LoggerLevel.get_levelno(level)
        handlers = list(filter(lambda x: levelno >= x.levelno, self.handlers))
        queued_handlers = list(filter(lambda x: levelno >= x.levelno, self.queued_handlers))
        return handlers, queued_handlers

    def log(self, level, message, args, kwargs):
        handlers, queued_handlers = self._filter_handlers(level)
        if len(handlers) + len(queued_handlers) == 0:
            return None

        record = LogRecord(self.name, level, message, args, None, **kwargs)
        if len(self.handlers) > 0:
            for handler in self.handlers:
                handler.emit(record)

        if len(self.queued_handlers) > 0:
            self.queue.put(record)


    def debug(self, message, *args, **kwargs):
        if self.dev_mode:
            self.log('DEBUG', message, args, kwargs)

    def info(self, message, *args, **kwargs):
        self.log('INFO', message, args, kwargs)

    def warning(self, message, *args, **kwargs):
        self.log('WARNING', message, args, kwargs)

    def error(self, message, *args, **kwargs):
        self.log('ERROR', message, args, kwargs)

    def critical(self, message, *args, **kwargs):
        self.log('CRITICAL', message, args, kwargs)

    def exception(self, message, *args, exc_info=True, **kwargs):
        self.error(message, *args, exc_info=exc_info, **kwargs)

logger = Logger("root")