import unittest

from robot.errors import DataError, VariableError
from robot.utils.asserts import assert_equal, assert_raises
from robot.variables import Variables

SCALARS = ["${var}", "${  v A  R }"]
LISTS = ["@{var}", "@{  v A  R }"]
NOKS = [
    "var", "$var", "${var", "${va}r", "@{va}r", "@var", "%{var}", " ${var}",
    "@{var} ", "\\${var}", "\\\\${var}", 42, None, ["${var}"], DataError,
]  # fmt: skip


class PythonObject:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __getitem__(self, index):
        return (self.a, self.b)[index]

    def __str__(self):
        return f"({self.a}, {self.b})"

    def __len__(self):
        return 2

    __repr__ = __str__


class TestVariables(unittest.TestCase):

    def setUp(self):
        self.varz = Variables()

    def test_set(self):
        value = ["value"]
        for var in SCALARS + LISTS:
            self.varz[var] = value
            assert_equal(self.varz[var], value)
            assert_equal(self.varz[var.lower().replace(" ", "")], value)
            self.varz.clear()

    def test_set_invalid(self):
        for var in NOKS:
            assert_raises(DataError, self.varz.__setitem__, var, "value")

    def test_set_scalar(self):
        for var in SCALARS:
            for value in [
                "string",
                "",
                10,
                ["hi", "u"],
                ["hi", 2],
                {"a": 1, "b": 2},
                self,
                None,
                unittest.TestCase,
            ]:
                self.varz[var] = value
                assert_equal(self.varz[var], value)

    def test_set_list(self):
        for var in LISTS:
            for value in [
                [],
                [""],
                ["str"],
                [10],
                ["hi", "u"],
                ["hi", 2],
                [{"a": 1, "b": 2}, self, None],
            ]:
                self.varz[var] = value
                assert_equal(self.varz[var], value)
                self.varz.clear()

    def test_replace_scalar(self):
        self.varz["${foo}"] = "bar"
        self.varz["${a}"] = "ari"
        for inp, exp in [
            ("${foo}", "bar"),
            ("${a}", "ari"),
            (r"$\{a}", "${a}"),
            ("", ""),
            ("hii", "hii"),
            ("Let's go to ${foo}!", "Let's go to bar!"),
            ("${foo}ba${a}-${a}", "barbaari-ari"),
        ]:
            assert_equal(self.varz.replace_scalar(inp), exp)

    def test_replace_list(self):
        self.varz["@{L}"] = ["v1", "v2"]
        self.varz["@{E}"] = []
        self.varz["@{S}"] = ["1", "2", "3"]
        for inp, exp in [
            (["@{L}"], ["v1", "v2"]),
            (["@{L}", "v3"], ["v1", "v2", "v3"]),
            (["v0", "@{L}", "@{E}", "v${S}[2]"], ["v0", "v1", "v2", "v3"]),
            ([], []),
            (["hi u", "hi 2", 3], ["hi u", "hi 2", 3]),
        ]:
            assert_equal(self.varz.replace_list(inp), exp)

    def test_replace_list_in_scalar_context(self):
        self.varz["@{list}"] = ["v1", "v2"]
        assert_equal(self.varz.replace_list(["@{list}"]), ["v1", "v2"])
        assert_equal(self.varz.replace_list(["-@{list}-"]), ["-['v1', 'v2']-"])

    def test_replace_list_item(self):
        self.varz["@{L}"] = ["v0", "v1"]
        assert_equal(self.varz.replace_list(["${L}[0]"]), ["v0"])
        assert_equal(self.varz.replace_scalar("${L}[1]"), "v1")
        assert_equal(self.varz.replace_scalar("-${L}[0]${L}[1]${L}[0]-"), "-v0v1v0-")
        self.varz["${L2}"] = ["v0", ["v11", "v12"]]
        assert_equal(self.varz.replace_list(["${L2}[0]"]), ["v0"])
        assert_equal(self.varz.replace_list(["${L2}[1]"]), [["v11", "v12"]])
        assert_equal(self.varz.replace_scalar("${L2}[0]"), "v0")
        assert_equal(self.varz.replace_scalar("${L2}[1]"), ["v11", "v12"])
        assert_equal(
            self.varz.replace_list(["${L}[0]", "@{L2}[1]"]),
            ["v0", "v11", "v12"],
        )

    def test_replace_dict_item(self):
        self.varz["&{D}"] = {"a": 1, 2: "b", "nested": {"a": 1}}
        assert_equal(self.varz.replace_scalar("${D}[a]"), 1)
        assert_equal(self.varz.replace_scalar("${D}[${2}]"), "b")
        assert_equal(self.varz.replace_scalar("${D}[nested][a]"), 1)
        assert_equal(self.varz.replace_scalar("${D}[nested]"), {"a": 1})
        assert_equal(self.varz.replace_scalar("&{D}[nested]"), {"a": 1})

    def test_replace_non_strings(self):
        self.varz["${d}"] = {"a": 1, "b": 2}
        self.varz["${n}"] = None
        assert_equal(self.varz.replace_scalar("${d}"), {"a": 1, "b": 2})
        assert_equal(self.varz.replace_scalar("${n}"), None)

    def test_replace_non_strings_inside_string(self):
        class Example:
            def __str__(self):
                return "Hello"

        self.varz["${h}"] = Example()
        self.varz["${w}"] = "world"
        res = self.varz.replace_scalar('Another "${h} ${w}" example')
        assert_equal(res, 'Another "Hello world" example')

    def test_replace_list_item_invalid(self):
        self.varz["@{L}"] = ["v0", "v1", "v3"]
        for inv in ["@{L}[3]", "@{NON}[0]", "@{L[2]}"]:
            assert_raises(VariableError, self.varz.replace_list, [inv])

    def test_replace_non_existing_list(self):
        assert_raises(VariableError, self.varz.replace_list, ["${nonexisting}"])

    def test_replace_non_existing_scalar(self):
        assert_raises(VariableError, self.varz.replace_scalar, "${nonexisting}")

    def test_replace_non_existing_string(self):
        assert_raises(VariableError, self.varz.replace_string, "${nonexisting}")

    def test_non_string_input(self):
        for item in [1, False, None, [], (), {}, object]:
            assert_equal(self.varz.replace_list([item]), [item])
            assert_equal(self.varz.replace_scalar(item), item)
            assert_equal(self.varz.replace_string(item), str(item))

    def test_replace_escaped(self):
        self.varz["${foo}"] = "bar"
        for inp, exp in [
            (r"\${foo}", r"${foo}"),
            (r"\\${foo}", r"\bar"),
            (r"\\\${foo}", r"\${foo}"),
            (r"\\\\${foo}", r"\\bar"),
            (r"\\\\\${foo}", r"\\${foo}"),
        ]:
            assert_equal(self.varz.replace_scalar(inp), exp)

    def test_variables_in_value(self):
        self.varz["${exists}"] = "Variable exists but is still not replaced"
        self.varz["${test}"] = "${exists} & ${does_not_exist}"
        assert_equal(self.varz["${test}"], "${exists} & ${does_not_exist}")
        self.varz["@{test}"] = ["${exists}", "&", "${does_not_exist}"]
        assert_equal(self.varz["@{test}"], "${exists} & ${does_not_exist}".split())

    def test_variable_as_object(self):
        obj = PythonObject("a", 1)
        self.varz["${obj}"] = obj
        assert_equal(self.varz["${obj}"], obj)
        expected = [f"Some text here {obj} and {obj} there"]
        actual = self.varz.replace_list(["Some text here ${obj} and ${obj} there"])
        assert_equal(actual, expected)

    def test_extended_variables(self):
        # Extended variables are vars like ${obj.name} when we have var ${obj}
        obj = PythonObject("a", [1, 2, 3])
        dic = {"a": 1, "o": obj}
        self.varz["${obj}"] = obj
        self.varz["${dic}"] = dic
        assert_equal(self.varz.replace_scalar("${obj.a}"), "a")
        assert_equal(self.varz.replace_scalar("${obj.b}"), [1, 2, 3])
        assert_equal(self.varz.replace_scalar("${obj.b[0]}-${obj.b[1]}"), "1-2")
        assert_equal(self.varz.replace_scalar('${dic["a"]}'), 1)
        assert_equal(self.varz.replace_scalar('${dic["o"]}'), obj)
        assert_equal(self.varz.replace_scalar('-${dic["o"].b[2]}-'), "-3-")

    def test_space_is_not_ignored_after_newline_in_extend_variable_syntax(self):
        self.varz["${x}"] = "test string"
        self.varz["${lf}"] = "\\n"
        self.varz["${lfs}"] = "\\n "
        for inp, exp in [
            ('${x.replace(" ", """\\n""")}', "test\nstring"),
            ('${x.replace(" ", """\\n """)}', "test\n string"),
            ('${x.replace(" ", """${lf}""")}', "test\nstring"),
            ('${x.replace(" ", """${lfs}""")}', "test\n string"),
        ]:
            assert_equal(self.varz.replace_scalar(inp), exp)

    def test_escaping_with_extended_variable_syntax(self):
        self.varz["${p}"] = "c:\\temp"
        assert self.varz["${p}"] == "c:\\temp"
        assert_equal(
            self.varz.replace_scalar('${p + "\\\\foo.txt"}'),
            "c:\\temp\\foo.txt",
        )

    def test_internal_variables(self):
        # Internal variables are variables like ${my${name}}
        self.varz["${name}"] = "name"
        self.varz["${my name}"] = "value"
        assert_equal(self.varz.replace_scalar("${my${name}}"), "value")
        self.varz["${whos name}"] = "my"
        assert_equal(self.varz.replace_scalar("${${whos name} ${name}}"), "value")
        assert_equal(self.varz.replace_scalar("${${whos${name}}${name}}"), "value")
        self.varz["${my name}"] = [1, 2, 3]
        assert_equal(self.varz.replace_scalar("${${whos${name}}${name}}"), [1, 2, 3])
        assert_equal(
            self.varz.replace_scalar("- ${${whos${name}}${name}} -"),
            "- [1, 2, 3] -",
        )

    def test_math_with_internal_vars(self):
        assert_equal(self.varz.replace_scalar("${${1}+${2}}"), 3)
        assert_equal(self.varz.replace_scalar("${${1}-${2}}"), -1)
        assert_equal(self.varz.replace_scalar("${${1}*${2}}"), 2)
        assert_equal(self.varz.replace_scalar("${${1}//${2}}"), 0)

    def test_math_with_internal_vars_with_spaces(self):
        assert_equal(self.varz.replace_scalar("${${1} + ${2.5}}"), 3.5)
        assert_equal(self.varz.replace_scalar("${${1} - ${2} + 1}"), 0)
        assert_equal(self.varz.replace_scalar("${${1} * ${2} - 1}"), 1)
        assert_equal(self.varz.replace_scalar("${${1} / ${2.0}}"), 0.5)

    def test_math_with_internal_vars_does_not_work_if_first_var_is_float(self):
        assert_raises(VariableError, self.varz.replace_scalar, "${${1.1}+${2}}")
        assert_raises(VariableError, self.varz.replace_scalar, "${${1.1} - ${2}}")
        assert_raises(VariableError, self.varz.replace_scalar, "${${1.1} * ${2}}")
        assert_raises(VariableError, self.varz.replace_scalar, "${${1.1}/${2}}")

    def test_list_variable_as_scalar(self):
        self.varz["@{name}"] = exp = ["spam", "eggs"]
        assert_equal(self.varz.replace_scalar("${name}"), exp)
        assert_equal(self.varz.replace_list(["${name}", 42]), [exp, 42])
        assert_equal(self.varz.replace_string("${name}"), str(exp))

    def test_copy(self):
        varz = Variables()
        varz["${foo}"] = "bar"
        copy = varz.copy()
        assert_equal(copy["${foo}"], "bar")

    def test_ignore_error(self):
        v = Variables()
        v["${X}"] = "x"
        v["@{Y}"] = [1, 2, 3]
        for item in [
            "${foo}",
            "foo${bar}",
            "${foo}",
            "@{zap}",
            "${Y}[7]",
            "${inv",
            "${{inv}",
            "${var}[inv",
            "${var}[key][inv",
        ]:
            if (
                item.count("{") == item.count("}")
                and item.count("[") == item.count("]")
            ):  # fmt: skip
                x_at_end = "x"
            else:
                x_at_end = "${x}"
            assert_equal(v.replace_string(item, ignore_errors=True), item)
            assert_equal(
                v.replace_string("${x}" + item + "${x}", ignore_errors=True),
                "x" + item + x_at_end,
            )
            assert_equal(v.replace_scalar(item, ignore_errors=True), item)
            assert_equal(
                v.replace_scalar("${x}" + item + "${x}", ignore_errors=True),
                "x" + item + x_at_end,
            )
            assert_equal(v.replace_list([item], ignore_errors=True), [item])
            assert_equal(
                v.replace_list(["${X}", item, "@{Y}"], ignore_errors=True),
                ["x", item, 1, 2, 3],
            )
            assert_equal(
                v.replace_list(["${x}" + item + "${x}", "@{NON}"], ignore_errors=True),
                ["x" + item + x_at_end, "@{NON}"],
            )

    def test_sequence_subscript(self):
        sequences = (
            [42, "my", "name"],
            (42, ["foo", "bar"], "name"),
            "abcDEF123#@$",
            b"abcDEF123#@$",
            bytearray(b"abcDEF123#@$"),
        )
        for var in sequences:
            self.varz["${var}"] = var
            assert_equal(self.varz.replace_scalar("${var}[0]"), var[0])
            assert_equal(self.varz.replace_scalar("${var}[-2]"), var[-2])
            assert_equal(self.varz.replace_scalar("${var}[::2]"), var[::2])
            assert_equal(self.varz.replace_scalar("${var}[1::2]"), var[1::2])
            assert_equal(self.varz.replace_scalar("${var}[1:-3:2]"), var[1:-3:2])
            assert_raises(VariableError, self.varz.replace_scalar, "${var}[0][1]")

    def test_dict_subscript(self):
        a_key = (42, b"key")
        var = {"foo": "bar", 42: [4, 2], "name": b"my-name", a_key: {4: 2}}
        self.varz["${a_key}"] = a_key
        self.varz["${var}"] = var
        assert_equal(self.varz.replace_scalar("${var}[foo][-1]"), var["foo"][-1])
        assert_equal(self.varz.replace_scalar("${var}[${42}][-1]"), var[42][-1])
        assert_equal(self.varz.replace_scalar("${var}[name][:3]"), var["name"][:3])
        assert_equal(self.varz.replace_scalar("${var}[${a_key}][${4}]"), var[a_key][4])
        assert_raises(VariableError, self.varz.replace_scalar, "${var}[1]")
        assert_raises(VariableError, self.varz.replace_scalar, "${var}[42:]")
        assert_raises(VariableError, self.varz.replace_scalar, "${var}[nonex]")

    def test_custom_class_subscriptable_like_sequence(self):
        # the two class attributes are accessible via indices 0 and 1
        # slicing should be supported here as well
        bytes_key = b"my"
        var = PythonObject([1, 2, 3, 4, 5], {bytes_key: "myname"})
        self.varz["${bytes_key}"] = bytes_key
        self.varz["${var}"] = var
        assert_equal(self.varz.replace_scalar("${var}[${0}][2::2]"), [3, 5])
        assert_equal(self.varz.replace_scalar("${var}[0][2::2]"), [3, 5])
        assert_equal(self.varz.replace_scalar("${var}[1][${bytes_key}][2:]"), "name")
        assert_equal(self.varz.replace_scalar("${var}\\[1]"), str(var) + "[1]")
        assert_equal(self.varz.replace_scalar("${var}[:][0][4]"), var[:][0][4])
        assert_equal(self.varz.replace_scalar("${var}[:-2]"), var[:-2])
        assert_equal(self.varz.replace_scalar("${var}[:7:-2]"), var[:7:-2])
        assert_equal(self.varz.replace_scalar("${var}[2::]"), ())
        assert_raises(VariableError, self.varz.replace_scalar, "${var}[${2}]")
        assert_raises(VariableError, self.varz.replace_scalar, "${var}[${bytes_key}]")

    def test_non_subscriptable(self):
        assert_raises(VariableError, self.varz.replace_scalar, "${1}[1]")


if __name__ == "__main__":
    unittest.main()
