#!/usr/bin/python3

"""Convert Spyder copyright file into a debian/copyright file.

It should be run from within the debian directory.  The output
is a file called copyright.stanzas which contains Files blocks for
files not under the overall Spyder MIT license.  This can then be included
in debian/copyright.
"""

# Copyright: 2021 Julian Gilbey <jdg@debian.org>
# License: MIT (Expat)

# Spyder provides a mostly computer-readable copyright file.  It is very
# long, and this script performs a conversion to create the debian/copyright
# file in an automated fashion.

import re
import sys


def main() -> None:
    """Read NOTICE.txt and writes the corresponding debian/copyright text.

    The output is not a complete debian/copyright file, just a collection
    of Files stanzas.  Unrecognised licenses and other issues are reported.

    """
    notice_txt = _read_notice_file()
    notice_stanzas = _split_notice_file(notice_txt)
    debian_stanzas = [_convert_notice_stanza(stanza) for stanza in notice_stanzas]
    _write_debian_stanzas(debian_stanzas)


SKIP_STANZAS = [
    "QDarkStyleSheet",
    "MathJax 2.7.5",
    "jQuery Javascript Library v1.7.2",
    "Sizzle CSS Selector Engine 1.7.2",
    "New Entry Template",
    # the next two stanzas both refer to the file "dataframeeditor.py"
    "DataFrameModel from QtPandas (Pandas 0.13.1)",
    "Classes from Gtabview 0.8",
]


# License lines included in the current NOTICE.txt file
LICENSES = {
    '3-Clause ("Modified") BSD License': "MIT",
    "Apache License 2.0 | https://www.apache.org/licenses/LICENSE-2.0": "Apache-2.0",
    "Apache License Version 2.0": "Apache-2.0",
    "BSD (3-clause) License | https://opensource.org/licenses/BSD-3-Clause": "BSD-3-clause",
    "Creative Commons Attribution 2.5 Generic": "CC-BY-2.5",
    "Creative Commons Attribution 3.0 International": "CC-BY-3.0",
    "Creative Commons Attribution 4.0 International": "CC-BY-4.0",
    "Creative Commons Attribution International 4.0": "CC-BY-4.0",
    "GNU Lesser General Public License 2.1 or later": "LGPL-2.1+",
    "GNU Lesser General Public License 3.0 or later": "LGPL-3.0+",
    "MIT (expat)": "MIT",
    "MIT (Expat) License | https://opensource.org/licenses/MIT": "MIT",
    "Python Software Foundation License Version 2": "PSFL-2",
    "SIL Open Font License 1.1 | https://scripts.sil.org/OFL": "OFL-1.1",
    "The MIT License (MIT)": "MIT",
}


def _read_notice_file() -> str:
    try:
        with open("../NOTICE.txt") as notice:
            notice_txt = notice.read()
    except OSError:
        print(
            "Cannot open/read ../NOTICE.txt. Are you in the debian directory?",
            file=sys.stderr,
        )
        raise
    return notice_txt


def _write_debian_stanzas(stanzas: list[str]) -> None:
    stanzas_nonempty = (stanza for stanza in stanzas if stanza != "")
    with open("copyright.stanzas", "w") as debian_copyright:
        print("\n".join(stanzas_nonempty), file=debian_copyright)


def _split_notice_file(txt: str) -> list[str]:
    stanzas = re.split(r"-{79}|={79}", txt)
    file_stanzas = []
    for stanza in stanzas:
        stanza = stanza.strip()
        title_line = _get_title_line(stanza)
        if title_line in SKIP_STANZAS:
            print(f"Skipping stanza (manually handled): {title_line}")
        elif "Files covered:" in stanza:
            file_stanzas.append(stanza)
        else:
            print(f"Skipping stanza (unrecognised): {title_line}")
    return file_stanzas


def _convert_notice_stanza(stanza: str) -> str:
    title_line = _get_title_line(stanza)

    files_match = re.search(r"Files covered:\s*$(.*)", stanza, flags=re.M | re.S)
    if files_match:
        files_str = files_match.group(1).strip()
    else:
        files_str = "UNKNOWN"
        print(f"Could not determine files string for:\n  {title_line}")
        return ""

    copyright_match = re.search(r"\n((?:Copyright.*\n)+)\n", stanza)
    if copyright_match:
        copyright_str = copyright_match.group(1).strip()
    else:
        copyright_str = "UNKNOWN"
        print(f"Could not determine copyright string for:\n  {title_line}")
        return ""

    license_match = re.search(r"^License: (.+)$", stanza, flags=re.M)
    if license_match:
        license_str_raw = license_match.group(1).strip()
    else:
        license_str_raw = "UNKNOWN"

    if license_str_raw in LICENSES:
        license_str = LICENSES[license_str_raw]
    else:
        license_str = "UNKNOWN"
        print(f"Could not determine license string for:\n  {title_line}")
        return ""

    return _produce_debian_stanza(title_line, files_str, copyright_str, license_str)


def _get_title_line(stanza: str) -> str:
    # stanza has already had leading blank lines stripped
    stanza_lines = stanza.splitlines(keepends=False)
    title_line = 0
    if len(stanza_lines) > 1 and stanza_lines[1].startswith("="):
        # this is a section title which we skip
        for linenum_2, line in enumerate(stanza_lines[2:]):
            if line.startswith("-----"):
                title_line = linenum_2 + 1
    return stanza_lines[title_line]


COPYRIGHT_STANZA = """Files:
{files_formatted}
Copyright:
{copyright_formatted}
Comment:
  This copyright information was extracted from NOTICE.txt for:
  {comment}
License: {license_str}
"""


def _produce_debian_stanza(
    comment: str, files: str, copyright_str: str, license_str: str
) -> str:
    # The key task here is to get the correct indentation
    files_formatted = re.sub("^", "  ", files, flags=re.M)
    copyright_formatted = re.sub(r"Copyright (?:\([Cc]\) )?", "  ", copyright_str)
    stanza = COPYRIGHT_STANZA.format(
        files_formatted=files_formatted,
        copyright_formatted=copyright_formatted,
        comment=comment,
        license_str=license_str,
    )
    return stanza


if __name__ == "__main__":
    main()
