# 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 runs"""
import unittest
from hyperorg import textruns


class TestRun(unittest.TestCase):
    """Runs"""

    # pylint: disable=missing-function-docstring

    class ConcreteRun(textruns.Run):
        """A concrete but simple run just for testing"""
        # pylint: disable=too-few-public-methods
        def __init__(self, count):
            runs = [f'run {i}' for i in range(count)]
            super().__init__(runs=runs)

    def test_abstract(self):
        with self.assertRaises(TypeError):
            textruns.Run()

    def test_len(self):
        sut = __class__.ConcreteRun(7)

        self.assertEqual(len(sut), 7)

    def test_get_item(self):
        sut = __class__.ConcreteRun(7)

        self.assertEqual(sut[0], 'run 0')
        self.assertEqual(sut[5], 'run 5')

    def test_get_item_out_of_index(self):
        sut = __class__.ConcreteRun(7)

        with self.assertRaises(IndexError):
            _ = sut[7]

    def test_equal(self):
        a = __class__.ConcreteRun(2)
        b = __class__.ConcreteRun(2)
        self.assertTrue(a == b)


class TestMarkup(unittest.TestCase):
    """Markup"""

    # pylint: disable=missing-function-docstring

    def test_init(self):
        sut = textruns.Markup(
            'marked',
            textruns.Markup.Kind.IMPORTANT
        )

        self.assertEqual(len(sut), 1)
        self.assertEqual(sut[0], 'marked')
        self.assertEqual(sut.kind, textruns.Markup.Kind.IMPORTANT)

    def test_nested_markup(self):
        """Markup with markup in it."""

        # This is an italic (emphasized) markup with one bold (important) word
        # in it.
        sut = textruns.Markup(
            'foo *important* bar',
            textruns.Token.EMPHASIZED
        )

        self.assertEqual(len(sut), 3)

        self.assertEqual(sut[0], 'foo ')
        self.assertEqual(sut[1][0], 'important')
        self.assertEqual(sut[1].kind, textruns.Markup.Kind.IMPORTANT)
        self.assertEqual(sut[2], ' bar')

    def test_link_inside(self):
        """Link inside the markup"""
        sut = textruns.Markup(
            'foo [[id:123][geordie]] bar',
            textruns.Token.UNDERLINED
        )
        self.assertEqual(len(sut), 3)

        self.assertEqual(sut[0], 'foo ')
        self.assertEqual(sut[1][0], 'geordie')
        self.assertEqual(sut[1].kind, textruns.Link.Kind.ORGID)
        self.assertEqual(sut[1].target, '123')
        self.assertEqual(sut[2], ' bar')

    def test_equal(self):
        sut_a = textruns.Markup('foobar', textruns.Markup.Kind.IMPORTANT)
        sut_b = textruns.Markup('foobar', textruns.Markup.Kind.IMPORTANT)
        sut_c = textruns.Markup('foobar', textruns.Markup.Kind.UNDERLINED)

        self.assertTrue(sut_a == sut_b)
        self.assertFalse(sut_a == sut_c)


