"""
Python Markdown

A Python implementation of John Gruber's Markdown.

Documentation: https://python-markdown.github.io/
GitHub: https://github.com/Python-Markdown/markdown/
PyPI: https://pypi.org/project/Markdown/

Started by Manfred Stienstra (http://www.dwerg.net/).
Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
Currently maintained by Waylan Limberg (https://github.com/waylan),
Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).

Copyright 2007-2019 The Python Markdown Project (v. 1.7 and later)
Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
Copyright 2004 Manfred Stienstra (the original version)

License: BSD (see LICENSE.md for details).
"""

from markdown.test_tools import TestCase
from markdown.extensions.codehilite import CodeHiliteExtension, CodeHilite
import os

try:
    import pygments  # noqa
    has_pygments = True
except ImportError:
    has_pygments = False

# The version required by the tests is the version specified and installed in the 'pygments' tox env.
# In any environment where the PYGMENTS_VERSION environment variabe is either not defined or doesn't
# match the version of Pygments installed, all tests which rely in pygments will be skipped.
required_pygments_version = os.environ.get('PYGMENTS_VERSION', '')


class TestCodeHiliteClass(TestCase):
    """ Test the markdown.extensions.codehilite.CodeHilite class. """

    def setUp(self):
        if has_pygments and pygments.__version__ != required_pygments_version:
            self.skipTest(f'Pygments=={required_pygments_version} is required')

    maxDiff = None

    def assertOutputEquals(self, source, expected, **options):
        """
        Test that source code block results in the expected output with given options.
        """

        output = CodeHilite(source, **options).hilite()
        self.assertMultiLineEqual(output.strip(), expected)

    def test_codehilite_defaults(self):
        if has_pygments:
            # Odd result as no lang given and a single comment is not enough for guessing.
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code># A Code Comment\n'
                '</code></pre>'
            )
        self.assertOutputEquals('# A Code Comment', expected)

    def test_codehilite_guess_lang(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="cp">&lt;?php</span> '
                '<span class="k">print</span><span class="p">(</span><span class="s2">&quot;Hello World&quot;</span>'
                '<span class="p">);</span> <span class="cp">?&gt;</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code>&lt;?php print(&quot;Hello World&quot;); ?&gt;\n'
                '</code></pre>'
            )
        # Use PHP as the the starting `<?php` tag ensures an accurate guess.
        self.assertOutputEquals('<?php print("Hello World"); ?>', expected, guess_lang=True)

    def test_codehilite_guess_lang_plain_text(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="err">plain text</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code>plain text\n'
                '</code></pre>'
            )
        # This will be difficult to guess.
        self.assertOutputEquals('plain text', expected, guess_lang=True)

    def test_codehilite_set_lang(self):
        if has_pygments:
            # Note an extra `<span class="x">` is added to end of code block when lang explicitly set.
            # Compare with expected output for `test_guess_lang`. Not sure why this happens.
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="cp">&lt;?php</span> '
                '<span class="k">print</span><span class="p">(</span><span class="s2">&quot;Hello World&quot;</span>'
                '<span class="p">);</span> <span class="cp">?&gt;</span><span class="x"></span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-php">&lt;?php print(&quot;Hello World&quot;); ?&gt;\n'
                '</code></pre>'
            )
        self.assertOutputEquals('<?php print("Hello World"); ?>', expected, lang='php')

    def test_codehilite_bad_lang(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="cp">&lt;?php</span> '
                '<span class="k">print</span><span class="p">(</span><span class="s2">'
                '&quot;Hello World&quot;</span><span class="p">);</span> <span class="cp">?&gt;</span>\n'
                '</code></pre></div>'
            )
        else:
            # Note that without pygments there is no way to check that the language name is bad.
            expected = (
                '<pre class="codehilite"><code class="language-unkown">'
                '&lt;?php print(&quot;Hello World&quot;); ?&gt;\n'
                '</code></pre>'
            )
        # The starting `<?php` tag ensures an accurate guess.
        self.assertOutputEquals('<?php print("Hello World"); ?>', expected, lang='unkown')

    def test_codehilite_use_pygments_false(self):
        expected = (
            '<pre class="codehilite"><code class="language-php">&lt;?php print(&quot;Hello World&quot;); ?&gt;\n'
            '</code></pre>'
        )
        self.assertOutputEquals('<?php print("Hello World"); ?>', expected, lang='php', use_pygments=False)

    def test_codehilite_lang_prefix_empty(self):
        expected = (
            '<pre class="codehilite"><code class="php">&lt;?php print(&quot;Hello World&quot;); ?&gt;\n'
            '</code></pre>'
        )
        self.assertOutputEquals(
            '<?php print("Hello World"); ?>', expected, lang='php', use_pygments=False, lang_prefix=''
        )

    def test_codehilite_lang_prefix(self):
        expected = (
            '<pre class="codehilite"><code class="lang-php">&lt;?php print(&quot;Hello World&quot;); ?&gt;\n'
            '</code></pre>'
        )
        self.assertOutputEquals(
            '<?php print("Hello World"); ?>', expected, lang='php', use_pygments=False, lang_prefix='lang-'
        )

    def test_codehilite_linenos_true(self):
        if has_pygments:
            expected = (
                '<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div>'
                '</td><td class="code"><div class="codehilite"><pre><span></span><code>plain text\n'
                '</code></pre></div>\n'
                '</td></tr></table>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', linenos=True)

    def test_codehilite_linenos_false(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code>plain text\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', linenos=False)

    def test_codehilite_linenos_none(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code>plain text\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', linenos=None)

    def test_codehilite_linenos_table(self):
        if has_pygments:
            expected = (
                '<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div>'
                '</td><td class="code"><div class="codehilite"><pre><span></span><code>plain text\n'
                '</code></pre></div>\n'
                '</td></tr></table>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', linenos='table')

    def test_codehilite_linenos_inline(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="linenos">1</span>plain text\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', linenos='inline')

    def test_codehilite_linenums_true(self):
        if has_pygments:
            expected = (
                '<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div>'
                '</td><td class="code"><div class="codehilite"><pre><span></span><code>plain text\n'
                '</code></pre></div>\n'
                '</td></tr></table>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', linenums=True)

    def test_codehilite_set_cssclass(self):
        if has_pygments:
            expected = (
                '<div class="override"><pre><span></span><code>plain text\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="override"><code class="language-text">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', cssclass='override')

    def test_codehilite_set_css_class(self):
        if has_pygments:
            expected = (
                '<div class="override"><pre><span></span><code>plain text\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="override"><code class="language-text">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', css_class='override')

    def test_codehilite_linenostart(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="linenos">42</span>plain text\n'
                '</code></pre></div>'
            )
        else:
            # TODO: Implement linenostart for no-pygments. Will need to check what JS libs look for.
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">plain text\n'
                '</code></pre>'
            )
        self.assertOutputEquals('plain text', expected, lang='text', linenos='inline', linenostart=42)

    def test_codehilite_linenos_hl_lines(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code>'
                '<span class="linenos">1</span><span class="hll">line 1\n'
                '</span><span class="linenos">2</span>line 2\n'
                '<span class="linenos">3</span><span class="hll">line 3\n'
                '</span></code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">line 1\n'
                'line 2\n'
                'line 3\n'
                '</code></pre>'
            )
        self.assertOutputEquals('line 1\nline 2\nline 3', expected, lang='text', linenos='inline', hl_lines=[1, 3])

    def test_codehilite_linenos_linenostep(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="linenos"> </span>line 1\n'
                '<span class="linenos">2</span>line 2\n'
                '<span class="linenos"> </span>line 3\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">line 1\n'
                'line 2\n'
                'line 3\n'
                '</code></pre>'
            )
        self.assertOutputEquals('line 1\nline 2\nline 3', expected, lang='text', linenos='inline', linenostep=2)

    def test_codehilite_linenos_linenospecial(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="linenos">1</span>line 1\n'
                '<span class="linenos special">2</span>line 2\n'
                '<span class="linenos">3</span>line 3\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text linenums">line 1\n'
                'line 2\n'
                'line 3\n'
                '</code></pre>'
            )
        self.assertOutputEquals('line 1\nline 2\nline 3', expected, lang='text', linenos='inline', linenospecial=2)

    def test_codehilite_startinline(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="k">print</span><span class="p">(</span>'
                '<span class="s2">&quot;Hello World&quot;</span><span class="p">);</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-php">print(&quot;Hello World&quot;);\n'
                '</code></pre>'
            )
        self.assertOutputEquals('print("Hello World");', expected, lang='php', startinline=True)


class TestCodeHiliteExtension(TestCase):
    """ Test codehilite extension. """

    def setUp(self):
        if has_pygments and pygments.__version__ != required_pygments_version:
            self.skipTest(f'Pygments=={required_pygments_version} is required')

    maxDiff = None

    def testBasicCodeHilite(self):
        if has_pygments:
            # Odd result as no lang given and a single comment is not enough for guessing.
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code># A Code Comment\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            '\t# A Code Comment',
            expected,
            extensions=['codehilite']
        )

    def testLinenumsTrue(self):
        if has_pygments:
            expected = (
                '<table class="codehilitetable"><tr>'
                '<td class="linenos"><div class="linenodiv"><pre>1</pre></div></td>'
                '<td class="code"><div class="codehilite"><pre><span></span>'
                '<code><span class="err"># A Code Comment</span>\n'
                '</code></pre></div>\n'
                '</td></tr></table>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="linenums"># A Code Comment\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            '\t# A Code Comment',
            expected,
            extensions=[CodeHiliteExtension(linenums=True)]
        )

    def testLinenumsFalse(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="c1"># A Code Comment</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-python"># A Code Comment\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            (
                '\t#!Python\n'
                '\t# A Code Comment'
            ),
            expected,
            extensions=[CodeHiliteExtension(linenums=False)]
        )

    def testLinenumsNone(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code># A Code Comment\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            '\t# A Code Comment',
            expected,
            extensions=[CodeHiliteExtension(linenums=None)]
        )

    def testLinenumsNoneWithShebang(self):
        if has_pygments:
            expected = (
                '<table class="codehilitetable"><tr>'
                '<td class="linenos"><div class="linenodiv"><pre>1</pre></div></td>'
                '<td class="code"><div class="codehilite"><pre><span></span>'
                '<code><span class="c1"># A Code Comment</span>\n'
                '</code></pre></div>\n'
                '</td></tr></table>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-python linenums"># A Code Comment\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            (
                '\t#!Python\n'
                '\t# A Code Comment'
            ),
            expected,
            extensions=[CodeHiliteExtension(linenums=None)]
        )

    def testLinenumsNoneWithColon(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="c1"># A Code Comment</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-python"># A Code Comment\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            (
                '\t:::Python\n'
                '\t# A Code Comment'
            ),
            expected,
            extensions=[CodeHiliteExtension(linenums=None)]
        )

    def testHighlightLinesWithColon(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="hll"><span class="c1">#line 1</span>\n'
                '</span><span class="c1">#line 2</span>\n'
                '<span class="c1">#line 3</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-python">#line 1\n'
                '#line 2\n'
                '#line 3\n'
                '</code></pre>'
            )
        # Double quotes
        self.assertMarkdownRenders(
            (
                '\t:::Python hl_lines="1"\n'
                '\t#line 1\n'
                '\t#line 2\n'
                '\t#line 3'
            ),
            expected,
            extensions=['codehilite']
        )
        # Single quotes
        self.assertMarkdownRenders(
            (
                "\t:::Python hl_lines='1'\n"
                '\t#line 1\n'
                '\t#line 2\n'
                '\t#line 3'
            ),
            expected,
            extensions=['codehilite']
        )

    def testUsePygmentsFalse(self):
        self.assertMarkdownRenders(
            (
                '\t:::Python\n'
                '\t# A Code Comment'
            ),
            (
                '<pre class="codehilite"><code class="language-python"># A Code Comment\n'
                '</code></pre>'
            ),
            extensions=[CodeHiliteExtension(use_pygments=False)]
        )

    def testLangPrefixEmpty(self):
        self.assertMarkdownRenders(
            (
                '\t:::Python\n'
                '\t# A Code Comment'
            ),
            (
                '<pre class="codehilite"><code class="python"># A Code Comment\n'
                '</code></pre>'
            ),
            extensions=[CodeHiliteExtension(use_pygments=False, lang_prefix='')]
        )

    def testLangPrefix(self):
        self.assertMarkdownRenders(
            (
                '\t:::Python\n'
                '\t# A Code Comment'
            ),
            (
                '<pre class="codehilite"><code class="lang-python"># A Code Comment\n'
                '</code></pre>'
            ),
            extensions=[CodeHiliteExtension(use_pygments=False, lang_prefix='lang-')]
        )

    def testDoubleEscape(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre>'
                '<span></span>'
                '<code><span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>'
                'This<span class="ni">&amp;amp;</span>That'
                '<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>'
                '\n</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-html">'
                '&lt;span&gt;This&amp;amp;That&lt;/span&gt;\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            (
                '\t:::html\n'
                '\t<span>This&amp;That</span>'
            ),
            expected,
            extensions=['codehilite']
        )

    def testEntitiesIntact(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre>'
                '<span></span>'
                '<code>&lt; &amp;lt; and &gt; &amp;gt;'
                '\n</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text">'
                '&lt; &amp;lt; and &gt; &amp;gt;\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            (
                '\t:::text\n'
                '\t< &lt; and > &gt;'
            ),
            expected,
            extensions=['codehilite']
        )

    def testHighlightAmps(self):
        if has_pygments:
            expected = (
                '<div class="codehilite"><pre><span></span><code>&amp;\n'
                '&amp;amp;\n'
                '&amp;amp;amp;\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code class="language-text">&amp;\n'
                '&amp;amp;\n'
                '&amp;amp;amp;\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            (
                '\t:::text\n'
                '\t&\n'
                '\t&amp;\n'
                '\t&amp;amp;'
            ),
            expected,
            extensions=['codehilite']
        )

    def testUnknownOption(self):
        if has_pygments:
            # Odd result as no lang given and a single comment is not enough for guessing.
            expected = (
                '<div class="codehilite"><pre><span></span><code><span class="err"># A Code Comment</span>\n'
                '</code></pre></div>'
            )
        else:
            expected = (
                '<pre class="codehilite"><code># A Code Comment\n'
                '</code></pre>'
            )
        self.assertMarkdownRenders(
            '\t# A Code Comment',
            expected,
            extensions=[CodeHiliteExtension(unknown='some value')],
        )
