from django.template import Context, Engine
from django.test import SimpleTestCase

from ..utils import setup


class IfChangedTagTests(SimpleTestCase):
    libraries = {"custom": "template_tests.templatetags.custom"}

    @setup(
        {
            "ifchanged01": (
                "{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}"
            )
        }
    )
    def test_ifchanged01(self):
        output = self.engine.render_to_string("ifchanged01", {"num": (1, 2, 3)})
        self.assertEqual(output, "123")

    @setup(
        {
            "ifchanged02": (
                "{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}"
            )
        }
    )
    def test_ifchanged02(self):
        output = self.engine.render_to_string("ifchanged02", {"num": (1, 1, 3)})
        self.assertEqual(output, "13")

    @setup(
        {
            "ifchanged03": (
                "{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}"
            )
        }
    )
    def test_ifchanged03(self):
        output = self.engine.render_to_string("ifchanged03", {"num": (1, 1, 1)})
        self.assertEqual(output, "1")

    @setup(
        {
            "ifchanged04": "{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}"
            "{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}"
            "{% endfor %}{% endfor %}"
        }
    )
    def test_ifchanged04(self):
        output = self.engine.render_to_string(
            "ifchanged04", {"num": (1, 2, 3), "numx": (2, 2, 2)}
        )
        self.assertEqual(output, "122232")

    @setup(
        {
            "ifchanged05": "{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}"
            "{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}"
            "{% endfor %}{% endfor %}"
        }
    )
    def test_ifchanged05(self):
        output = self.engine.render_to_string(
            "ifchanged05", {"num": (1, 1, 1), "numx": (1, 2, 3)}
        )
        self.assertEqual(output, "1123123123")

    @setup(
        {
            "ifchanged06": "{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}"
            "{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}"
            "{% endfor %}{% endfor %}"
        }
    )
    def test_ifchanged06(self):
        output = self.engine.render_to_string(
            "ifchanged06", {"num": (1, 1, 1), "numx": (2, 2, 2)}
        )
        self.assertEqual(output, "1222")

    @setup(
        {
            "ifchanged07": "{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}"
            "{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}"
            "{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}"
            "{% endfor %}{% endfor %}{% endfor %}"
        }
    )
    def test_ifchanged07(self):
        output = self.engine.render_to_string(
            "ifchanged07", {"num": (1, 1, 1), "numx": (2, 2, 2), "numy": (3, 3, 3)}
        )
        self.assertEqual(output, "1233323332333")

    @setup(
        {
            "ifchanged08": "{% for data in datalist %}{% for c,d in data %}"
            "{% if c %}{% ifchanged %}{{ d }}{% endifchanged %}"
            "{% endif %}{% endfor %}{% endfor %}"
        }
    )
    def test_ifchanged08(self):
        output = self.engine.render_to_string(
            "ifchanged08",
            {
                "datalist": [
                    [(1, "a"), (1, "a"), (0, "b"), (1, "c")],
                    [(0, "a"), (1, "c"), (1, "d"), (1, "d"), (0, "e")],
                ]
            },
        )
        self.assertEqual(output, "accd")

    @setup(
        {
            "ifchanged-param01": (
                "{% for n in num %}{% ifchanged n %}..{% endifchanged %}"
                "{{ n }}{% endfor %}"
            )
        }
    )
    def test_ifchanged_param01(self):
        """
        Test one parameter given to ifchanged.
        """
        output = self.engine.render_to_string("ifchanged-param01", {"num": (1, 2, 3)})
        self.assertEqual(output, "..1..2..3")

    @setup(
        {
            "ifchanged-param02": (
                "{% for n in num %}{% for x in numx %}"
                "{% ifchanged n %}..{% endifchanged %}{{ x }}"
                "{% endfor %}{% endfor %}"
            )
        }
    )
    def test_ifchanged_param02(self):
        output = self.engine.render_to_string(
            "ifchanged-param02", {"num": (1, 2, 3), "numx": (5, 6, 7)}
        )
        self.assertEqual(output, "..567..567..567")

    @setup(
        {
            "ifchanged-param03": "{% for n in num %}{{ n }}{% for x in numx %}"
            "{% ifchanged x n %}{{ x }}{% endifchanged %}"
            "{% endfor %}{% endfor %}"
        }
    )
    def test_ifchanged_param03(self):
        """
        Test multiple parameters to ifchanged.
        """
        output = self.engine.render_to_string(
            "ifchanged-param03", {"num": (1, 1, 2), "numx": (5, 6, 6)}
        )
        self.assertEqual(output, "156156256")

    @setup(
        {
            "ifchanged-param04": (
                "{% for d in days %}{% ifchanged %}{{ d.day }}{% endifchanged %}"
                "{% for h in d.hours %}{% ifchanged d h %}{{ h }}{% endifchanged %}"
                "{% endfor %}{% endfor %}"
            )
        }
    )
    def test_ifchanged_param04(self):
        """
        Test a date+hour like construct, where the hour of the last day is
        the same but the date had changed, so print the hour anyway.
        """
        output = self.engine.render_to_string(
            "ifchanged-param04",
            {"days": [{"hours": [1, 2, 3], "day": 1}, {"hours": [3], "day": 2}]},
        )
        self.assertEqual(output, "112323")

    @setup(
        {
            "ifchanged-param05": (
                "{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}"
                "{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}"
                "{% endfor %}{% endfor %}"
            )
        }
    )
    def test_ifchanged_param05(self):
        """
        Logically the same as above, just written with explicit ifchanged
        for the day.
        """
        output = self.engine.render_to_string(
            "ifchanged-param05",
            {"days": [{"hours": [1, 2, 3], "day": 1}, {"hours": [3], "day": 2}]},
        )
        self.assertEqual(output, "112323")

    @setup(
        {
            "ifchanged-else01": "{% for id in ids %}{{ id }}"
            "{% ifchanged id %}-first{% else %}-other{% endifchanged %}"
            ",{% endfor %}"
        }
    )
    def test_ifchanged_else01(self):
        """
        Test the else clause of ifchanged.
        """
        output = self.engine.render_to_string(
            "ifchanged-else01", {"ids": [1, 1, 2, 2, 2, 3]}
        )
        self.assertEqual(output, "1-first,1-other,2-first,2-other,2-other,3-first,")

    @setup(
        {
            "ifchanged-else02": "{% for id in ids %}{{ id }}-"
            '{% ifchanged id %}{% cycle "red" "blue" %}{% else %}gray{% endifchanged %}'
            ",{% endfor %}"
        }
    )
    def test_ifchanged_else02(self):
        output = self.engine.render_to_string(
            "ifchanged-else02", {"ids": [1, 1, 2, 2, 2, 3]}
        )
        self.assertEqual(output, "1-red,1-gray,2-blue,2-gray,2-gray,3-red,")

    @setup(
        {
            "ifchanged-else03": "{% for id in ids %}{{ id }}"
            '{% ifchanged id %}-{% cycle "red" "blue" %}{% else %}{% endifchanged %}'
            ",{% endfor %}"
        }
    )
    def test_ifchanged_else03(self):
        output = self.engine.render_to_string(
            "ifchanged-else03", {"ids": [1, 1, 2, 2, 2, 3]}
        )
        self.assertEqual(output, "1-red,1,2-blue,2,2,3-red,")

    @setup(
        {
            "ifchanged-else04": "{% for id in ids %}"
            "{% ifchanged %}***{{ id }}*{% else %}...{% endifchanged %}"
            "{{ forloop.counter }}{% endfor %}"
        }
    )
    def test_ifchanged_else04(self):
        output = self.engine.render_to_string(
            "ifchanged-else04", {"ids": [1, 1, 2, 2, 2, 3, 4]}
        )
        self.assertEqual(output, "***1*1...2***2*3...4...5***3*6***4*7")

    @setup(
        {
            "ifchanged-filter-ws": "{% load custom %}{% for n in num %}"
            '{% ifchanged n|noop:"x y" %}..{% endifchanged %}{{ n }}'
            "{% endfor %}"
        }
    )
    def test_ifchanged_filter_ws(self):
        """
        Test whitespace in filter arguments
        """
        output = self.engine.render_to_string("ifchanged-filter-ws", {"num": (1, 2, 3)})
        self.assertEqual(output, "..1..2..3")