class TestLink(unittest.TestCase):
    """Test Link class."""

    # pylint: disable=missing-function-docstring
    def test_init_label(self):
        """Usual init call.

        Be aware that the opening ``[[`` and closing ``]]`` are removed before
        the Link run is initialized. That re-movement happens on the layer of
        the `element` module.
        """
        link = textruns.Link('id:target][label')

        self.assertEqual(link[0], 'label')
        self.assertEqual(link.target, 'target')
        self.assertEqual(link.kind, textruns.Link.Kind.ORGID)

    def test_init_target_only(self):
        link = textruns.Link('id:target')

        self.assertEqual(len(link), 0)
        self.assertEqual(link.target, 'target')
        self.assertEqual(link.kind, textruns.Link.Kind.ORGID)

    def test_init_all_kinds(self):

        test_data = {kind: f'{kind.value}:foo' for kind in textruns.Link.Kind}

        test_data[textruns.Link.Kind.NONE] \
            = test_data[textruns.Link.Kind.NONE].replace(':', '')
        test_data[textruns.Link.Kind.HTTPS] \
            = test_data[textruns.Link.Kind.HTTPS].replace(':', '://')

        for kind in test_data:

            sut = textruns.Link(test_data[kind])

            self.assertEqual(sut.kind, kind)

    def test_case_insensitive_kinds(self):
        test_data = {
            textruns.Link.Kind.ORGID: (
                'ID:', 'id:', 'Id:', 'iD:'),
            textruns.Link.Kind.HTTPS: (
                'https://', 'HTTPS://', 'Https://', 'HTtPs://', 'httPs://'),
            textruns.Link.Kind.FILE: (
                'fIle:', 'fiLE:', 'FILE:', 'file:')
        }

        # pylint: disable-next=consider-using-dict-items
        test_data = [(k, v) for k in test_data for v in test_data[k]]

        for kind, link_prefix in test_data:
            with self.subTest((kind, link_prefix)):

                sut = textruns.Link(f'{link_prefix}foobar')

                self.assertEqual(sut.kind, kind)

    def test_no_kind_links(self):
        """Links without kind are OK. """
        link = textruns.Link('foo')

        self.assertEqual(link.kind, textruns.Link.Kind.NONE)

    def test_init_unknown_kind(self):
        """Links with unknown kind are not OK."""
        with self.assertRaises(ValueError):
            textruns.Link('foo:bar')

    def test_equal(self):
        sut_a = textruns.Link('foo][bar')
        sut_b = textruns.Link('foo][bar')
        sut_c = textruns.Link('id:foo][bar')
        sut_d = textruns.Link('fxx][bar')

        self.assertTrue(sut_a == sut_b)
        self.assertFalse(sut_a == sut_c)
        self.assertFalse(sut_a == sut_d)

    def test_kind_target_labels(self):
        test_data = (
            ('id:foo][bar', ('id', 'foo', 'bar')),
            ('https:foo][bar', ('https', 'foo', 'bar')),
            ('foo][bar', (None, 'foo', 'bar')),
            ('id:foo', ('id', 'foo', None)),
            ('id:foo][bar:extra', ('id', 'foo', 'bar:extra')),
            ('foo][bar:extra', (None, 'foo', 'bar:extra')),
        )

        for sut, expect in test_data:
            with self.subTest(sut):
                # pylint: disable=protected-access
                self.assertEqual(
                    textruns.Link._REX_KIND_TARGET_LABEL.match(sut).groups(),
                    expect
                )


class TestImage(unittest.TestCase):
    """Image"""

    # pylint: disable=missing-function-docstring

    def test_target(self):
        sut = textruns.Image('img.png')
        self.assertEqual(sut.target, 'img.png')

    def test_alternative(self):
        sut = textruns.Image('img.png')
        self.assertEqual(sut.alternative, 'img.png')

    def test_alternative_https(self):
        sut = textruns.Image('https://foo.bar/img.png')
        self.assertEqual(sut.alternative, 'img.png')

    def test_leading_folder_seperator(self):
        sut = textruns.Image('/img.png')

        self.assertEqual(sut.target, '/img.png')


