import unittest

from icalendar.parser import (
    Contentline,
    Contentlines,
    Parameters,
    dquote,
    foldline,
    q_join,
    q_split,
)
from icalendar.prop import vText


class IcalendarTestCase(unittest.TestCase):
    def setUp(self):
        if not hasattr(self, "assertRaisesRegex"):
            self.assertRaisesRegex = self.assertRaisesRegexp

    def test_long_lines(self):
        c = Contentlines([Contentline("BEGIN:VEVENT")])
        c.append(Contentline("".join("123456789 " * 10)))
        self.assertEqual(
            c.to_ical(),
            b"BEGIN:VEVENT\r\n123456789 123456789 123456789 123456789 "
            b"123456789 123456789 123456789 1234\r\n 56789 123456789 "
            b"123456789 \r\n",
        )

        # from doctests
        # Notice that there is an extra empty string in the end of the content
        # lines. That is so they can be easily joined with:
        # '\r\n'.join(contentlines))
        self.assertEqual(
            Contentlines.from_ical("A short line\r\n"), ["A short line", ""]
        )
        self.assertEqual(
            Contentlines.from_ical("A faked\r\n  long line\r\n"),
            ["A faked long line", ""],
        )
        self.assertEqual(
            Contentlines.from_ical(
                "A faked\r\n  long line\r\nAnd another lin\r\n\te that is folded\r\n"
            ),
            ["A faked long line", "And another line that is folded", ""],
        )

    def test_contentline_class(self):
        self.assertEqual(
            Contentline("Si meliora dies, ut vina, poemata reddit").to_ical(),
            b"Si meliora dies, ut vina, poemata reddit",
        )

        # A long line gets folded
        c = Contentline("".join(["123456789 "] * 10)).to_ical()
        self.assertEqual(
            c,
            (
                b"123456789 123456789 123456789 123456789 123456789 123456789 "
                b"123456789 1234\r\n 56789 123456789 123456789 "
            ),
        )

        # A folded line gets unfolded
        self.assertEqual(
            Contentline.from_ical(c),
            (
                "123456789 123456789 123456789 123456789 123456789 123456789 "
                "123456789 123456789 123456789 123456789 "
            ),
        )

        # https://tools.ietf.org/html/rfc5545#section-3.3.11
        # An intentional formatted text line break MUST only be included in
        # a "TEXT" property value by representing the line break with the
        # character sequence of BACKSLASH, followed by a LATIN SMALL LETTER
        # N or a LATIN CAPITAL LETTER N, that is "\n" or "\N".

        # Newlines are not allowed in content lines
        self.assertRaises(AssertionError, Contentline, b"1234\r\n\r\n1234")

        self.assertEqual(Contentline("1234\\n\\n1234").to_ical(), b"1234\\n\\n1234")

        # We do not fold within a UTF-8 character
        c = Contentline(
            b"This line has a UTF-8 character where it should be "
            b"folded. Make sure it g\xc3\xabts folded before that "
            b"character."
        )

        self.assertIn(b"\xc3\xab", c.to_ical())

        # Another test of the above
        c = Contentline(b"x" * 73 + b"\xc3\xab" + b"\\n " + b"y" * 10)

        self.assertEqual(c.to_ical().count(b"\xc3"), 1)

        # Don't fail if we fold a line that is exactly X times 74 characters
        # long
        c = Contentline("".join(["x"] * 148)).to_ical()

        # It can parse itself into parts,
        # which is a tuple of (name, params, vals)
        self.assertEqual(
            Contentline("dtstart:20050101T120000").parts(),
            ("dtstart", Parameters({}), "20050101T120000"),
        )

        self.assertEqual(
            Contentline("dtstart;value=datetime:20050101T120000").parts(),
            ("dtstart", Parameters({"VALUE": "datetime"}), "20050101T120000"),
        )

        c = Contentline(
            "ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com"
        )
        self.assertEqual(
            c.parts(),
            (
                "ATTENDEE",
                Parameters({"ROLE": "REQ-PARTICIPANT", "CN": "Max Rasmussen"}),
                "MAILTO:maxm@example.com",
            ),
        )
        self.assertEqual(
            c.to_ical().decode("utf-8"),
            "ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com",
        )

        # and back again
        # NOTE: we are quoting property values with spaces in it.
        parts = (
            "ATTENDEE",
            Parameters({"ROLE": "REQ-PARTICIPANT", "CN": "Max Rasmussen"}),
            "MAILTO:maxm@example.com",
        )
        self.assertEqual(
            Contentline.from_parts(*parts),
            'ATTENDEE;CN="Max Rasmussen";ROLE=REQ-PARTICIPANT:'
            "MAILTO:maxm@example.com",
        )

        # and again
        parts = ("ATTENDEE", Parameters(), "MAILTO:maxm@example.com")
        self.assertEqual(
            Contentline.from_parts(*parts), "ATTENDEE:MAILTO:maxm@example.com"
        )

        # A value can also be any of the types defined in PropertyValues
        parts = ("ATTENDEE", Parameters(), vText("MAILTO:test@example.com"))
        self.assertEqual(
            Contentline.from_parts(*parts), "ATTENDEE:MAILTO:test@example.com"
        )

        # A value in UTF-8
        parts = ("SUMMARY", Parameters(), vText("INternational char æ ø å"))
        self.assertEqual(
            Contentline.from_parts(*parts), "SUMMARY:INternational char æ ø å"
        )

        # A value can also be unicode
        parts = ("SUMMARY", Parameters(), vText("INternational char æ ø å"))
        self.assertEqual(
            Contentline.from_parts(*parts), "SUMMARY:INternational char æ ø å"
        )

        # Traversing could look like this.
        name, params, vals = c.parts()
        self.assertEqual(name, "ATTENDEE")
        self.assertEqual(vals, "MAILTO:maxm@example.com")
        self.assertEqual(
            sorted(params.items()),
            sorted([("ROLE", "REQ-PARTICIPANT"), ("CN", "Max Rasmussen")]),
        )

        # And the traditional failure
        with self.assertRaisesRegex(
            ValueError, "Content line could not be parsed into parts"
        ):
            Contentline("ATTENDEE;maxm@example.com").parts()

        # Another failure:
        with self.assertRaisesRegex(
            ValueError, "Content line could not be parsed into parts"
        ):
            Contentline(":maxm@example.com").parts()

        self.assertEqual(
            Contentline("key;param=:value").parts(),
            ("key", Parameters({"PARAM": ""}), "value"),
        )

        self.assertEqual(
            Contentline('key;param="pvalue":value').parts(),
            ("key", Parameters({"PARAM": "pvalue"}), "value"),
        )

        # Should bomb on missing param:
        with self.assertRaisesRegex(
            ValueError, "Content line could not be parsed into parts"
        ):
            Contentline.from_ical("k;:no param").parts()

        self.assertEqual(
            Contentline("key;param=pvalue:value", strict=False).parts(),
            ("key", Parameters({"PARAM": "pvalue"}), "value"),
        )

        # If strict is set to True, uppercase param values that are not
        # double-quoted, this is because the spec says non-quoted params are
        # case-insensitive.
        self.assertEqual(
            Contentline("key;param=pvalue:value", strict=True).parts(),
            ("key", Parameters({"PARAM": "PVALUE"}), "value"),
        )

        self.assertEqual(
            Contentline('key;param="pValue":value', strict=True).parts(),
            ("key", Parameters({"PARAM": "pValue"}), "value"),
        )

        contains_base64 = (
            b"X-APPLE-STRUCTURED-LOCATION;"
            b'VALUE=URI;X-ADDRESS="Kaiserliche Hofburg, 1010 Wien";'
            b"X-APPLE-MAPKIT-HANDLE=CAESxQEZgr3QZXJyZWljaA==;"
            b"X-APPLE-RADIUS=328.7978217977285;X-APPLE-REFERENCEFRAME=1;"
            b"X-TITLE=Heldenplatz:geo:48.206686,16.363235"
        )

        self.assertEqual(
            Contentline(contains_base64, strict=True).parts(),
            (
                "X-APPLE-STRUCTURED-LOCATION",
                Parameters(
                    {
                        "X-APPLE-RADIUS": "328.7978217977285",
                        "X-ADDRESS": "Kaiserliche Hofburg, 1010 Wien",
                        "X-APPLE-REFERENCEFRAME": "1",
                        "X-TITLE": "HELDENPLATZ",
                        "X-APPLE-MAPKIT-HANDLE": "CAESXQEZGR3QZXJYZWLJAA==",
                        "VALUE": "URI",
                    }
                ),
                "geo:48.206686,16.363235",
            ),
        )

    def test_fold_line(self):
        self.assertEqual(foldline("foo"), "foo")
        self.assertEqual(
            foldline(
                "Lorem ipsum dolor sit amet, consectetur adipiscing "
                "elit. Vestibulum convallis imperdiet dui posuere."
            ),
            (
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
                "Vestibulum conval\r\n lis imperdiet dui posuere."
            ),
        )

        # I don't really get this test
        # at least just but bytes in there
        # porting it to "run" under python 2 & 3 makes it not much better
        with self.assertRaises(AssertionError):
            foldline("привет".encode(), limit=3)

        self.assertEqual(foldline("foobar", limit=4), "foo\r\n bar")
        self.assertEqual(
            foldline(
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
                ". Vestibulum convallis imperdiet dui posuere."
            ),
            (
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
                " Vestibulum conval\r\n lis imperdiet dui posuere."
            ),
        )
        self.assertEqual(
            foldline("DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ"),
            "DESCRIPTION:АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭ\r\n ЮЯ",
        )

    def test_value_double_quoting(self):
        self.assertEqual(dquote("Max"), "Max")
        self.assertEqual(dquote("Rasmussen, Max"), '"Rasmussen, Max"')
        self.assertEqual(dquote("name:value"), '"name:value"')

    def test_q_split(self):
        self.assertEqual(
            q_split('Max,Moller,"Rasmussen, Max"'),
            ["Max", "Moller", '"Rasmussen, Max"'],
        )

    def test_q_split_bin(self):
        for s in ("X-SOMETHING=ABCDE==", ",,,"):
            for maxsplit in range(-1, 3):
                self.assertEqual(
                    q_split(s, "=", maxsplit=maxsplit), s.split("=", maxsplit)
                )

    def test_q_join(self):
        self.assertEqual(
            q_join(["Max", "Moller", "Rasmussen, Max"]), 'Max,Moller,"Rasmussen, Max"'
        )
