"""Tests for template engine."""

import unittest

from beets.util import functemplate


def _normexpr(expr):
    """Normalize an Expression object's parts, collapsing multiple
    adjacent text blocks and removing empty text blocks. Generates a
    sequence of parts.
    """
    textbuf = []
    for part in expr.parts:
        if isinstance(part, str):
            textbuf.append(part)
        else:
            if textbuf:
                text = "".join(textbuf)
                if text:
                    yield text
                    textbuf = []
            yield part
    if textbuf:
        text = "".join(textbuf)
        if text:
            yield text


def _normparse(text):
    """Parse a template and then normalize the resulting Expression."""
    return _normexpr(functemplate._parse(text))


class ParseTest(unittest.TestCase):
    def test_empty_string(self):
        assert list(_normparse("")) == []

    def _assert_symbol(self, obj, ident):
        """Assert that an object is a Symbol with the given identifier."""
        assert isinstance(obj, functemplate.Symbol), f"not a Symbol: {obj}"
        assert obj.ident == ident, f"wrong identifier: {obj.ident} vs. {ident}"

    def _assert_call(self, obj, ident, numargs):
        """Assert that an object is a Call with the given identifier and
        argument count.
        """
        assert isinstance(obj, functemplate.Call), f"not a Call: {obj}"
        assert obj.ident == ident, f"wrong identifier: {obj.ident} vs. {ident}"
        assert len(obj.args) == numargs, (
            f"wrong argument count in {obj.ident}: {len(obj.args)} vs. {numargs}"
        )

    def test_plain_text(self):
        assert list(_normparse("hello world")) == ["hello world"]

    def test_escaped_character_only(self):
        assert list(_normparse("$$")) == ["$"]

    def test_escaped_character_in_text(self):
        assert list(_normparse("a $$ b")) == ["a $ b"]

    def test_escaped_character_at_start(self):
        assert list(_normparse("$$ hello")) == ["$ hello"]

    def test_escaped_character_at_end(self):
        assert list(_normparse("hello $$")) == ["hello $"]

    def test_escaped_function_delim(self):
        assert list(_normparse("a $% b")) == ["a % b"]

    def test_escaped_sep(self):
        assert list(_normparse("a $, b")) == ["a , b"]

    def test_escaped_close_brace(self):
        assert list(_normparse("a $} b")) == ["a } b"]

    def test_bare_value_delim_kept_intact(self):
        assert list(_normparse("a $ b")) == ["a $ b"]

    def test_bare_function_delim_kept_intact(self):
        assert list(_normparse("a % b")) == ["a % b"]

    def test_bare_opener_kept_intact(self):
        assert list(_normparse("a { b")) == ["a { b"]

    def test_bare_closer_kept_intact(self):
        assert list(_normparse("a } b")) == ["a } b"]

    def test_bare_sep_kept_intact(self):
        assert list(_normparse("a , b")) == ["a , b"]

    def test_symbol_alone(self):
        parts = list(_normparse("$foo"))
        assert len(parts) == 1
        self._assert_symbol(parts[0], "foo")

    def test_symbol_in_text(self):
        parts = list(_normparse("hello $foo world"))
        assert len(parts) == 3
        assert parts[0] == "hello "
        self._assert_symbol(parts[1], "foo")
        assert parts[2] == " world"

    def test_symbol_with_braces(self):
        parts = list(_normparse("hello${foo}world"))
        assert len(parts) == 3
        assert parts[0] == "hello"
        self._assert_symbol(parts[1], "foo")
        assert parts[2] == "world"

    def test_unclosed_braces_symbol(self):
        assert list(_normparse("a ${ b")) == ["a ${ b"]

    def test_empty_braces_symbol(self):
        assert list(_normparse("a ${} b")) == ["a ${} b"]

    def test_call_without_args_at_end(self):
        assert list(_normparse("foo %bar")) == ["foo %bar"]

    def test_call_without_args(self):
        assert list(_normparse("foo %bar baz")) == ["foo %bar baz"]

    def test_call_with_unclosed_args(self):
        assert list(_normparse("foo %bar{ baz")) == ["foo %bar{ baz"]

    def test_call_with_unclosed_multiple_args(self):
        assert list(_normparse("foo %bar{bar,bar baz")) == [
            "foo %bar{bar,bar baz"
        ]

    def test_call_empty_arg(self):
        parts = list(_normparse("%foo{}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 1)
        assert list(_normexpr(parts[0].args[0])) == []

    def test_call_single_arg(self):
        parts = list(_normparse("%foo{bar}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 1)
        assert list(_normexpr(parts[0].args[0])) == ["bar"]

    def test_call_two_args(self):
        parts = list(_normparse("%foo{bar,baz}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 2)
        assert list(_normexpr(parts[0].args[0])) == ["bar"]
        assert list(_normexpr(parts[0].args[1])) == ["baz"]

    def test_call_with_escaped_sep(self):
        parts = list(_normparse("%foo{bar$,baz}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 1)
        assert list(_normexpr(parts[0].args[0])) == ["bar,baz"]

    def test_call_with_escaped_close(self):
        parts = list(_normparse("%foo{bar$}baz}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 1)
        assert list(_normexpr(parts[0].args[0])) == ["bar}baz"]

    def test_call_with_symbol_argument(self):
        parts = list(_normparse("%foo{$bar,baz}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 2)
        arg_parts = list(_normexpr(parts[0].args[0]))
        assert len(arg_parts) == 1
        self._assert_symbol(arg_parts[0], "bar")
        assert list(_normexpr(parts[0].args[1])) == ["baz"]

    def test_call_with_nested_call_argument(self):
        parts = list(_normparse("%foo{%bar{},baz}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 2)
        arg_parts = list(_normexpr(parts[0].args[0]))
        assert len(arg_parts) == 1
        self._assert_call(arg_parts[0], "bar", 1)
        assert list(_normexpr(parts[0].args[1])) == ["baz"]

    def test_nested_call_with_argument(self):
        parts = list(_normparse("%foo{%bar{baz}}"))
        assert len(parts) == 1
        self._assert_call(parts[0], "foo", 1)
        arg_parts = list(_normexpr(parts[0].args[0]))
        assert len(arg_parts) == 1
        self._assert_call(arg_parts[0], "bar", 1)
        assert list(_normexpr(arg_parts[0].args[0])) == ["baz"]

    def test_sep_before_call_two_args(self):
        parts = list(_normparse("hello, %foo{bar,baz}"))
        assert len(parts) == 2
        assert parts[0] == "hello, "
        self._assert_call(parts[1], "foo", 2)
        assert list(_normexpr(parts[1].args[0])) == ["bar"]
        assert list(_normexpr(parts[1].args[1])) == ["baz"]

    def test_sep_with_symbols(self):
        parts = list(_normparse("hello,$foo,$bar"))
        assert len(parts) == 4
        assert parts[0] == "hello,"
        self._assert_symbol(parts[1], "foo")
        assert parts[2] == ","
        self._assert_symbol(parts[3], "bar")

    def test_newline_at_end(self):
        parts = list(_normparse("foo\n"))
        assert len(parts) == 1
        assert parts[0] == "foo\n"


class EvalTest(unittest.TestCase):
    def _eval(self, template):
        values = {"foo": "bar", "baz": "BaR"}
        functions = {"lower": str.lower, "len": len}
        return functemplate.Template(template).substitute(values, functions)

    def test_plain_text(self):
        assert self._eval("foo") == "foo"

    def test_subtitute_value(self):
        assert self._eval("$foo") == "bar"

    def test_subtitute_value_in_text(self):
        assert self._eval("hello $foo world") == "hello bar world"

    def test_not_subtitute_undefined_value(self):
        assert self._eval("$bar") == "$bar"

    def test_function_call(self):
        assert self._eval("%lower{FOO}") == "foo"

    def test_function_call_with_text(self):
        assert self._eval("A %lower{FOO} B") == "A foo B"

    def test_nested_function_call(self):
        assert self._eval("%lower{%lower{FOO}}") == "foo"

    def test_symbol_in_argument(self):
        assert self._eval("%lower{$baz}") == "bar"

    def test_function_call_exception(self):
        res = self._eval("%lower{a,b,c,d,e}")
        assert isinstance(res, str)

    def test_function_returning_integer(self):
        assert self._eval("%len{foo}") == "3"

    def test_not_subtitute_undefined_func(self):
        assert self._eval("%bar{}") == "%bar{}"

    def test_not_subtitute_func_with_no_args(self):
        assert self._eval("%lower") == "%lower"

    def test_function_call_with_empty_arg(self):
        assert self._eval("%len{}") == "0"