class TokenPositions(unittest.TestCase):
    """Test `textruns._token_position()`"""

    # pylint: disable=missing-function-docstring,protected-access
    def test_token_close(self):
        self.assertEqual(
            textruns._token_positions('Foo* bar'),
            [(3, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE)]
        )

    def test_token_open(self):
        self.assertEqual(
            textruns._token_positions('Foo *bar'),
            [(4, textruns.Token.IMPORTANT, textruns.TokenState.OPEN)]
        )

    def test_all_tokens(self):
        """All kinds of tokens."""
        for token in textruns.Token:

            if token is not textruns.Token.LINK:
                text = f'Foo {token.value}markup{token.value} bar'
            else:
                text = 'Foo [[marku]] bar'

            expect = [
                (4, token, textruns.TokenState.OPEN),
                (11, token, textruns.TokenState.CLOSE)
            ]

            result = textruns._token_positions(text)

            self.assertEqual(result, expect)

    def test_side_by_side(self):
        """One markup after the other."""
        text = 'Foo ~code~ and /italic/ bar'
        self.assertEqual(
            textruns._token_positions(text),
            [
                (4, textruns.Token.CODE, textruns.TokenState.OPEN),
                (9, textruns.Token.CODE, textruns.TokenState.CLOSE),
                (15, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
                (22, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            ]
        )

    def test_entangled(self):
        """Entangled markup tokens.

        It doesn't matter at this point if this are correct pairs or not.
        """

        text = '/Worf *Picard/ Diana*'
        self.assertEqual(
            textruns._token_positions(text),
            [
                (0, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
                (6, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
                (13, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
                (20, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE),
            ]
        )

    def test_enclosed(self):
        """Nested markup tokens."""
        text = 'Frank /Burns *is* a/ fret.'
        self.assertEqual(
            textruns._token_positions(text),
            [
                (6, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
                (13, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
                (16, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE),
                (19, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            ]
        )

    def test_link(self):
        """Link tokens."""
        text = 'Giants [[id:1234][Foo]] Cyberspace'
        result = textruns._token_positions(text)

        self.assertEqual(
            result,
            [
                (7, textruns.Token.LINK, textruns.TokenState.OPEN),
                (21, textruns.Token.LINK, textruns.TokenState.CLOSE)
            ]
        )

    def test_none_token(self):
        """
        Blanks after OPEN or before CLOSE is not allowed. It is not a token.
        """
        self.assertEqual(
            textruns._token_positions(" / foobar /"),
            []
        )


class TokenPairs(unittest.TestCase):
    """Test `textruns._token_pairs()`"""

    # pylint: disable=missing-function-docstring,protected-access
    def test_all_tokens(self):
        for token in textruns.Token:
            positions = [
                (6, token, textruns.TokenState.OPEN),
                (20, token, textruns.TokenState.CLOSE)]
            expect = [(token, 6, 20)]

        self.assertEqual(textruns._token_pairs(positions), expect)

    def test_side_by_side(self):
        positions = [
            (2, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
            (6, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            (9, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
            (14, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE)
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [
                (textruns.Token.EMPHASIZED, 2, 6),
                (textruns.Token.IMPORTANT, 9, 14),
            ]
        )

    def test_enclosed(self):
        """The encloused markup is deleted.

        The intention is to parse the inner content of the markup in another
        iteration.
        """

        # The two inner tokens are ignored
        positions = [
            (2, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
            (9, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
            (14, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE),
            (20, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [
                (textruns.Token.EMPHASIZED, 2, 20),
            ]
        )

    def test_entangled(self):
        """First wins.

        The pairs are entangled like 'a /b *c/ d* e'. The first wins means
        that the emphasized is closed first and valid in that case.
        """
        positions = [
            (0, textruns.Token.EMPHASIZED, textruns.TokenState.OPEN),
            (6, textruns.Token.IMPORTANT, textruns.TokenState.OPEN),
            (13, textruns.Token.EMPHASIZED, textruns.TokenState.CLOSE),
            (20, textruns.Token.IMPORTANT, textruns.TokenState.CLOSE)
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [(textruns.Token.EMPHASIZED, 0, 13)]
        )

    def test_link(self):
        positions = [
            (7, textruns.Token.LINK, textruns.TokenState.OPEN),
            (21, textruns.Token.LINK, textruns.TokenState.CLOSE)
        ]

        self.assertEqual(
            textruns._token_pairs(positions),
            [(textruns.Token.LINK, 7, 21)]
        )


class RunsFromTokenPairs(unittest.TestCase):
    """Test `textruns._runs_from_token_pairs()`"""

    # pylint: disable=missing-function-docstring,protected-access
    def test_all_tokens_except_link(self):
        """Testing all token types.

        The token character/marker used in the original string doesn't
        matter. Only its position does. Because of that here ``X`` is used.
        """

        # Tokens at index position 6 and 11
        text = 'Riker XwithX Data'

        for token in textruns.Token:

            # don't test links this way
            if token == textruns.Token.LINK:
                continue

            pairs = [(token, 6, 11)]

            result = textruns._runs_from_token_pairs(text, pairs)

            self.assertEqual(str(result[0]), 'Riker ')
            self.assertEqual(str(result[1].runs[0]), 'with')
            self.assertEqual(result[1].kind.value, token.value)
            self.assertEqual(str(result[2]), ' Data')

    def test_link(self):
        text = 'Riker [[id:with]] Data'
        pairs = [(textruns.Token.LINK, 6, 15)]

        result = textruns._runs_from_token_pairs(text, pairs)

        self.assertEqual(result[0], 'Riker ')
        self.assertEqual(len(result[1]), 0)
        self.assertEqual(result[1].target, 'with')
        self.assertEqual(result[1].kind, textruns.Link.Kind.ORGID)
        self.assertEqual(result[2], ' Data')

    def test_side_by_side(self):
        text = 'Foo ~code~ and /italic/ bar'
        pairs = [
            (textruns.Token.CODE, 4, 9),
            (textruns.Token.EMPHASIZED, 15, 22),
        ]
        result = textruns._runs_from_token_pairs(text, pairs)

        # text runs
        self.assertEqual(str(result[0]), 'Foo ')
        self.assertEqual(str(result[2]), ' and ')
        self.assertEqual(str(result[4]), ' bar')

        # markups
        self.assertEqual(str(result[1].runs[0]), 'code')
        self.assertEqual(str(result[3].runs[0]), 'italic')
        self.assertEqual(result[1].kind, textruns.Markup.Kind.CODE)
        self.assertEqual(result[3].kind, textruns.Markup.Kind.EMPHASIZED)


class CutIntoRuns(unittest.TestCase):
    """Test conversion from text to runs.

    Here with `textruns.cut_into_runs()` the combination of the three
    functions `textruns._token_positions()`, `textruns._token_pairs()` and
    `textruns._runs_from_token_pairs()` is tested.
    """

    # pylint: disable=missing-function-docstring
    def test_text_only(self):
        """One simple text run."""
        text = 'Another one got caught today'
        result = textruns.cut_into_runs(text)

        self.assertEqual(len(result), 1)
        self.assertIsInstance(result[0], str)

    def test_empty_string(self):
        """Empty strings.

        This is e.g. useful with list elements where empty
        list entries are allowed.
        """
        result = textruns.cut_into_runs('')
        self.assertEqual(len(result), 1)
        self.assertEqual(result[0], '')

    def test_verbatim_only(self):
        sut = textruns.cut_into_runs('=--foo=')

        self.assertEqual(len(sut), 1)
        self.assertEqual(
            sut[0],
            textruns.Markup('--foo', textruns.Markup.Kind.VERBATIM)
        )

    def test_markup_combined(self):
        """Bigger text with multiple markup in it."""
        text = (
            '+Governments+ of the *Industrial World*, you weary =giants= of '
            'flesh and steel, I come from _Cyberspace_, the new home of Mind. '
            'On /behalf of the future/, I ask you of the past to leave us '
            'alone. You are ~not welcome~ among us. You have no sovereignty '
            'where we gather.'
        )

        result = textruns.cut_into_runs(text)

        self.assertEqual(len(result), 12)

        expected_parts = {
            1: ' of the ',
            3: ', you weary ',
            5: ' of flesh and steel, I come from ',
            7: ', the new home of Mind. On ',
            9: ', I ask you of the past to leave us alone. You are ',
            11: ' among us. You have no sovereignty where we gather.'
        }
        for idx, exp in expected_parts.items():
            self.assertEqual(str(result[idx]), exp)

        expected_parts = {
            0: 'Governments',
            2: 'Industrial World',
            4: 'giants',
            6: 'Cyberspace',
            8: 'behalf of the future',
            10: 'not welcome',
        }
        for idx, exp in expected_parts.items():
            self.assertEqual(str(result[idx].runs[0]), exp)

        marker_at_index = [
            (0, textruns.Markup.Kind.STRIKETHROUGH),
            (2, textruns.Markup.Kind.IMPORTANT),
            (4, textruns.Markup.Kind.VERBATIM),
            (6, textruns.Markup.Kind.UNDERLINED),
            (8, textruns.Markup.Kind.EMPHASIZED),
            (10, textruns.Markup.Kind.CODE)
        ]

        for idx, kind in marker_at_index:
            self.assertEqual(result[idx].kind, kind)

    def test_links_combined_two(self):
        test = (
            'This is [[id:1234][World]] and [[roam:Buhtz][Me]] ende'
        )

        result = textruns.cut_into_runs(test)

        self.assertEqual(len(result), 5)

        self.assertEqual(result[0], 'This is ')
        self.assertEqual(result[1][0], 'World')
        self.assertEqual(result[1].target, '1234')
        self.assertEqual(result[2], ' and ')
        self.assertEqual(result[3][0], 'Me')
        self.assertEqual(result[3].target, 'Buhtz')
        self.assertEqual(result[4], ' ende')

    def test_links_combined_all(self):
        test = (
            'This is [[https://world.me][World]] and [[roam:Buhtz][Me]] '
            'or just [[roam:Buhtz]]. Also try [[id:12345][with label]] or '
            'that [[https://out.there]] somewhere.'
        )

        result = textruns.cut_into_runs(test)

        self.assertEqual(len(result), 11)

        self.assertEqual(result[1][0], 'World')
        self.assertEqual(result[1].target, 'world.me')
        self.assertEqual(result[3][0], 'Me')
        self.assertEqual(result[3].target, 'Buhtz')
        self.assertEqual(len(result[5]), 0)
        self.assertEqual(result[5].target, 'Buhtz')
        self.assertEqual(result[7][0], 'with label')
        self.assertEqual(result[7].target, '12345')
        self.assertEqual(len(result[9]), 0)
        self.assertEqual(result[9].target, 'out.there')

    def test_links_and_markup_neighbors(self):
        test = 'you _weary_ [[https://big.com][giants]]'

        result = textruns.cut_into_runs(test)

        self.assertEqual(len(result), 4)

        self.assertEqual(result[0], 'you ')
        self.assertEqual(result[1][0], 'weary')
        self.assertEqual(result[1].kind, textruns.Markup.Kind.UNDERLINED)
        self.assertEqual(result[2], ' ')
        self.assertEqual(result[3][0], 'giants')
        self.assertEqual(result[3].target, 'big.com')
        self.assertEqual(result[3].kind, textruns.Link.Kind.HTTPS)

    def test_links_variants(self):
        test_data = [
            (  # orgid
                'This [[id:0123456789][is an OrgId link]] test.',
                {
                    1: [textruns.Link.Kind.ORGID,
                        '0123456789', 'is an OrgId link'],
                },
            ),
            (  # roam with labels
                'This [[roam:0123456789][is a Roam link]] test.',
                {
                    1: [textruns.Link.Kind.ROAM,
                        '0123456789', 'is a Roam link'],
                },
            ),
            (  # roam without labels
                'This [[roam:Buhtz]] test.',
                {
                    1: [textruns.Link.Kind.ROAM, 'Buhtz', None],
                },
            ),
            (  # https with label
                'This [[https://my.world][Buhtz]] test.',
                {
                    1: [textruns.Link.Kind.HTTPS, 'my.world', 'Buhtz'],
                },
            ),
            (  # two orgid
                'This [[id:01234][Foo]] and [[id:5678][bar]].',
                {
                    1: [textruns.Link.Kind.ORGID, '01234', 'Foo'],
                    3: [textruns.Link.Kind.ORGID, '5678', 'bar'],
                },
            ),
        ]

        for text, links in test_data:
            result = textruns.cut_into_runs(text)

            for idx, lnk in links.items():
                kind, target, label = lnk

                self.assertEqual(result[idx].kind, kind)
                self.assertEqual(result[idx].target, target)

                if label:
                    self.assertEqual(result[idx][0], label)
                else:
                    self.assertEqual(len(result[idx]), 0)

    def test_links_label_newline(self):
        text = 'This [[id:123][is an\nOrgId link]] test.'
        result = textruns.cut_into_runs(text)

        self.assertEqual(result[1][0], 'is an\nOrgId link')

    def test_url_with_slash_at_end(self):
        """If there is a slash at the end of an URL it was (see #64)
        interpreted as emphasized inline markup.
        """
        text = 'Foo [[https://my.world/]] bar'
        result = textruns.cut_into_runs(text)

        self.assertEqual(result[0], 'Foo ')
        self.assertEqual(len(result[1]), 0)
        self.assertEqual(result[1].target, 'my.world/')
        self.assertEqual(result[2], ' bar')

    def test_multiple_links_and_markup_all(self):
        test = (
            '*Governments* of the [[roam:Industrial World][IW]], you _weary_'
            ' [[https://big.com][giants]] /of/ [[id:1854][flesh]] and '
            '=steel=, +I+ come from [[roam:Cyberspace]], the ~new home~ of '
            '[[https://Mind.org]]. On behalf of the future'
        )

        result = textruns.cut_into_runs(test)

        self.assertEqual(len(result), 22)

        # the key here is the index in ``result``
        check_links = {
            2: ('Industrial World', 'IW', textruns.Link.Kind.ROAM),
            6: ('big.com', 'giants', textruns.Link.Kind.HTTPS),
            10: ('1854', 'flesh', textruns.Link.Kind.ORGID),
            16: ('Cyberspace', None, textruns.Link.Kind.ROAM),
            20: ('Mind.org', None, textruns.Link.Kind.HTTPS)
        }

        for idx_key, links in check_links.items():
            target, label, kind = links
            self.assertEqual(result[idx_key].target, target)

            if label:
                self.assertEqual(result[idx_key][0], label)
            else:
                self.assertEqual(len(result[idx_key]), 0)

            self.assertEqual(result[idx_key].kind, kind)

        check_markup = {
            0: ('Governments', textruns.Markup.Kind.IMPORTANT),
            4: ('weary', textruns.Markup.Kind.UNDERLINED),
            8: ('of', textruns.Markup.Kind.EMPHASIZED),
            12: ('steel', textruns.Markup.Kind.VERBATIM),
            14: ('I', textruns.Markup.Kind.STRIKETHROUGH),
            18: ('new home', textruns.Markup.Kind.CODE)
        }

        for idx_key, markup in check_markup.items():
            text, kind = markup
            self.assertEqual(len(result[idx_key]), 1)
            self.assertEqual(result[idx_key][0], text)
            self.assertEqual(result[idx_key].kind, kind)

    def test_markup_without_prefix_suffix(self):
        sut = textruns.cut_into_runs('+Government+')

        self.assertEqual(len(sut), 1)

        self.assertEqual(sut[0][0], 'Government')
        self.assertEqual(sut[0].kind, textruns.Markup.Kind.STRIKETHROUGH)

    def test_markup_prefix_suffix(self):
        result = textruns.cut_into_runs('foo +Government+ bar')

        self.assertEqual(len(result), 3)
        self.assertEqual(str(result[0]), 'foo ')
        self.assertEqual(str(result[2]), ' bar')

        self.assertIsInstance(result[1], textruns.Markup)
        m = result[1]

        self.assertEqual(str(m.runs[0]), 'Government')
        self.assertEqual(m.kind, textruns.Markup.Kind.STRIKETHROUGH)

    def test_markup_variant_blanks(self):
        result = textruns.cut_into_runs(' +Government+ ')

        self.assertEqual(len(result), 3)
        self.assertEqual(str(result[0]), ' ')
        self.assertEqual(str(result[2]), ' ')

        self.assertEqual(str(result[1].runs[0]), 'Government')
        self.assertEqual(result[1].kind, textruns.Markup.Kind.STRIKETHROUGH)

    def test_markup_two(self):
        for kind in textruns.Markup.Kind:
            text = 'Governments {m}of{m} the {m}Industrial{m} World' \
                   .format(m=kind.value)

            result = textruns.cut_into_runs(text)

            self.assertEqual(len(result), 5, kind)
            self.assertEqual(str(result[0]), 'Governments ', kind)
            self.assertEqual(str(result[1].runs[0]), 'of', kind)
            self.assertEqual(str(result[2]), ' the ', kind)
            self.assertEqual(str(result[3].runs[0]), 'Industrial', kind)
            self.assertEqual(str(result[4]), ' World', kind)

    def test_markup_two_without_pre_suffix(self):
        for kind in textruns.Markup.Kind:
            text = '{m}Governments{m} of {m}the{m} Industrial {m}World{m}' \
                   .format(m=kind.value)

            result = textruns.cut_into_runs(text)

            self.assertEqual(len(result), 5, kind)
            self.assertEqual(str(result[0].runs[0]), 'Governments', kind)
            self.assertEqual(str(result[1]), ' of ', kind)
            self.assertEqual(str(result[2].runs[0]), 'the', kind)
            self.assertEqual(str(result[3]), ' Industrial ', kind)
            self.assertEqual(str(result[4].runs[0]), 'World', kind)

    def test_markup_entangled(self):
        """First wins."""
        text = '/italics ~code/ verbatim~'
        result = textruns.cut_into_runs(text)

        self.assertEqual(len(result), 2)
        self.assertEqual(str(result[0].runs[0]), 'italics ~code')
        self.assertEqual(str(result[1]), ' verbatim~')

    def test_unsecure_http(self):
        """HTTP links are not secure but will be parsed.

            See #29 about an improvement of that behavior
            unsafe (because of HTTP-only) hyperlinks not parsed
        """
        text = 'This [[http://foo.bar][foobar]] is not safe.'
        result = textruns.cut_into_runs(text)

        self.assertEqual(len(result), 3)
        self.assertEqual(result[1][0], 'foobar')
        self.assertEqual(result[1].target, 'foo.bar')
        self.assertEqual(result[1].kind, textruns.Link.Kind.HTTP)


class DontTouchVerbatimOrCode(unittest.TestCase):
    """Test special rule that content enclosed by verbatim or code markups
    (``=`` and ``~``) should not be parsed or cut.
    """

    # pylint: disable=missing-function-docstring

    def test_markup_class(self):
        """Class `textruns.Markup` shouldn't parse verbatim/code."""

        for text in ['[[id:123][geordie]]',
                     '*don\'t parse it*']:

            for kind in [textruns.Markup.Kind.CODE,
                         textruns.Markup.Kind.VERBATIM]:

                m = textruns.Markup(text, kind)

                self.assertEqual(len(m.runs), 1)
                self.assertEqual(str(m.runs[0]), text)

    def test_ignore_links(self):
        """Don't treat links when enclosded by verbatim or code markup."""
        for kind in [textruns.Markup.Kind.CODE,
                     textruns.Markup.Kind.VERBATIM]:
            text = 'Foo {m}very [[id:1234][important]] link{m} bar' \
                   .format(m=kind.value)

            result = textruns.cut_into_runs(text)

            self.assertEqual(len(result), 3)
            self.assertIsInstance(result[1], textruns.Markup)

            self.assertEqual(len(result[1].runs), 1)
            self.assertEqual(str(result[1].runs[0]),
                             'very [[id:1234][important]] link')

            self.assertEqual(result[1].kind, kind)

    def test_ignore_markup(self):
        for vc in [textruns.Markup.Kind.CODE,
                   textruns.Markup.Kind.VERBATIM]:
            for kind in textruns.Markup.Kind:

                if kind == vc:
                    continue

                text = 'Foo {vc}with {m}in-markup{m} bar{vc} fin.'.format(
                    vc=vc.value, m=kind.value)

                result = textruns.cut_into_runs(text)

                self.assertEqual(len(result), 3)
                self.assertIsInstance(result[1], textruns.Markup)

                self.assertEqual(len(result[1].runs), 1)
                self.assertEqual(
                    str(result[1].runs[0]),
                    f'with {kind.value}in-markup{kind.value} bar')


class NestedLinkAndMarkup(unittest.TestCase):
    """Test special situation with nested markup/links."""

    # pylint: disable=missing-function-docstring

    def test_markup_in_link(self):
        text = 'Foo [[id:1234][This is *important*]] bar'
        result = textruns.cut_into_runs(text)

        self.assertEqual(len(result), 3)
        self.assertEqual(result[0], 'Foo ')
        self.assertEqual(len(result[1]), 2)
        self.assertEqual(result[1][0], 'This is ')
        self.assertEqual(result[1].kind, textruns.Link.Kind.ORGID)
        self.assertEqual(result[1][1][0], 'important')
        self.assertEqual(result[1][1].kind, textruns.Markup.Kind.IMPORTANT)
        self.assertEqual(result[2], ' bar')

    def test_link_in_markup(self):
        text = 'Foo *very [[id:1234][important]] link* bar'
        result = textruns.cut_into_runs(text)

        self.assertEqual(len(result), 3)

        self.assertEqual(result[0], 'Foo ')
        self.assertEqual(len(result[1]), 3)
        self.assertEqual(result[1][0], 'very ')
        self.assertEqual(result[1][1][0], 'important')
        self.assertEqual(result[1][1].target, '1234')
        self.assertEqual(result[1][1].kind, textruns.Link.Kind.ORGID)
        self.assertEqual(result[1][2], ' link')
        self.assertEqual(result[1].kind, textruns.Markup.Kind.IMPORTANT)
        self.assertEqual(result[2], ' bar')


class ImagesAndLinks(unittest.TestCase):
    """Images and Links"""
    def test_inline_image(self):
        """Simple inline image."""
        sut = 'Foo [[img.png]] bar'

        result = textruns.cut_into_runs(sut)

        self.assertEqual(len(result), 3)
        self.assertIsInstance(result[1], textruns.Image)
        image = result[1]
        self.assertEqual(image.target, 'img.png')

    def test_inline_image_file_kind(self):
        """Inline image with "file:" prefix"""
        sut = 'Foo [[file:img.png]] bar'

        result = textruns.cut_into_runs(sut)

        self.assertEqual(len(result), 3)
        self.assertEqual(result[1].target, 'img.png')

    def test_inline_linked_image_locale_file(self):
        """Linked inline image with "file:" prefix"""
        sut = 'Foo [[linktarget.png][file:labelimage.png]] bar'

        result = textruns.cut_into_runs(sut)
        self.assertEqual(len(result), 3)

        link = result[1]
        self.assertIsInstance(link, textruns.Link)

        self.assertEqual(link.kind, textruns.Link.Kind.NONE)
        self.assertEqual(link.target, 'linktarget.png')

        # The "label" of the link is an image
        image = link[0]
        self.assertIsInstance(image, textruns.Image)
        self.assertEqual(image.target, 'labelimage.png')

    def test_inline_linked_image_https(self):
        """Linked inline image with "https:" prefix"""
        sut = 'Foo [[linktarget.png][https://labelimage.png]] bar'

        result = textruns.cut_into_runs(sut)
        self.assertEqual(len(result), 3)

        link = result[1]
        self.assertIsInstance(link, textruns.Link)

        self.assertEqual(link.kind, textruns.Link.Kind.NONE)
        self.assertEqual(link.target, 'linktarget.png')

        # The "label" of the link is an image
        image = link[0]
        self.assertIsInstance(image, textruns.Image)
        self.assertEqual(image.target, 'https://labelimage.png')

    def test_not_a_linked_image(self):
        """Just a linked text."""

        # Images as labels are only recoginied with file: or https:// prefix.
        # Otherwise it is just a string.
        sut = 'Foo [[linktarget.png][labelimage.png]] bar'

        result = textruns.cut_into_runs(sut)

        link = result[1]

        label = link[0]
        self.assertEqual(label, 'labelimage.png')
