# SPDX-FileCopyrightText: 2022 Emmanuele Bassi
#
# SPDX-License-Identifier: LGPL-2.1-or-later

import os
import re

import textwrap

# Disable line length warnings as wrapping the templates would be hard
# flake8: noqa: E501


class RstCodeGenerator:
    """Generates documentation in reStructuredText format."""

    def __init__(self, ifaces):
        self.ifaces = ifaces
        self._generate_expand_dicts()

    def _expand(self, s, expandParamsAndConstants):
        """Expands parameters and constant literals."""
        res = []
        for line in textwrap.dedent(s).split("\n"):
            line = line.rstrip()
            if line == "":
                res.append("")
                continue
            for key in self._expand_member_dict_keys:
                line = line.replace(key, self._expand_member_dict[key])
            for key in self._expand_iface_dict_keys:
                line = line.replace(key, self._expand_iface_dict[key])
            if expandParamsAndConstants:
                # replace @foo with ``foo``
                line = re.sub(
                    "@[a-zA-Z0-9_]*",
                    lambda m: "``" + m.group(0)[1:] + "``",
                    line,
                )
                # replace e.g. %TRUE with ``TRUE``
                line = re.sub(
                    "%[a-zA-Z0-9_]*",
                    lambda m: "``" + m.group(0)[1:] + "``",
                    line,
                )
            res.append(line)
        return "\n".join(res)

    def _generate_expand_dicts(self):
        """Generates the dictionaries used to expand gtk-doc sigils."""
        self._expand_member_dict = {}
        self._expand_iface_dict = {}
        for i in self.ifaces:
            key = f"#{i.name}"
            value = f"`{i.name}`_"
            self._expand_iface_dict[key] = value

            for m in i.methods:
                key = "%s.%s()" % (i.name, m.name)
                value = f"`{i.name}.{m.name}`_"
                self._expand_member_dict[key] = value

            for s in i.signals:
                key = "#%s::%s" % (i.name, s.name)
                value = f"`{i.name}::{s.name}`_"
                self._expand_member_dict[key] = value

            for p in i.properties:
                key = "#%s:%s" % (i.name, p.name)
                value = f"`{i.name}:{p.name}`_"
                self._expand_member_dict[key] = value

        # Make sure to expand the keys in reverse order so e.g. #org.foo.Iface:MediaCompat
        # is evaluated before #org.foo.Iface:Media ...
        self._expand_member_dict_keys = sorted(
            self._expand_member_dict.keys(), reverse=True
        )
        self._expand_iface_dict_keys = sorted(
            self._expand_iface_dict.keys(), reverse=True
        )

    def _generate_header(self, iface):
        """Generates the header and preamble of the document."""
        iface_name = iface.name_without_prefix
        header_len = len(iface_name)
        res = [
            f".. _{iface.name}:",
            "",
            "=" * header_len,
            iface_name,
            "=" * header_len,
            "",
            "-----------",
            "Description",
            "-----------",
            "",
            f".. _{iface.name} Description:",
            "",
            iface.doc_string_brief.strip(),
            "",
            self._expand(iface.doc_string, True),
            "",
        ]
        if iface.since:
            res += [
                f"Interface available since: {iface.since}.",
                "",
            ]
        if iface.deprecated:
            res += [
                ".. warning::",
                "",
                "   This interface is deprecated.",
                "",
                "",
            ]
        res += [""]
        return "\n".join(res)

    def _generate_section(self, title, name):
        """Generates a section with the given title."""
        res = [
            f".. _{name} {title}:",
            "",
            "-" * len(title),
            title,
            "-" * len(title),
            "",
            "",
        ]
        return "\n".join(res)

    def _generate_properties(self, iface):
        """Generates the properties section."""
        res = []
        for p in iface.properties:
            title = f"{iface.name}:{p.name}"
            if p.readable and p.writable:
                access = "readwrite"
            elif p.writable:
                access = "writable"
            else:
                access = "readable"
            res += [
                f".. _{title}:",
                "",
                title,
                "^" * len(title),
                "",
                "::",
                "",
                f"    {p.name} {access} {p.signature}",
                "",
                "",
                self._expand(p.doc_string, True),
                "",
            ]
            if p.since:
                res += [
                    f"Property available since: {p.since}.",
                    "",
                ]
            if p.deprecated:
                res += [
                    ".. warning::",
                    "",
                    "   This property is deprecated.",
                    "",
                    "",
                ]
            res += [""]
        return "\n".join(res)

    def _generate_method_signature(self, method):
        """Generates the method signature as a code block."""
        res = [
            "::",
            "",
        ]
        n_in_args = len(method.in_args)
        n_out_args = len(method.out_args)
        if n_in_args == 0 and n_out_args == 0:
            res += [
                f"    {method.name} ()",
            ]
        else:
            res += [
                f"    {method.name} (",
            ]
            for idx, arg in enumerate(method.in_args):
                if idx == n_in_args - 1 and n_out_args == 0:
                    res += [
                        f"      IN {arg.name} {arg.signature}",
                    ]
                else:
                    res += [
                        f"      IN {arg.name} {arg.signature},",
                    ]
            for idx, arg in enumerate(method.out_args):
                if idx == n_out_args - 1:
                    res += [
                        f"      OUT {arg.name} {arg.signature}",
                    ]
                else:
                    res += [
                        f"      OUT {arg.name} {arg.signature},",
                    ]
            res += [
                "    )",
                "",
            ]
        res += [""]
        return "\n".join(res)

    def _generate_methods(self, iface):
        """Generates the methods section."""
        res = []
        for m in iface.methods:
            title = f"{iface.name}.{m.name}"
            res += [
                f".. _{title}:",
                "",
                title,
                "^" * len(title),
                "",
                self._generate_method_signature(m),
                "",
                self._expand(m.doc_string, True),
                "",
            ]
            for a in m.in_args:
                arg_desc = self._expand(a.doc_string, True)
                res += [
                    f"{a.name}",
                    f"  {arg_desc}",
                    "",
                ]
            for a in m.out_args:
                arg_desc = self._expand(a.doc_string, True)
                res += [
                    f"{a.name}",
                    f"  {arg_desc}",
                    "",
                ]
            res += [""]
            if m.since:
                res += [
                    f"Method available since: {m.since}.",
                    "",
                ]
            if m.deprecated:
                res += [
                    ".. warning::",
                    "",
                    "   This method is deprecated.",
                    "",
                    "",
                ]
            res += [""]
        return "\n".join(res)

    def _generate_signal_signature(self, signal):
        """Generates the signal signature."""
        res = [
            "::",
            "",
        ]
        n_args = len(signal.args)
        if n_args == 0:
            res += [
                f"    {signal.name} ()",
            ]
        else:
            res += [
                f"    {signal.name} (",
            ]
            for idx, arg in enumerate(signal.args):
                if idx == n_args - 1:
                    res += [
                        f"      {arg.name} {arg.signature}",
                    ]
                else:
                    res += [
                        f"      {arg.name} {arg.signature},",
                    ]
            res += [
                "    )",
                "",
            ]
        res += [""]
        return "\n".join(res)

    def _generate_signals(self, iface):
        """Generates the signals section."""
        res = []
        for s in iface.signals:
            title = f"{iface.name}::{s.name}"
            res += [
                f".. _{title}:",
                "",
                title,
                "^" * len(title),
                "",
                self._generate_signal_signature(s),
                "",
                self._expand(s.doc_string, True),
                "",
            ]
            for a in s.args:
                arg_desc = self._expand(a.doc_string, True)
                res += [
                    f"{a.name}",
                    f"  {arg_desc}",
                    "",
                ]
            res += [""]
            if s.since:
                res += [
                    f"Signal available since: {s.since}.",
                    "",
                ]
            if s.deprecated:
                res += [
                    ".. warning::",
                    "",
                    "   This signal is deprecated.",
                    "",
                    "",
                ]
            res += [""]
        return "\n".join(res)

    def generate(self, rst, outdir):
        """Generates the reStructuredText file for each interface."""
        for i in self.ifaces:
            with open(os.path.join(outdir, f"{rst}-{i.name}.rst"), "w") as outfile:
                outfile.write(self._generate_header(i))
                if len(i.properties) > 0:
                    outfile.write(self._generate_section("Properties", i.name))
                    outfile.write(self._generate_properties(i))
                if len(i.methods) > 0:
                    outfile.write(self._generate_section("Methods", i.name))
                    outfile.write(self._generate_methods(i))
                if len(i.signals) > 0:
                    outfile.write(self._generate_section("Signals", i.name))
                    outfile.write(self._generate_signals(i))
