#!/usr/bin/env python3
"""
Choose a man page to read interactively from a menu with fuzzy search
"""

import argparse
import asyncio
import os
import re

import ptfuzzmenu
from prompt_toolkit import Application
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.bindings.focus import focus_next
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
from prompt_toolkit.layout.containers import VSplit
from prompt_toolkit.layout.layout import Layout
from prompt_toolkit.styles import Style
from prompt_toolkit.widgets import Frame, TextArea

E = KeyPressEvent

PATH = "/usr/share/man"


def generator():
    labelre = re.compile(
        re.escape(PATH)
        + r"/(?P<label>man(?P<section>[0-9]+)/(?P<base>.*))\.[0-9]\S*\.gz"
    )
    for root, _, files in os.walk(PATH):
        for filename in files:
            path = os.path.join(root, filename)
            m = labelre.match(path)
            if not m:
                continue
            yield (m.group("label"), (int(m.group("section")), m.group("base")))


async def man_loader(contents, queue):
    while True:
        item = None
        item = await queue.get()
        while not queue.empty():
            item = await queue.get()
        contents.text = f"Loading {item[1]}..."
        width = 80
        if contents.window.render_info:
            width = str(contents.window.render_info.window_width - 1)
        man = await asyncio.create_subprocess_shell(
            f"MANWIDTH={width} man --encoding=utf-8 {str(item[0])} {item[1]} | col -bh",
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.STDOUT,
        )
        (manpage, _) = await man.communicate()
        contents.text = manpage.decode("utf-8", errors="ignore").replace("\t", "    ")
        queue.task_done()


async def manmenu(no_search: bool):
    items = list(generator())
    items.sort()
    contents = TextArea(text="", multiline=True, wrap_lines=True, read_only=True)
    manloader_queue = asyncio.Queue()
    manloader_task = asyncio.create_task(man_loader(contents, manloader_queue))

    def handle_current(label, item) -> None:
        manloader_queue.put_nowait(item)

    if no_search:
        menu = ptfuzzmenu.VMenu(items=items, handle_current=handle_current)
    else:
        menu = ptfuzzmenu.FuzzMenu(items=items, handle_current=handle_current)
    root_container = VSplit(
        [
            Frame(title="Man pages", body=menu),
            Frame(title="Contents", body=contents),
        ]
    )
    layout = Layout(root_container)
    layout.focus(menu)
    # Choose some strong colors just for show:
    style = Style.from_dict(
        {
            "fuzzmenu.focused fuzzmenu.current": "fg:black bg:white",
            "fuzzmenu.unfocused fuzzmenu.current": "reverse",
            "fuzzmenu.unfocused fuzzmenu.item": "fg:grey bg:black",
        }
    )
    kb = KeyBindings()
    app: Application[None] = Application(
        layout=layout,
        key_bindings=kb,
        full_screen=True,
        style=style,
    )

    @kb.add("tab")
    def tab(event: E) -> None:
        focus_next(event)

    @kb.add("c-c")
    @kb.add("c-d")
    @kb.add("escape", "q")
    def close(event: E) -> None:
        manloader_task.cancel()
        app.exit()

    await app.run_async()


async def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--version", "-V", action="version", version="%(prog)s " + ptfuzzmenu.version()
    )
    parser.add_argument(
        "--no-search",
        action="store_true",
        help="Use a simple menu, with no fuzzy search",
    )
    args = parser.parse_args()
    await manmenu(no_search=args.no_search)


if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())
