#!/usr/bin/env python3

import argparse
import asyncio
import struct
import logging
import re

from sipyco.pc_rpc import Server
from sipyco import common_args
from sipyco.logging_tools import log_with_name
from sipyco.asyncio_tools import SignalHandler
from sipyco.keepalive import async_open_connection

from artiq.coredevice.comm_mgmt import Request, Reply

logger = logging.getLogger(__name__)

def get_argparser():
    parser = argparse.ArgumentParser(
        description="ARTIQ controller for core device logs")
    common_args.verbosity_args(parser)
    common_args.simple_network_args(parser, 1068)
    parser.add_argument("--simulation", action="store_true",
                        help="Simulation - does not connect to device")
    parser.add_argument("core_addr", metavar="CORE_ADDR",
                        help="hostname or IP address of the core device")
    return parser


class PingTarget:
    def ping(self):
        return True


async def get_logs_sim(host):
    while True:
        await asyncio.sleep(2)
        log_with_name("firmware.simulation", logging.INFO, "hello " + host)


async def get_logs(host):
    try:
        reader, writer = await async_open_connection(
            host,
            1380,
            after_idle=1,
            interval=1,
            max_fails=3,
        )
        writer.write(b"ARTIQ management\n")
        endian = await reader.readexactly(1)
        if endian == b"e":
            endian = "<"
        elif endian == b"E":
            endian = ">"
        else:
            raise IOError("Incorrect reply from device: expected e/E.")
        writer.write(struct.pack("B", Request.PullLog.value))
        await writer.drain()

        while True:
            length, = struct.unpack(endian + "l", await reader.readexactly(4))
            log = await reader.readexactly(length)

            for line in log.decode("utf-8").splitlines():
                m = re.match(r"^\[.+?\] (TRACE|DEBUG| INFO| WARN|ERROR)\((.+?)\): (.+)$", line)
                levelname = m.group(1)
                if levelname == 'TRACE':
                    level = logging.TRACE
                elif levelname == 'DEBUG':
                    level = logging.DEBUG
                elif levelname == ' INFO':
                    level = logging.INFO
                elif levelname == ' WARN':
                    level = logging.WARN
                elif levelname == 'ERROR':
                    level = logging.ERROR
                name = 'firmware.' + m.group(2).replace('::', '.')
                text = m.group(3)
                log_with_name(name, level, text)
    except asyncio.CancelledError:
        raise
    except:
        logger.error("Logging connection terminating with exception", exc_info=True)


def main():
    args = get_argparser().parse_args()
    common_args.init_logger_from_args(args)

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
        signal_handler = SignalHandler()
        signal_handler.setup()
        try:
            get_logs_task = asyncio.ensure_future(
                get_logs_sim(args.core_addr) if args.simulation else get_logs(args.core_addr),
                loop=loop)
            try:
                server = Server({"corelog": PingTarget()}, None, True)
                loop.run_until_complete(server.start(common_args.bind_address_from_args(args), args.port))
                try:
                    _, pending = loop.run_until_complete(asyncio.wait(
                        [loop.create_task(signal_handler.wait_terminate()),
                         loop.create_task(server.wait_terminate()),
                         get_logs_task],
                        return_when=asyncio.FIRST_COMPLETED))
                    for task in pending:
                        task.cancel()
                finally:
                    loop.run_until_complete(server.stop())
            finally:
                pass
        finally:
            signal_handler.teardown()
    finally:
        loop.close()

if __name__ == "__main__":
    main()
