# SPDX-FileCopyrightText: © 2023 Christian BUHTZ <c.buhtz@posteo.jp>
#
# SPDX-License-Identifier: GPL-3.0-only
#
# This file is part of the program "Hyperorg" which is released under GNU
# General Public License v3 (GPLv3).
# See folder LICENSES or go to <https://www.gnu.org/licenses/#GPL>.
"""Tests about lists in org format converted into ListElements"""
import unittest
from hyperorg import textruns
from hyperorg import elements
from hyperorg import parser


class Lists(unittest.TestCase):
    """Parse lists"""

    def test_ignore_single_empty_line(self):
        """Ignore one empty line between list items.

        A list ends with...
         - two blank lines
         - (bullet) less indented as line before
        """

        # This is one list. The one empty line is ignored.
        content = [
            '- one',
            '',
            '- two'
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 1)
        self.assertEqual(sut[0][0], ['one'])
        self.assertEqual(sut[0][1], ['two'])

    def test_two_list(self):
        """Two lists separated by two empty lines"""
        # two lists
        content = [
            '- one',
            '',
            '',
            '- two'
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 2)
        self.assertEqual(sut[0][0], ['one'])
        self.assertEqual(sut[1][0], ['two'])

    def test_finish_with_paragraph(self):
        """List followed by a paragraph"""
        content = [
            '- one',
            'two'
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 2)
        self.assertEqual(sut[0], elements.ListElement('- one', False))
        self.assertEqual(sut[1], elements.Paragraph(['two']))

    def test_finish_with_heading(self):
        """List followed by a heading"""
        content = [
            '- one',
            '* two'
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 2)
        self.assertEqual(sut[0], elements.ListElement('- one', False))
        self.assertEqual(sut[1], elements.Heading('* two'))

    def test_one_item(self):
        """List with one item."""
        content = [
            '- one'
        ]

        _, sut = parser.OrgContentParser(content).result()

        # one element
        self.assertEqual(len(sut), 1)

        # a list element
        self.assertIsInstance(sut[0], elements.ListElement)

        # list content; first list item
        self.assertEqual(sut[0][0], ['one'])

    def test_multiple_items(self):
        """List with one item."""
        content = [
            '- one',
            '- two',
            '- three',
        ]

        _, sut = parser.OrgContentParser(content).result()

        # one element
        self.assertEqual(len(sut), 1)

        # a list element
        self.assertIsInstance(sut[0], elements.ListElement)

        # list content; first list item
        self.assertEqual(sut[0][0], ['one'])
        self.assertEqual(sut[0][1], ['two'])
        self.assertEqual(sut[0][2], ['three'])

    def test_unordered(self):
        """List with one item."""
        content = [
            '- one'
        ]

        _, sut = parser.OrgContentParser(content).result()

        self.assertFalse(sut[0].ordered)

    def test_ordered(self):
        """List with one item."""
        content = [
            '1. one'
        ]

        _, sut = parser.OrgContentParser(content).result()

        self.assertTrue(sut[0].ordered)

    def test_sub_list(self):
        """List with list"""
        content = [
            '- one',
            '  - two'
        ]

        _, sut = parser.OrgContentParser(content).result()

        # one list
        self.assertEqual(len(sut), 1)

        # with two items
        self.assertEqual(len(sut[0]), 2)

        # first item
        self.assertEqual(sut[0][0], ['one'])

        # second item
        self.assertIsInstance(sut[0][1], elements.ListElement)
        self.assertEqual(sut[0][1][0], ['two'])

    def test_sub_list_in_the_middle(self):
        """List with list in the middle"""
        content = [
            '- one',
            '  - two',
            '- three'
        ]

        _, sut = parser.OrgContentParser(content).result()

        # one list
        self.assertEqual(len(sut), 1)

        # with two items
        self.assertEqual(len(sut[0]), 3)

        # first item
        self.assertEqual(sut[0][0], ['one'])
        # third item
        self.assertEqual(sut[0][2], ['three'])

        # second item
        self.assertIsInstance(sut[0][1], elements.ListElement)
        self.assertEqual(sut[0][1][0], ['two'])

    def test_messup_whitespaces(self):
        """Unusual space between bullet and content"""
        content = [
            '  -   one',
            '    - two',
            '  - three'
        ]

        _, sut = parser.OrgContentParser(content).result()

        # one element
        self.assertEqual(len(sut), 1)

        # a list element
        self.assertIsInstance(sut[0], elements.ListElement)

        # list content; first list item
        self.assertEqual(sut[0][0], ['one'])
        self.assertEqual(sut[0][2], ['three'])

        # the sublist
        self.assertIsInstance(sut[0][1], elements.ListElement)
        self.assertEqual(sut[0][1][0], ['two'])

    def test_mixed_bullets(self):
        """The first bullet decides if the list is (un)ordered.
        They type of following bullets in the same list is ignored.
        """

        # NOTE Schlägt fehl, weil hier die bullets gemixed sind. Der Parser
        # nutzt aber jeweils nur die Pattern für ORDERED lists und findet daher
        # in Zeile 2 und drei nichts.
        content = [
            '  12345.   one',
            '    - two',
            '  - three'
        ]
        _, sut = parser.OrgContentParser(content).result()

        # one element
        self.assertEqual(len(sut), 1)

        # a list element
        self.assertIsInstance(sut[0], elements.ListElement)
        self.assertEqual(sut[0].ordered, True)
        self.assertEqual(sut[0][0], ['one'])
        self.assertEqual(sut[0][2], ['three'])

        # the sublist
        self.assertIsInstance(sut[0][1], elements.ListElement)
        self.assertFalse(sut[0][1].ordered)
        self.assertEqual(sut[0][1][0], ['two'])

    def test_list_or_heading_asterix_with_whitespace(self):
        """Special situation with asterix as list item bullet.

        An asterix as a list item bullet is allowed only if there is minimal
        one whitespace in front of it. If not it's not a list item but a
        heading.
        """

        # list with two items
        content = [
            ' * one',
            ' * two'
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 1)
        self.assertEqual(len(sut[0]), 2)

    def test_list_or_heading_asterix_without_whitespace(self):
        """Special situation with asterix as list item bullet.

        An asterix as a list item bullet is allowed only if there is minimal
        one whitespace in front of it. If not it's not a list item but a
        heading.
        """

        # it is a heading
        # no whitespace before the bullet
        content = [
            '* one',
            '* two',
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 2)
        self.assertEqual(sut[0], elements.Heading('* one'))
        self.assertEqual(sut[1], elements.Heading('* two'))

    def test_counters(self):
        """Multiple types of counters"""

        for bullet_a, bullet_b in [('1.', '2.'),
                                   ('1.', '1.'),
                                   ('1)', '2)')]:

            content = [
                f'{bullet_a} one',
                f'{bullet_b} two'
            ]

            with self.subTest(f'{content=}'):
                _, sut = parser.OrgContentParser(content).result()
                self.assertEqual(len(sut), 1)
                self.assertIsInstance(sut[0], elements.ListElement)

    def test_paragraph_before(self):
        """Paragraph before a list."""
        content = [
            'paragraph',
            '- one'
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 2)
        self.assertIsInstance(sut[0], elements.Paragraph)
        self.assertIsInstance(sut[1], elements.ListElement)


