#!/usr/bin/env python3
# hi -- show recent IRC highlights
import argparse
import datetime
import fcntl
import io
import re
import os
import platformdirs
import subprocess
import sys
import textwrap

IRC_HOST = "star"

def tty_width():
    if cols := os.environ.get("COLUMNS"):
        return int(cols)
    if sys.stdout.isatty():
        return os.get_terminal_size().columns
    return 80

def err(msg):
    print("\033[30;41m%s\033[m" % msg, file=sys.stderr)

def download_log(remote_host, remote_path, local_path):
    with open(local_path, "ab") as fh:
        fcntl.flock(fh, fcntl.LOCK_EX)

        try:
            osize = os.stat(local_path).st_size
            rcmd = f"tail -c +{osize+1} '{remote_path}'"
        except FileNotFoundError:
            osize = 0
            rcmd = f"cat '{remote_path}'"

        subprocess.run(["ssh", remote_host, rcmd],
                       stdout=fh,
                       check=True)

        nsize = os.stat(local_path).st_size
        return nsize-osize, osize

def parse_log(fh):
    for line in fh:
        date, wname, text = line.split("\t", 2)
        date = datetime.datetime.strptime(date, "%Y-%m-%d %H:%M:%S %z")
        wname = re.sub(r"^<(.+)>$", r"\1", wname)
        head = ""
        text = text.rstrip()

        if "#" in wname:
            # Channel messages
            name = "?"
            if m := re.match(r"^<-i-> Mode (\S+) \[(.+?)\] by (\S+)$", text):
                # /mode
                name = m[3]
                head = "-- %s:" % name
                text = "Mode %s [%s]" % (m[1], m[2])
            elif m := re.match(r"^<(\S+)> (.*)$", text):
                # Regular
                name = m[1]
                head = "(%s)" % name
                text = m[2]
            elif m := re.match(r"^\* (\S+) (.*)$", text):
                # /me
                name = m[1]
                head = " * %s" % name
                text = m[2]
            else:
                err(f"Unmatched: {wname=} {text=}")
                continue
        elif wname.startswith("server."):
            server = wname.removeprefix("server.")
            if m := re.match(r"^<(\S+)> (\S+) \S+: (.*)$", text):
                # /notice
                name = m[2]
                head = "-%s-" % name
                text = m[3]
                if re.match(r"^(nick|chan)serv$", name, re.I):
                    continue
                if re.match(r"^ARUTHA", name, re.I):
                    continue
            elif text.startswith("<%s>" % server):
                # server text like /quote help foo
                name = server
                head = "Server:"
                text = text.removeprefix("<%s>" % server).removeprefix(" ")
                continue
            else:
                err(f"Unmatched: {server=} {text=}")
                continue
        elif "." in wname:
            err(f"Unmatched: {wname=} {text=}")
            continue
        else:
            server = wname
            if m := re.match(r"^<(\S+)> (.*)$", text):
                # Private messages (/msg)
                name = m[1]
                head = "Msg: <%s>" % name
                text = m[2]
            else:
                err(f"Unmatched: {wname=} {text=}")
                continue
            #if m := re.match(r"^<(\S+)> \1: (.*)$", text):
            #    # /notice
            #    name = "Msg: -%s-" % m[1]
            #    text = m[2]
            #elif m := re.match(r"^<(\S+)> \1 (.*)$", text):
            #    # /me
            #    name = "Msg: * %s" % m[1]
            #    text = m[2]
            #elif m := re.match(r"^<(\S+)> (.*)$", text):
            #    # /msg
            #    print("GOT MSG: {text=}")
            #    name = "Msg: (%s)" % m[1]
            #    text = m[2]

        yield (date, wname, head, text)

def sgr(arg, text):
    return "\033[%sm%s\033[m" % (arg, text)

def vis(text):
    return re.sub(r"[\x00-\x1F\x80-\x9F]",
                  lambda m: "\033[2m<%02X>\033[m" % ord(m[0]),
                  text)

def print_log(fh, unseen):
    SGR_DATE = "1;31"
    SGR_WNAME = "1;32"
    SGR_WTAIL = "32"
    SGR_TIME = "38;5;244"

    columns = tty_width() - len("   HH:MM | ") - len(" ")
    lastdate = None
    lastwname = None

    for date, wname, name, text in parse_log(fh):
        if wname != lastwname:
            hdrdate = date.strftime("%b %-d")
            hdrname = wname
            hdrtail = ""

            if m := re.match(r"^(.+)(#.+)$", wname):
                hdrname = m[2]
                hdrtail = "(%s)" % m[1]

            if unseen:
                hdrdate = sgr(SGR_DATE, hdrdate)
                hdrname = sgr(SGR_WNAME, hdrname)
                hdrtail = sgr(SGR_WTAIL, hdrtail)

            if lastwname:
                print()

            print(hdrdate, hdrname, hdrtail)

        lines = textwrap.wrap(name + " " + text, columns)
        sdate = date.strftime("%H:%M")
        for line in lines:
            print(" "*2, sgr(SGR_TIME, sdate), sgr(SGR_TIME, "│"), vis(line))
            sdate = " " * len(sdate)

        lastdate = date
        lastwname = wname

parser = argparse.ArgumentParser()
parser.add_argument("-q", "--quiet",
                        action="store_true",
                        help="don't show old messages")
parser.add_argument("-n", "--lines",
                        type=int,
                        default=0,
                        help="how many old messages to show")
args = parser.parse_args()

os.umask(0o077)
if os.getuid() == 0:
    exit("hi: will not run as root")

rhost = IRC_HOST
rpath = "irclogs/perl.highmon.log"

# Keep state in sync regardless of where it is run from.
if os.uname().nodename != rhost:
    print("hi: running on %s" % rhost)
    exit(subprocess.call(["on", "-H", rhost, *sys.argv]))

cachepath = platformdirs.user_cache_path("nullroute.eu.org/highlights.txt")
delta, pos = download_log(rhost, rpath, cachepath)
if delta:
    with open(cachepath, "rb") as fh:
        # In case `hi` hasn't been run for a very long time on this system,
        # limit output to ~32k so that it doesn't kill the terminal.
        if delta > 32*1024:
            fh.seek(-32*1024, os.SEEK_END)
            fh.readline()
        else:
            fh.seek(pos)
        print_log(io.TextIOWrapper(fh), unseen=True)
elif args.quiet:
    print("hi: no new messages")
else:
    with os.popen("tail -n %d '%s'" % (args.lines, cachepath), "r") as fh:
        print_log(fh, unseen=False)
    print("hi: no new messages")
