#!/usr/bin/env python3
# pylint: disable=invalid-name,missing-docstring,consider-using-f-string
#
# Copyright 2023 Richard Hughes <richard@hughsie.com>
#
# SPDX-License-Identifier: LGPL-2.1-or-later

import os
import sys
import argparse
from typing import List, Dict
from jinja2 import Environment, FileSystemLoader, select_autoescape


def _replace_bookend(line: str, search: str, replace_l: str, replace_r: str) -> str:
    try:
        while line.find(search) != -1:
            it = iter(line.split(search, maxsplit=2))
            line_tmp: str = ""
            for token_before in it:
                line_tmp += token_before
                line_tmp += replace_l
                line_tmp += next(it)  # token_mid
                line_tmp += replace_r
                line_tmp += next(it)  # token_after
            line = line_tmp
    except StopIteration:
        pass
    return line


def _strip_md(data: str) -> str:
    content = ""
    for line in data.split("\n"):
        # skip the man page header
        if line.startswith("%"):
            continue
        if line.startswith("|"):
            line = line[2:]
        # create links to other "man" pages
        if line.startswith("<") and (line.endswith("(1)>") or line.endswith("(5)>")):
            line = line.strip("<>")
            name = line.split("(")[0]
            line = f"[`{line}`](./{name}.html)"
        content += f"{line}\n"
    return content


def _convert_md_to_man(data: str) -> str:
    sections = data.split("\n\n")
    troff_lines: List[str] = []

    # ignore the docgen header
    if sections[0].startswith("---"):
        sections = sections[1:]

    # header
    split = sections[0].split(" ", maxsplit=4)
    if split[0] != "%" or split[3] != "|":
        print(
            "no man header detected, expected something like "
            "'% fwupdagent(1) 1.2.5 | fwupdagent man page' and got {}".format(
                sections[0]
            )
        )
        sys.exit(1)
    man_cmd = split[1][:-3]
    man_sect = int(split[1][-2:-1])
    troff_lines.append(f'.TH "{man_cmd}" "{man_sect}" "" {split[2]} "{split[4]}"')
    troff_lines.append(".hy")  # hyphenate

    # content
    in_name_section = False
    for section in sections[1:]:
        lines = section.split("\n")
        sectkind: str = ".PP"  # begin a new paragraph
        sectalign: int = 4

        # convert markdown headers to section headers
        if lines[-1].startswith("##"):
            lines = [lines[-1].strip("#").strip()]
            sectkind = ".SH"
            # track if we're entering the NAME section
            in_name_section = lines[0].upper() == "NAME"

        # join long lines
        line = ""
        indent = False
        for line_tmp in lines:
            if not line_tmp:
                continue
            if line_tmp.startswith("```"):
                indent = not indent
                line_tmp = "```"  # strip the language
            if line_tmp.startswith("| "):
                line_tmp = line_tmp[2:]
            if indent:
                line += ".nf\n"
                line += line_tmp + "\n"
                line += ".fi\n"
                continue
            elif line_tmp.startswith("* "):
                line += ".IP \\[bu] 2\n"
                line += line_tmp[2:]
                continue
            line_tmp = line_tmp.replace("\\-", "-")
            line += line_tmp
            if line_tmp.endswith("."):
                line += " "
            line += " "

        # make bold, and add smart quotes
        line = _replace_bookend(line, "**", "\\f[B]", "\\f[R]")
        line = _replace_bookend(line, "`", "\\f[B]", "\\f[R]")
        line = _replace_bookend(line, '"', "“", "”")

        # in the NAME section, replace em dash with troff hyphen for whatis compatibility
        if in_name_section and sectkind != ".SH":
            line = line.replace("—", "\\-")

        # add troff
        if sectalign != 4:
            troff_lines.append(f".RS {sectalign}")
        if sectkind == ".SH":
            # section headers must be on same line for apropos/whatis compatibility
            troff_lines.append(f"{sectkind} {line.strip()}")
        else:
            troff_lines.append(sectkind)
            troff_lines.append(line)
        if sectalign != 4:
            troff_lines.append(".RE")

    # success
    return "\n".join(troff_lines)


def _add_defines(defines: Dict[str, str], fn: str) -> None:
    with open(fn, "rb") as f:
        for line in f.read().decode().split("\n"):
            if (
                line.find("set_config_default") == -1
                and line.find("config_set_default") == -1
            ):
                continue
            try:
                _, wrapped_key, wrapped_value = line.split(",", maxsplit=2)
                wrapped_value = wrapped_value.rsplit(")", maxsplit=1)[0]
            except ValueError:
                print(f"failed to define value for {line} in {fn}")
                continue
            try:
                _, key, _ = wrapped_key.split('"', maxsplit=2)
            except ValueError:
                continue
            try:
                _, value, _ = wrapped_value.split('"', maxsplit=2)
            except ValueError:
                value = wrapped_value.strip()
            source_prefix: str = (
                os.path.basename(os.path.dirname(fn)).replace("-", "_") + "_"
            )
            if source_prefix == "src_":
                source_prefix = ""
            defines[f"{source_prefix}{key}"] = {"NULL": ""}.get(value, value)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-o", "--output")
    parser.add_argument(
        "-r", "--replace", action="append", nargs=2, metavar=("symbol", "version")
    )
    parser.add_argument("-d", "--defines", action="append")
    parser.add_argument("--md", action="store_true")
    args, argv = parser.parse_known_args()
    if len(argv) != 1:
        print(f"usage: {sys.argv[0]} MARKDOWN [-o TROFF]\n")
        sys.exit(1)

    # load in #defines to populate the defaults
    subst: Dict[str, str] = {}
    if args.defines:
        for fn_define in args.defines:
            try:
                _add_defines(subst, fn_define)
            except FileNotFoundError:
                print(f"{fn_define} not found")
                sys.exit(1)

    # static defines
    if args.replace:
        for key, value in args.replace:
            subst[key] = value

    # use Jinja2 to process as a template
    env = Environment(
        loader=FileSystemLoader(os.path.dirname(argv[0])),
        autoescape=select_autoescape(),
        keep_trailing_newline=True,
    )
    template = env.get_template(os.path.basename(argv[0]))
    rendered = template.render(subst)

    # Stripped markdown mode is used for HTML docs
    if args.md:
        out = _strip_md(rendered)
    else:
        out = _convert_md_to_man(rendered)

    # success
    if args.output:
        with open(args.output, "wb") as f_out:
            f_out.write(out.encode())
    else:
        print(out)
