# Parsing SPDX statements in header comments.
#
# Author: Malte J. Ziebarth (malte.ziebarth@tum.de)
#
# Copyright (C) 2025-2026 Technische Universität München
#
# Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
# the European Commission - subsequent versions of the EUPL (the "Licence");
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at:
#
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Licence is distributed on an "AS IS" basis,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the Licence for the specific language governing permissions and
# limitations under the Licence.
#
# SPDX-FileCopyrightText: 2025-2026 Technical University of Munich
# SPDX-License-Identifier: EUPL-1.2
# SPDX-FileContributor: Malte J. Ziebarth <malte.ziebarth@tum.de>

from typing import TextIO

class SPDXInfo:
    """
    SPDX info contained in a header.
    """
    authors: list[tuple[str, str | None]]
    license_spdx: str | None
    copyright: list[str]

    def __init__(self):
        self.authors = []
        self.license_spdx = None
        self.copyright = []


def parse_spdx(
        f: TextIO,
        extended_author_syntax: bool
    ) -> SPDXInfo:
    """
    Tries to find an SPDX identifier in a header file.
    """
    # def

    # Make sure that we start with a comment:
    line = f.readline().strip()
    if line.startswith("/*"):
        def parse_and_isolate_comment(line: str) -> tuple[str,bool]:
            """
            Returns the isolated comment in this line, plus a boolean
            flag that is true if the comment ends after this line.
            """
            split = line.split('*/')
            line = split[0]
            i0 = 0
            while i0 < len(line) and line[i0] in (' ','*'):
                i0 += 1
            return line[i0:], len(split) > 1

    elif line.startswith('//'):
        def parse_and_isolate_comment(line: str) -> tuple[str, bool]:
            """
            Returns the isolated comment in this line, plus a boolean
            flag that is true if the comment ends after this line.
            """
            if line.startswith('//'):
                return line[2:], False
            return "", True
    else:
        raise RuntimeError(
            "Header has to start with a comment; instead found line '"
            + line + "'."
        )

    spdx: SPDXInfo = SPDXInfo()
    parsing_author: bool = False
    def parse_comment_for_spdx(comment: str):
        """
        Parses relevant SPDX info in the comment.
        """
        nonlocal parsing_author
        comment = comment.strip()

        # Specific parsing code:
        def extended_author_parsing(author: str) -> tuple[str, str | None]:
            """
            Extended author parsing syntax
            (e.g. "Author: Random Person (rp@rdomain.org)")
            """
            email = None
            for bo,bc in (("(",")"), ("<",">"), ("[","]")):
                if bo in author:
                    author, email = author.split(bo)
                    author = author.strip()
                    email = email.split(bc)[0]
            if email is None and "@" in author:
                # There's an email present but no parantheses.
                split = author.split()
                al: list[str] = []
                for s in split:
                    if "@" in s:
                        email = s
                    else:
                        al.append(s)
                author = " ".join(al)

            return author, email

        if comment.startswith("SPDX-FileCopyrightText:"):
            # Copyright (type 1)
            spdx.copyright.append(comment.split("SPDX-FileCopyrightText:")[1].strip())
        elif comment.startswith("SPDX-FileContributor:"):
            author = comment.split("SPDX-FileContributor:")[1].strip()
            # REUSE / SPDX convention is to have email adresses in angle brackets <>:
            if "<" in author:
                # Have a contact address.
                author, address = author.split("<")
                address = address.split(">")[0]
            else:
                address = None
            spdx.authors.append((author, address))
        elif comment.startswith("SPDX-License-Identifier:"):
            if spdx.license_spdx is not None:
                raise RuntimeError(
                    "Redefinition of license identifier."
                )
        elif comment.startswith("Copyright") or comment.startswith("©"):
            # Copyright (type 2)
            spdx.copyright.append(comment)
        elif extended_author_syntax and comment.lower().startswith("author:"):
            # Author statement (extended syntax):
            parsing_author = comment.endswith(',')
            author = comment[7:]

            spdx.authors.append(extended_author_parsing(author))
        elif extended_author_syntax and parsing_author:
            parsing_author = comment.endswith(',')
            spdx.authors.append(extended_author_parsing(comment))

        else:
            print("pass on comment '" + str(comment) + "'.")
            pass


    # Iterate the whole section of the initial comment:
    comment, ends_after = parse_and_isolate_comment(line)
    parse_comment_for_spdx(comment)
    while not ends_after:
        comment, ends_after = parse_and_isolate_comment(f.readline().strip())
        parse_comment_for_spdx(comment)


    return spdx