class IfChangedTests(SimpleTestCase):
    @classmethod
    def setUpClass(cls):
        cls.engine = Engine()
        super().setUpClass()

    def test_ifchanged_concurrency(self):
        """
        #15849 -- ifchanged should be thread-safe.
        """
        template = self.engine.from_string(
            "[0{% for x in foo %},{% with var=get_value %}{% ifchanged %}"
            "{{ var }}{% endifchanged %}{% endwith %}{% endfor %}]"
        )

        # Using generator to mimic concurrency.
        # The generator is not passed to the 'for' loop, because it does a list(values)
        # instead, call gen.next() in the template to control the generator.
        def gen():
            yield 1
            yield 2
            # Simulate that another thread is now rendering.
            # When the IfChangeNode stores state at 'self' it stays at '3' and
            # skip the last yielded value below.
            iter2 = iter([1, 2, 3])
            output2 = template.render(
                Context({"foo": range(3), "get_value": lambda: next(iter2)})
            )
            self.assertEqual(
                output2,
                "[0,1,2,3]",
                "Expected [0,1,2,3] in second parallel template, got {}".format(
                    output2
                ),
            )
            yield 3

        gen1 = gen()
        output1 = template.render(
            Context({"foo": range(3), "get_value": lambda: next(gen1)})
        )
        self.assertEqual(
            output1,
            "[0,1,2,3]",
            "Expected [0,1,2,3] in first template, got {}".format(output1),
        )

    def test_ifchanged_render_once(self):
        """
        #19890. The content of ifchanged template tag was rendered twice.
        """
        template = self.engine.from_string(
            '{% ifchanged %}{% cycle "1st time" "2nd time" %}{% endifchanged %}'
        )
        output = template.render(Context({}))
        self.assertEqual(output, "1st time")

    def test_include(self):
        """
        #23516 -- This works as a regression test only if the cached loader
        isn't used. Hence we don't use the @setup decorator.
        """
        engine = Engine(
            loaders=[
                (
                    "django.template.loaders.locmem.Loader",
                    {
                        "template": (
                            '{% for x in vars %}{% include "include" %}{% endfor %}'
                        ),
                        "include": "{% ifchanged %}{{ x }}{% endifchanged %}",
                    },
                ),
            ]
        )
        output = engine.render_to_string("template", {"vars": [1, 1, 2, 2, 3, 3]})
        self.assertEqual(output, "123")

    def test_include_state(self):
        """Tests the node state for different IncludeNodes (#27974)."""
        engine = Engine(
            loaders=[
                (
                    "django.template.loaders.locmem.Loader",
                    {
                        "template": (
                            '{% for x in vars %}{% include "include" %}'
                            '{% include "include" %}{% endfor %}'
                        ),
                        "include": "{% ifchanged %}{{ x }}{% endifchanged %}",
                    },
                ),
            ]
        )
        output = engine.render_to_string("template", {"vars": [1, 1, 2, 2, 3, 3]})
        self.assertEqual(output, "112233")
