#!/usr/bin/env python3
# Copyright: 2026, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

"""Tests for SPDX licence-expression parsing and DEP-5 rendering.

DEP-5 (spec sec 7.2) uses lowercase or/and with `and` binding tighter than
`or`, and a comma -- not parentheses -- to override priority. SPDX uses
uppercase AND/OR/WITH and parentheses, with the same `AND > OR` precedence.
"""

import unittest


class TestSpdxExpressionToDep5(unittest.TestCase):
    """SPDX expression -> DEP-5 string."""

    def test_simple_and_with(self):
        from decopy.spdx import spdx_expression_to_dep5

        cases = [
            ("GPL-2.0-only", "GPL-2"),
            ("GPL-2.0+", "GPL-2+"),
            (
                "GPL-2.0-or-later WITH Font-exception-2.0",
                "GPL-2+ with Font exception",
            ),
        ]
        for spdx, dep5 in cases:
            with self.subTest(spdx=spdx):
                self.assertEqual(spdx_expression_to_dep5(spdx), dep5)

    def test_disjunction_and_conjunction(self):
        from decopy.spdx import spdx_expression_to_dep5

        cases = [
            # spec sec 7.2 examples (dual-licence, combined-work)
            ("GPL-1.0-or-later OR Artistic-2.0", "GPL-1+ or Artistic-2"),
            ("GPL-2.0-only AND BSD-3-Clause", "GPL-2 and BSD-3-clause"),
            ("MIT OR Apache-2.0 OR ISC", "Expat or Apache-2 or ISC"),
            # default `and > or`: no comma needed
            (
                "GPL-2.0-only OR Artistic-2.0 AND BSD-3-Clause",
                "GPL-2 or Artistic-2 and BSD-3-clause",
            ),
        ]
        for spdx, dep5 in cases:
            with self.subTest(spdx=spdx):
                self.assertEqual(spdx_expression_to_dep5(spdx), dep5)

    def test_grouping_renders_with_comma(self):
        from decopy.spdx import spdx_expression_to_dep5

        # An `or` grouped under an `and` is the one case DEP-5 expresses with a
        # comma (a parenthesised SPDX `or` operand of an `and`).
        cases = [
            ("(GPL-2.0-only OR MIT) AND Apache-2.0", "GPL-2 or Expat, and Apache-2"),
            ("Apache-2.0 AND (MIT OR ISC)", "Apache-2, and Expat or ISC"),
            (
                "(GPL-2.0-only OR MIT) AND (Apache-2.0 OR ISC)",
                "GPL-2 or Expat, and Apache-2 or ISC",
            ),
        ]
        for spdx, dep5 in cases:
            with self.subTest(spdx=spdx):
                self.assertEqual(spdx_expression_to_dep5(spdx), dep5)

    def test_with_in_compound_and_deprecated_token(self):
        from decopy.spdx import spdx_expression_to_dep5

        cases = [
            # WITH binds tighter than OR: no comma
            (
                "GPL-2.0-or-later WITH Classpath-exception-2.0 OR MIT",
                "GPL-2+ with ClassPath exception or Expat",
            ),
            # deprecated combined single token (commit 6e) survives as one leaf
            (
                "GPL-2.0-with-classpath-exception",
                "GPL-2 with ClassPath exception",
            ),
        ]
        for spdx, dep5 in cases:
            with self.subTest(spdx=spdx):
                self.assertEqual(spdx_expression_to_dep5(spdx), dep5)


class TestSpdxExpressionParse(unittest.TestCase):
    """Parse tree shape and precedence (implemented in 9a)."""

    def test_and_binds_tighter_than_or(self):
        from decopy.spdx import And, License, Or, parse_spdx_expression

        self.assertEqual(
            parse_spdx_expression("A OR B AND C"),
            Or((License("A"), And((License("B"), License("C"))))),
        )

    def test_with_binds_tighter_than_or(self):
        from decopy.spdx import License, Or, With, parse_spdx_expression

        self.assertEqual(
            parse_spdx_expression("A WITH E OR B"),
            Or((With(License("A"), "E"), License("B"))),
        )

    def test_parentheses_override_precedence(self):
        from decopy.spdx import And, License, Or, parse_spdx_expression

        self.assertEqual(
            parse_spdx_expression("(A OR B) AND C"),
            And((Or((License("A"), License("B"))), License("C"))),
        )


if __name__ == "__main__":
    unittest.main()