class ListsWithLinebreaks(unittest.TestCase):
    """Parse lists with multiline items"""

    def test_one_item(self):
        """One item list with linebreak"""
        content = [
            '- one',
            '  two'
        ]

        _, sut = parser.OrgContentParser(content).result()

        # one element
        self.assertEqual(len(sut), 1)

        # list content; first list item
        self.assertEqual(sut[0][0], ['one two'])

    def test_two_items(self):
        """Items with linebreaks.

        Items can have linebreaks if the indentation of the next line is
        correct. e.g.
        - correct item with
          line break.
        - this incorrect
        because of missing indentation.
        """
        content = [
            ' - one',
            '   two',
            ' - three'
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 1)
        self.assertEqual(len(sut[0]), 2)

        self.assertEqual(sut[0][0], ['one two'])
        self.assertEqual(sut[0][1], ['three'])

    def test_sub_list_with_linebreak_items(self):
        """Sublist with multiline entries.

        Based on Issue #31"""

        content = [
            '- eins',

            '- Lorem ipsum dolor sit amet, consectetur adipisici elit, '
            'sed eiusmod tempor',

            '  incidunt ut labore et dolore magna aliqua',

            '  - sub eins',

            '  - Xorem ipsum dolor sit amet, consectetur adipisici elit,',

            '    incidunt ut labore e dolore magna aliqua',

            '  - drei'
        ]
        _, sut = parser.OrgContentParser(content).result()

        # one main list element
        self.assertEqual(len(sut), 1)
        self.assertIsInstance(sut[0], elements.ListElement)

        # three items
        self.assertEqual(len(sut[0]), 3)

        # item one
        self.assertEqual(sut[0][0], ['eins'])

        # item two
        self.assertEqual(
            sut[0][1],
            [
                'Lorem ipsum dolor sit amet, consectetur adipisici elit, '
                'sed eiusmod tempor'
                ' incidunt ut labore et dolore magna aliqua'
            ]
        )

        # third item is a sub list
        sublist = sut[0][2]
        self.assertEqual(len(sublist), 3)

        # sub lists 1st item
        self.assertEqual(sublist[0], ['sub eins'])

        # sub lists 2nd item
        self.assertEqual(
            sublist[1],
            [
                'Xorem ipsum dolor sit amet, consectetur adipisici elit,'
                ' incidunt ut labore e dolore magna aliqua'
            ]
        )

        # sub lists 3rd item (two runs)
        self.assertEqual(sublist[2], ['drei'])

    def test_sub_list_simple(self):
        """Sublist with simple multiline entries.

        Based on Issue #31"""

        content = [
            '- one',
            '- two',
            '  two-next-line',
            '  - three',
            '  - four',
            '    four-next-line',
            '  - five'
        ]
        _, sut = parser.OrgContentParser(content).result()

        # one list
        self.assertEqual(len(sut), 1)

        # three items
        self.assertEqual(len(sut[0]), 3)

        # 1st item
        self.assertEqual(sut[0][0], ['one'])
        self.assertEqual(sut[0][1], ['two two-next-line'])

        # 3rd item is a sublist with 3 items
        sublist = sut[0][2]
        self.assertEqual(len(sublist), 3)

        # sublists 1st item
        self.assertEqual(sublist[0], ['three'])

        # sublists 2nd item (two runs)
        self.assertEqual(sublist[1], ['four four-next-line'])

        # sublists 3rd item
        self.assertEqual(sublist[2], ['five'])


