from twisted.protocols.basic import LineReceiver
import sys
import json
import logging
from logging.handlers import TimedRotatingFileHandler
import platform
from os import chmod
from shutil import copyfile

try:
    # Python 2: "unicode" is built-in
    unicode
except NameError:
    unicode = str

logger = logging.getLogger(__name__)
system = platform.system()

def setup_logging(logfile=None, backup_count=3): 
    logger = logging.getLogger()
    logger.handlers.clear()

    formatter = logging.Formatter(
        "[%(asctime)s] %(levelname)s [agent %(process)d/%(thread)d] "
        "%(module)s.%(funcName)s:%(lineno)d] %(message)s",
        "%d/%b/%Y %H:%M:%S",
    )

    if logfile != None:
        handler = TimedRotatingFileHandler(
            filename=logfile,
            when="midnight",
            backupCount=backup_count,
            encoding="utf-8",
            delay=True,
        )
    else:
        handler = logging.StreamHandler(sys.stderr)

    handler.setFormatter(formatter)
    logger.addHandler(handler)
    return logger


def setup_logging_timed(logfile: str, level: str, backup_count: int):
    handler = TimedRotatingFileHandler(
        filename=str(log_path),
        when="midnight",
        interval=1,
        backupCount=backup_count,
        encoding="utf-8",
        utc=False,          # use local time (Budapest)
        delay=True,         # create file only when first log is emitted
    )

    formatter = logging.Formatter(
        format="[%(asctime)s] %(levelname)s [agent %(process)d/%(thread)d] "
               "%(module)s.%(funcName)s:%(lineno)d] %(message)s",
        datefmt="%d/%b/%Y %H:%M:%S",
    )
    handler.setFormatter(formatter)
    root = logging.getLogger()
    root.setLevel(level)

    # Avoid duplicate handlers if setup called multiple times
    root.handlers.clear()
    root.addHandler(handler)
    return root


class SerialLineReceiverBase(LineReceiver, object):
    MAX_LENGTH = 1024*1024*128

    def __init__(self, *args, **kwargs):
        if system == "FreeBSD":
            self.delimiter = b'\n'
        else:
            self.delimiter = b'\r'
        super(SerialLineReceiverBase, self).__init__(*args, **kwargs)

    def send_response(self, response, args):
 #       logger.debug("send_response %s %s" % (response, args))
        self.transport.write(json.dumps({'response': response,
                                         'args': args}) + '\r\n')

    def send_command(self, command, args):
        self.transport.write(json.dumps({'command': command,
                                         'args': args}) + '\r\n')

    def handle_command(self, command, args):
        raise NotImplementedError("Subclass must implement abstract method")

    def handle_response(self, response, args):
        raise NotImplementedError("Subclass must implement abstract method")

    def lineReceived(self, data):
#        logger.debug("lineReceived: %s", data)
        if (isinstance(data, unicode)):
            data = data.strip('\0')
        else:
            data = data.strip(b'\0')
        try:
            data = json.loads(data)
            args = data.get('args', {})
            if not isinstance(args, dict):
                args = {}
            command = data.get('command', None)
            response = data.get('response', None)
 #           logger.debug('[serial] valid json: %s' % (data, ))
        except (ValueError, KeyError) as e:
 #           logger.error('[serial] invalid json: %s (%s)' % (data, e))
            self.clearLineBuffer()
            return

        if command is not None and isinstance(command, unicode):
#            logger.debug('received command: %s (%s)' % (command, args[:10]))
            try:
                self.handle_command(command, args)
            except Exception as e:
                logger.exception("Unhandled exception during line recived: ")
        elif response is not None and isinstance(response, unicode):
            logger.debug('received reply: %s (%s)' % (response, args))
            self.clearLineBuffer()
            self.handle_response(response, args)


def copy_file(src, dst, overw=False, mode=None):
    exists = False
    copyed = False
    try: 
        open(dst, 'r').close()
        exists = True
    except FileNotFoundError: 
        pass

    try:
        if not exists or overw:
            copyfile(src, dst)
            copyed = True
            logger.debug('copy_file:(%s, %s, %s, %s)' % (src, dst, overw, mode))
        if mode != None:
            chmod(dst, mode)
            logger.debug('chmod:(%s, %s)' % (dst, mode))
    except Exception as e:
           logger.exception('Unhandled %s during copy_file:(%s, %s, %s, %s)' % (e, src, dst, overw, mode))

    return copyed


