#! /usr/bin/env python3

# $Id$
# Author: Adam Turner.
# Copyright: This module is placed in the public domain
#            or under the `Zero Clause BSD licence`_,
#            whichever is more permissive.
#
# .. _Zero Clause BSD licence: https://opensource.org/license/0BSD

"""
Tests for `docutils.utils._roman_numerals`.
"""

from pathlib import Path
import sys
import unittest

if __name__ == '__main__':
    # prepend the "docutils root" to the Python library path
    # so we import the local `docutils` package.
    sys.path.insert(0, str(Path(__file__).resolve().parents[2]))

from docutils.utils._roman_numerals import (
    MIN,
    MAX,
    RomanNumeral,
    OutOfRangeError,
    InvalidRomanNumeralError,
)


class NewRomanNumeralTestCase(unittest.TestCase):
    def test_zero(self) -> None:
        with self.assertRaises(OutOfRangeError) as ctx:
            RomanNumeral(0)
        msg = str(ctx.exception)
        self.assertEqual(
            msg,
            'Number out of range (must be between 1 and 4,999). Got 0.',
        )

    def test_one(self) -> None:
        self.assertEqual(int(RomanNumeral(1)), 1)

    def test_MIN(self) -> None:
        self.assertEqual(int(RomanNumeral(MIN)), MIN)

    def test_forty_two(self) -> None:
        self.assertEqual(int(RomanNumeral(42)), 42)

    def test_four_thousand_nine_hundred_and_ninety_nine(self) -> None:
        self.assertEqual(int(RomanNumeral(4_999)), 4_999)

    def test_MAX(self) -> None:
        self.assertEqual(int(RomanNumeral(MAX)), MAX)

    def test_five_thousand(self) -> None:
        with self.assertRaises(OutOfRangeError) as ctx:
            RomanNumeral(5_000)
        msg = str(ctx.exception)
        self.assertEqual(
            msg,
            'Number out of range (must be between 1 and 4,999). Got 5000.',
        )

    def test_minus_one(self) -> None:
        with self.assertRaises(OutOfRangeError) as ctx:
            RomanNumeral(-1)
        msg = str(ctx.exception)
        self.assertEqual(
            msg,
            'Number out of range (must be between 1 and 4,999). Got -1.',
        )

    def test_float(self) -> None:
        with self.assertRaises(TypeError) as ctx:
            RomanNumeral(4.2)
        msg = str(ctx.exception)
        self.assertEqual(
            msg,
            "RomanNumeral: an integer is required, not 'float'",
        )


class ToStringTestCase(unittest.TestCase):
    def test_str(self):
        test_numerals = [
            'I', 'II', 'III', 'IV', 'V',
            'VI', 'VII', 'VIII', 'IX', 'X',
            'XI', 'XII', 'XIII', 'XIV', 'XV',
            'XVI', 'XVII', 'XVIII', 'XIX', 'XX',
            'XXI', 'XXII', 'XXIII', 'XXIV',
        ]
        for n, roman_str in enumerate(test_numerals, start=1):
            with self.subTest(id=n, roman_str=roman_str):
                num = RomanNumeral(n)
                self.assertEqual(f'{num}', roman_str)

    def test_uppercase(self):
        test_numerals = [
            'I', 'II', 'III', 'IV', 'V',
            'VI', 'VII', 'VIII', 'IX', 'X',
            'XI', 'XII', 'XIII', 'XIV', 'XV',
            'XVI', 'XVII', 'XVIII', 'XIX', 'XX',
            'XXI', 'XXII', 'XXIII', 'XXIV',
        ]
        for n, roman_str in enumerate(test_numerals, start=1):
            with self.subTest(id=n, roman_str=roman_str):
                num = RomanNumeral(n)
                self.assertEqual(num.to_uppercase(), roman_str)

    def test_lowercase(self):
        test_numerals = [
            'i', 'ii', 'iii', 'iv', 'v',
            'vi', 'vii', 'viii', 'ix', 'x',
            'xi', 'xii', 'xiii', 'xiv', 'xv',
            'xvi', 'xvii', 'xviii', 'xix', 'xx',
            'xxi', 'xxii', 'xxiii', 'xxiv',
        ]
        for n, roman_str in enumerate(test_numerals, start=1):
            with self.subTest(id=n, roman_str=roman_str):
                num = RomanNumeral(n)
                self.assertEqual(num.to_lowercase(), roman_str)

    def test_minitrue(self):
        # IGNORANCE IS STRENGTH
        num = RomanNumeral(1984)
        self.assertEqual(f'{num}', 'MCMLXXXIV')
        self.assertEqual(num.to_uppercase(), 'MCMLXXXIV')
        self.assertEqual(num.to_lowercase(), 'mcmlxxxiv')


