__author__ = 'Lukas Leufen'
__date__ = '2019-11-22'


import pytest

from src.datastore import AbstractDataStore, DataStoreByVariable, DataStoreByScope
from src.datastore import NameNotFoundInDataStore, NameNotFoundInScope, EmptyScope


class TestAbstractDataStore:

    @pytest.fixture
    def ds(self):
        return AbstractDataStore()

    def test_init(self, ds):
        assert ds._store == {}

    def test_clear_data_store(self, ds):
        ds._store["test"] = "test2"
        assert len(ds._store.keys()) == 1
        ds.clear_data_store()
        assert len(ds._store.keys()) == 0


class TestDataStoreByVariable:

    @pytest.fixture
    def ds(self):
        return DataStoreByVariable()

    def test_put(self, ds):
        ds.set("number", 3, "general.subscope")
        assert ds._store["number"]["general.subscope"] == 3

    def test_get(self, ds):
        ds.set("number", 3, "general.subscope")
        assert ds.get("number", "general.subscope") == 3

    def test_get_with_sub_scope(self, ds):
        ds.set("number", 3, "general")
        ds.set("number", 10, "general.subscope")
        assert ds.get("number", "general.subscope") == 10
        assert ds.get("number", "general") == 3

    def test_get_with_not_existing_sub_scope(self, ds):
        ds.set("number", 3, "general")
        ds.set("number2", 10, "general.subscope")
        ds.set("number2", 1, "general")
        assert ds.get("number", "general.subscope") == 3

    def test_raise_not_in_data_store(self, ds):
        ds.set("number", 22, "general")
        with pytest.raises(NameNotFoundInDataStore) as e:
            ds.get("number3", "general")
        assert "Couldn't find number3 in data store" in e.value.args[0]

    def test_get_default(self, ds):
        ds.set("number", 3, "general")
        assert ds.get_default("number", "general", 45) == 3
        assert ds.get_default("number", "general.sub", 45) == 3
        assert ds.get_default("number", "other", 45) == 45

    def test_search(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 22, "general2")
        ds.set("number", 22, "general.sub")
        assert ds.search_name("number") == ["general", "general.sub", "general2"]

    def test_raise_not_in_scope(self, ds):
        ds.set("number", 11, "general.sub")
        with pytest.raises(NameNotFoundInScope) as e:
            ds.get("number", "general.sub2")
        assert "Couldn't find number in scope general.sub2 . number is only defined in ['general.sub']" in e.value.args[0]

    def test_list_all_scopes(self, ds):
        ds.set("number", 22, "general2")
        ds.set("number", 11, "general.sub")
        ds.set("number2", 2, "general.sub")
        ds.set("number", 3, "general.sub3")
        ds.set("number", 1, "general")
        assert ds.list_all_scopes() == ['general', 'general.sub', 'general.sub3', 'general2']

    def test_search_scope(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.search_scope("general.sub") == ["number", "number1"]

    def test_search_empty_scope(self, ds):
        ds.set("number", 22, "general2")
        ds.set("number", 11, "general.sub")
        with pytest.raises(EmptyScope) as e:
            ds.search_scope("general.sub2")
        assert "Given scope general.sub2 is not part of the data store." in e.value.args[0]
        assert "Available scopes are: ['general.sub', 'general2']" in e.value.args[0]

    def test_list_all_names(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.list_all_names() == ["number", "number1", "number2"]

    def test_search_scope_and_all_superiors(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.search_scope("general.sub", current_scope_only=False) == ["number", "number1"]
        assert ds.search_scope("general.sub.sub", current_scope_only=False) == ["number", "number1", "number2"]

    def test_search_scope_return_all(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.search_scope("general.sub", return_all=True) == [("number", "general.sub", 11),
                                                                   ("number1", "general.sub", 22)]

    def test_search_scope_and_all_superiors_return_all(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        ds.set("number", "ABC", "general.sub.sub")
        assert ds.search_scope("general.sub", current_scope_only=False, return_all=True) == \
            [("number", "general.sub", 11), ("number1", "general.sub", 22)]
        assert ds.search_scope("general.sub.sub", current_scope_only=False, return_all=True) == \
            [("number", "general.sub.sub", "ABC"), ("number1", "general.sub", 22), ("number2", "general.sub.sub", 3)]

    def test_create_args_dict(self, ds):
        ds.set("tester1", 1, "general")
        ds.set("tester2", 11, "general")
        ds.set("tester2", 10, "general.sub")
        ds.set("tester3", 21, "general")
        args = ["tester1", "tester2", "tester3", "tester4"]
        assert ds.create_args_dict(args) == {"tester1": 1, "tester2": 11, "tester3": 21}
        assert ds.create_args_dict(args, "general.sub") == {"tester1": 1, "tester2": 10, "tester3": 21}
        assert ds.create_args_dict(["notAvail", "alsonot"]) == {}

    def test_set_args_from_dict(self, ds):
        ds.set_args_from_dict({"tester1": 1, "tester2": 10, "tester3": 21})
        assert ds.get("tester1", "general") == 1
        assert ds.get("tester2", "general") == 10
        assert ds.get("tester3", "general") == 21
        ds.set_args_from_dict({"tester1": 111}, "general.sub")
        assert ds.get("tester1", "general.sub") == 111
        assert ds.get("tester3", "general.sub") == 21


class TestDataStoreByScope:

    @pytest.fixture
    def ds(self):
        return DataStoreByScope()

    def test_put_with_scope(self, ds):
        ds.set("number", 3, "general.subscope")
        assert ds._store["general.subscope"]["number"] == 3

    def test_get(self, ds):
        ds.set("number", 3, "general.subscope")
        assert ds.get("number", "general.subscope") == 3

    def test_get_with_sub_scope(self, ds):
        ds.set("number", 3, "general")
        ds.set("number", 10, "general.subscope")
        assert ds.get("number", "general.subscope") == 10
        assert ds.get("number", "general") == 3

    def test_get_with_not_existing_sub_scope(self, ds):
        ds.set("number", 3, "general")
        ds.set("number2", 10, "general.subscope")
        ds.set("number2", 1, "general")
        assert ds.get("number", "general.subscope") == 3

    def test_raise_not_in_data_store(self, ds):
        ds.set("number", 22, "general")
        with pytest.raises(NameNotFoundInDataStore) as e:
            ds.get("number3", "general")
        assert "Couldn't find number3 in data store" in e.value.args[0]

    def test_get_default(self, ds):
        ds.set("number", 3, "general")
        assert ds.get_default("number", "general", 45) == 3
        assert ds.get_default("number", "general.sub", 45) == 3
        assert ds.get_default("number", "other", 45) == 45

    def test_search(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 22, "general2")
        ds.set("number", 22, "general.sub")
        assert ds.search_name("number") == ["general", "general.sub", "general2"]

    def test_raise_not_in_scope(self, ds):
        ds.set("number", 11, "general.sub")
        with pytest.raises(NameNotFoundInScope) as e:
            ds.get("number", "general.sub2")
        assert "Couldn't find number in scope general.sub2 . number is only defined in ['general.sub']" in e.value.args[0]

    def test_list_all_scopes(self, ds):
        ds.set("number", 22, "general2")
        ds.set("number", 11, "general.sub")
        ds.set("number2", 2, "general.sub")
        ds.set("number", 3, "general.sub3")
        ds.set("number", 1, "general")
        assert ds.list_all_scopes() == ['general', 'general.sub', 'general.sub3', 'general2']

    def test_search_scope(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.search_scope("general.sub") == ["number", "number1"]

    def test_search_empty_scope(self, ds):
        ds.set("number", 22, "general2")
        ds.set("number", 11, "general.sub")
        with pytest.raises(EmptyScope) as e:
            ds.search_scope("general.sub2")
        assert "Given scope general.sub2 is not part of the data store." in e.value.args[0]
        assert "Available scopes are: ['general.sub', 'general2']" in e.value.args[0]

    def test_list_all_names(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.list_all_names() == ["number", "number1", "number2"]

    def test_search_scope_and_all_superiors(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.search_scope("general.sub", current_scope_only=False) == ["number", "number1"]
        assert ds.search_scope("general.sub.sub", current_scope_only=False) == ["number", "number1", "number2"]

    def test_search_scope_return_all(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        assert ds.search_scope("general.sub", return_all=True) == [("number", "general.sub", 11),
                                                                   ("number1", "general.sub", 22)]

    def test_search_scope_and_all_superiors_return_all(self, ds):
        ds.set("number", 22, "general")
        ds.set("number", 11, "general.sub")
        ds.set("number1", 22, "general.sub")
        ds.set("number2", 3, "general.sub.sub")
        ds.set("number", "ABC", "general.sub.sub")
        assert ds.search_scope("general.sub", current_scope_only=False, return_all=True) == \
            [("number", "general.sub", 11), ("number1", "general.sub", 22)]
        assert ds.search_scope("general.sub.sub", current_scope_only=False, return_all=True) == \
            [("number", "general.sub.sub", "ABC"), ("number1", "general.sub", 22), ("number2", "general.sub.sub", 3)]

    def test_create_args_dict(self, ds):
        ds.set("tester1", 1, "general")
        ds.set("tester2", 11, "general")
        ds.set("tester2", 10, "general.sub")
        ds.set("tester3", 21, "general")
        args = ["tester1", "tester2", "tester3", "tester4"]
        assert ds.create_args_dict(args) == {"tester1": 1, "tester2": 11, "tester3": 21}
        assert ds.create_args_dict(args, "general.sub") == {"tester1": 1, "tester2": 10, "tester3": 21}
        assert ds.create_args_dict(["notAvail", "alsonot"]) == {}

    def test_set_args_from_dict(self, ds):
        ds.set_args_from_dict({"tester1": 1, "tester2": 10, "tester3": 21})
        assert ds.get("tester1", "general") == 1
        assert ds.get("tester2", "general") == 10
        assert ds.get("tester3", "general") == 21
        ds.set_args_from_dict({"tester1": 111}, "general.sub")
        assert ds.get("tester1", "general.sub") == 111
        assert ds.get("tester3", "general.sub") == 21