class ComplexLists(unittest.TestCase):
    """List items can be code blocks, images or something else that is itself
    and element and not just a run."""

    def test_code_block(self):
        """List with one item."""
        content = [
            '- one',
            '- two',
            '  #+begin_src python',
            '  import hyperorg',
            '  #+end_src',
            '- three'
        ]

        _, sut = parser.OrgContentParser(content).result()

        # one list
        self.assertEqual(len(sut), 1)
        self.assertIsInstance(sut[0], elements.ListElement)
        # with three items
        self.assertEqual(len(sut[0]), 3)

        self.assertEqual(sut[0][0], ['one'])
        self.assertEqual(sut[0][2], ['three'])

        item = sut[0][1]
        self.assertEqual(item[0], 'two')
        self.assertEqual(item[1].name, 'src')
        self.assertEqual(item[1].language, 'python')
        self.assertEqual(item[1][0], 'import hyperorg')

    def test_link(self):
        """Items with links"""
        content = [
            ' - [[https://foo.bar][link]]',
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 1)
        self.assertEqual(len(sut[0]), 1)

        expected_link = textruns.Link.from_org_string(
            'https://foo.bar][link'
        )
        self.assertEqual(sut[0][0], [expected_link])

    def test_link_label_with_newline(self):
        """List item contain linebreak in linklabel"""
        content = [
            ' - [[https://foo.bar][link',
            '   with newline]]',
        ]
        _, sut = parser.OrgContentParser(content).result()

        # one list
        self.assertEqual(len(sut), 1)
        # one item
        self.assertEqual(len(sut[0]), 1)
        # with one run
        self.assertEqual(len(sut[0][0]), 1)

        # items first run element (link)
        self.assertEqual(sut[0][0][0].runs, ['link with newline'])
        self.assertEqual(sut[0][0][0].target, 'foo.bar')

    def test_link_target_with_newline(self):
        """List item with link element having a newline in its URL.

        The result is an URL with a blank in it. Of course it breaks the URL
        but org html export does it the same way.
        """
        content = [
            ' - [[https://foo',
            '   .bar][link with newline]]',
        ]
        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 1)
        self.assertEqual(len(sut[0]), 1)
        self.assertEqual(len(sut[0][0]), 1)

        self.assertEqual(sut[0][0][0].runs, ['link with newline'])
        self.assertEqual(sut[0][0][0].target, 'foo .bar')

    def test_whitespace_lines(self):
        """Parse lines with spaces only"""
        content = [
            '1. Gewünschtes Packet installieren:',
            '   #+begin_example',
            '   apt install',
            '   #+end_example',
            # this two "empty" but not zero-length lines are the "problem"
            '   ',
            'Paragraph'
        ]

        _, sut = parser.OrgContentParser(content).result()

        self.assertEqual(len(sut), 2)
        self.assertIsInstance(sut[0], elements.ListElement)
        self.assertIsInstance(sut[1], elements.Paragraph)