class FromStringTestCase(unittest.TestCase):
    def test_uppercase(self):
        test_numerals = [
            'I', 'II', 'III', 'IV', 'V',
            'VI', 'VII', 'VIII', 'IX', 'X',
            'XI', 'XII', 'XIII', 'XIV', 'XV',
            'XVI', 'XVII', 'XVIII', 'XIX', 'XX',
            'XXI', 'XXII', 'XXIII', 'XXIV',
        ]
        for n, roman_str in enumerate(test_numerals, start=1):
            with self.subTest(id=n, roman_str=roman_str):
                expected = RomanNumeral(n)
                parsed = RomanNumeral.from_string(roman_str)
                self.assertEqual(expected, parsed)

    def test_lowercase(self):
        test_numerals = [
            'i', 'ii', 'iii', 'iv', 'v',
            'vi', 'vii', 'viii', 'ix', 'x',
            'xi', 'xii', 'xiii', 'xiv', 'xv',
            'xvi', 'xvii', 'xviii', 'xix', 'xx',
            'xxi', 'xxii', 'xxiii', 'xxiv',
        ]
        for n, roman_str in enumerate(test_numerals, start=1):
            with self.subTest(id=n, roman_str=roman_str):
                expected = RomanNumeral(n)
                parsed = RomanNumeral.from_string(roman_str)
                self.assertEqual(expected, parsed)

    def test_special(self):
        parsed = RomanNumeral.from_string('MDCCCXXIII')
        self.assertEqual(RomanNumeral(1823), parsed)

        parsed = RomanNumeral.from_string('mdcccxxiii')
        self.assertEqual(RomanNumeral(1823), parsed)

        parsed = RomanNumeral.from_string('MCMLXXXIV')
        self.assertEqual(RomanNumeral(1984), parsed)

        parsed = RomanNumeral.from_string('mcmlxxxiv')
        self.assertEqual(RomanNumeral(1984), parsed)

        parsed = RomanNumeral.from_string('MM')
        self.assertEqual(RomanNumeral(2000), parsed)

        parsed = RomanNumeral.from_string('mm')
        self.assertEqual(RomanNumeral(2000), parsed)

        parsed = RomanNumeral.from_string('MMMMCMXCIX')
        self.assertEqual(RomanNumeral(4_999), parsed)

        parsed = RomanNumeral.from_string('mmmmcmxcix')
        self.assertEqual(RomanNumeral(4_999), parsed)

    def test_invalid(self):
        with self.assertRaises(InvalidRomanNumeralError) as ctx:
            RomanNumeral.from_string('Not a Roman numeral!')
        msg = str(ctx.exception)
        self.assertEqual(msg, 'Invalid Roman numeral: Not a Roman numeral!')

    def test_mixed_case(self):
        with self.assertRaises(InvalidRomanNumeralError) as ctx:
            RomanNumeral.from_string('McMlXxXiV')
        msg = str(ctx.exception)
        self.assertEqual(msg, 'Invalid Roman numeral: McMlXxXiV')


class RoundTripTestCase(unittest.TestCase):
    def test_round_trip(self):
        for n in range(MIN, MAX + 1, 19):
            num = RomanNumeral(n)
            parsed = RomanNumeral.from_string(str(num))
            self.assertEqual(num, parsed)


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