# PYTHON_ARGCOMPLETE_OK
# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

import curses
import datetime as dt
import math
import sys
from collections import defaultdict

import trytond.commandline as commandline
import trytond.config as config


def main():
    parser = commandline.get_parser_stat()
    commandline.set_autocomplete(parser)
    options = parser.parse_args()
    config.update_etc(options.configfile)

    import trytond.status as status

    def _main(stdscr):
        global reverse
        stdscr.nodelay(1)
        reverse = True
        processes = {}
        status_pad = curses.newpad(1, 1)
        cache_pad = curses.newpad(1, 1)

        def refresh_status():
            now = dt.datetime.now()
            height, width = stdscr.getmaxyx()

            def expired(process):
                return process['expire'] > now

            def format_status(since, id_, request):
                since = str(dt.timedelta(seconds=int(since)))
                pid, node = id_.split('@', 1)
                if len(node) > 12:
                    node = node[:5] + '…' + node[:6]
                return f"{pid:>5} {node:<12} {since:>18} {request}"
            status_pad.clear()
            status = [format_status(*i) for i in sorted(
                    ((msg['since'], p['id'], msg['request'])
                        for p in filter(expired, processes.values())
                        for msg in p['status']),
                    reverse=reverse)]
            prow = min(len(status) + 1, height // 2)
            pcol = max(max(map(len, status), default=0), width)
            status_pad.resize(len(status) + 1, pcol + 1)
            for i, line in enumerate(status, 1):
                status_pad.addnstr(i, 0, line.ljust(pcol), pcol)
            status_pad.addnstr(
                0, 0, "{pid:>5} {node:^12} {since:>18} {request} ({n})".format(
                    pid="pid",
                    node="node",
                    since="TIME" + ('↑' if reverse else '↓'),
                    request="request",
                    n=len(status),
                    ).upper().ljust(pcol), pcol, curses.A_REVERSE)
            status_pad.noutrefresh(0, 0, 0, 0, prow, width - 1)

            def ratio(cache):
                if cache['hit'] or cache['miss']:
                    return cache['hit'] / (cache['hit'] + cache['miss'])
                return 0

            def format_cache(name, hit, miss, ratio, size):
                return (
                    f"{hit:{size}d} {miss:{size}d} {ratio * 100:6.2f} {name}")

            cache_pad.clear()
            cache_stats = defaultdict(lambda: defaultdict(lambda: 0))
            for p in filter(expired, processes.values()):
                for cache in p['caches']:
                    stats = cache_stats[cache['name']]
                    stats['name'] = cache['name']
                    stats['hit'] += cache['hit']
                    stats['miss'] += cache['miss']
            for cache in cache_stats.values():
                cache['ratio'] = ratio(cache)
            try:
                size = math.ceil(math.log10(
                        max(s['hit'] + s['miss']
                            for s in cache_stats.values())))
            except ValueError:
                size = 1
            size = max(size, 4)
            caches = [format_cache(size=size, **cache) for cache in sorted(
                    cache_stats.values(),
                    key=lambda c: (c['ratio'], c['miss']),
                    reverse=reverse)]
            crow = max(len(caches) + 1, (height - prow))
            ccol = max(max(map(len, caches), default=0), width)
            cache_pad.resize(crow + 1, ccol + 1)
            for i, line in enumerate(caches, 1):
                cache_pad.addnstr(i, 0, line.ljust(ccol), ccol)
            cache_pad.addstr(
                0, 0,
                "{hit:>{size}} {miss:>{size}} {ratio:>6} {name} ({n})".format(
                    size=size,
                    hit="hit",
                    miss="miss",
                    ratio="% " + ('↑' if reverse else '↓'),
                    name="name",
                    n=len(caches),
                    ).upper().ljust(ccol), curses.A_REVERSE)
            cache_pad.noutrefresh(0, 0, prow, 0, height - 1, width - 1)

        def refresh():
            global reverse
            refresh_status()
            stdscr.refresh()

            key = stdscr.getch()
            if key == ord('q'):
                sys.exit()
            elif key == ord('r'):
                reverse = not reverse
                refresh()

        def update(data=None):
            if data:
                pid = data['id']
                data['expire'] = dt.datetime.now() + dt.timedelta(seconds=10)
                processes[pid] = data
            refresh()
        refresh()
        return status.listen(config.get('database', 'path'), update)

    if not curses.wrapper(_main):
        sys.stderr.write("status not supported on this platform\n")


if __name__ == '__main__':
    main()
