"""pyzmq log watcher.

Easily view log messages published by the PUBHandler in zmq.log.handlers

Designed to be run as an executable module - try this to see options:
    python -m zmq.log -h

Subscribes to the '' (empty string) topic by default which means it will work
out-of-the-box with a PUBHandler object instantiated with default settings.
If you change the root topic with PUBHandler.setRootTopic() you must pass
the value to this script with the --topic argument.

Note that the default formats for the PUBHandler object selectively include
the log level in the message. This creates redundancy in this script as it
always prints the topic of the message, which includes the log level.
Consider overriding the default formats with PUBHandler.setFormat() to
avoid this issue.

"""

# encoding: utf-8

# Copyright (C) PyZMQ Developers
# Distributed under the terms of the Modified BSD License.

import argparse
from datetime import datetime

import zmq

parser = argparse.ArgumentParser('ZMQ Log Watcher')
parser.add_argument('zmq_pub_url', type=str, help='URL to a ZMQ publisher socket.')
parser.add_argument(
    '-t',
    '--topic',
    type=str,
    default='',
    help='Only receive messages that start with this topic.',
)
parser.add_argument(
    '--timestamp', action='store_true', help='Append local time to the log messages.'
)
parser.add_argument(
    '--separator',
    type=str,
    default=' | ',
    help='String to print between topic and message.',
)
parser.add_argument(
    '--dateformat',
    type=str,
    default='%Y-%d-%m %H:%M',
    help='Set alternative date format for use with --timestamp.',
)
parser.add_argument(
    '--align',
    action='store_true',
    default=False,
    help='Try to align messages by the width of their topics.',
)
parser.add_argument(
    '--color',
    action='store_true',
    default=False,
    help='Color the output based on the error level. Requires the colorama module.',
)
args = parser.parse_args()


if args.color:
    import colorama

    colorama.init()
    colors = {
        'DEBUG': colorama.Fore.LIGHTCYAN_EX,
        'INFO': colorama.Fore.LIGHTWHITE_EX,
        'WARNING': colorama.Fore.YELLOW,
        'ERROR': colorama.Fore.LIGHTRED_EX,
        'CRITICAL': colorama.Fore.LIGHTRED_EX,
        '__RESET__': colorama.Fore.RESET,
    }
else:
    colors = {}


ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.subscribe(args.topic.encode("utf8"))
sub.connect(args.zmq_pub_url)

topic_widths: dict[int, int] = {}

while True:
    try:
        if sub.poll(10, zmq.POLLIN):
            topic, msg = sub.recv_multipart()
            topics = topic.decode('utf8').strip().split('.')

            if args.align:
                topics.extend(' ' for extra in range(len(topics), len(topic_widths)))
                aligned_parts = []
                for key, part in enumerate(topics):
                    topic_widths[key] = max(len(part), topic_widths.get(key, 0))
                    fmt = ''.join(('{:<', str(topic_widths[key]), '}'))
                    aligned_parts.append(fmt.format(part))

            if len(topics) == 1:
                level = topics[0]
            else:
                level = topics[1]

            fields = {
                'msg': msg.decode('utf8').strip(),
                'ts': (
                    datetime.now().strftime(args.dateformat) + ' '
                    if args.timestamp
                    else ''
                ),
                'aligned': (
                    '.'.join(aligned_parts)
                    if args.align
                    else topic.decode('utf8').strip()
                ),
                'color': colors.get(level, ''),
                'color_rst': colors.get('__RESET__', ''),
                'sep': args.separator,
            }
            print('{ts}{color}{aligned}{sep}{msg}{color_rst}'.format(**fields))
    except KeyboardInterrupt:
        break

sub.disconnect(args.zmq_pub_url)
if args.color:
    print(colorama.Fore.RESET)
