import os
from unittest import TestCase

from nose.tools import assert_equal  # pylint: disable=E0611
from mock.mock import Mock, MagicMock, call

from wlauto.exceptions import ConfigError
from wlauto.core.configuration.parsers import (get_aliased_param,
                                               _load_file, ConfigParser, EnvironmentVarsParser,
                                               CommandLineArgsParser)
from wlauto.core.configuration import (WAConfiguration, RunConfiguration, JobsConfiguration,
                                       PluginCache)
from wlauto.utils.types import toggle_set


class TestFunctions(TestCase):

    def test_load_file(self):
        # This does not test read_pod

        # Non-existant file
        with self.assertRaises(ValueError):
            _load_file("THIS-IS-NOT-A-FILE", "test file")
        base_path = os.path.dirname(os.path.realpath(__file__))

        # Top level entry not a dict
        with self.assertRaisesRegexp(ConfigError, r".+ does not contain a valid test file structure; top level must be a dict\."):
            _load_file(os.path.join(base_path, "data", "test-agenda-not-dict.yaml"), "test file")

        # Yaml syntax error
        with self.assertRaisesRegexp(ConfigError, r"Error parsing test file .+: Syntax Error on line 1"):
            _load_file(os.path.join(base_path, "data", "test-agenda-bad-syntax.yaml"), "test file")

        # Ideal case
        _load_file(os.path.join(base_path, "data", "test-agenda.yaml"), "test file")

    def test_get_aliased_param(self):
        # Ideal case
        d_correct = {"workload_parameters": [1, 2, 3],
                     "instruments": [2, 3, 4],
                     "some_other_param": 1234}
        assert_equal(get_aliased_param(d_correct, [
            'workload_parameters',
            'workload_params',
            'params'
        ], default=[], pop=False), [1, 2, 3])

        # Two aliases for the same parameter given
        d_duplicate = {"workload_parameters": [1, 2, 3],
                       "workload_params": [2, 3, 4]}
        with self.assertRaises(ConfigError):
            get_aliased_param(d_duplicate, [
                'workload_parameters',
                'workload_params',
                'params'
            ], default=[])

        # Empty dict
        d_none = {}
        assert_equal(get_aliased_param(d_none, [
            'workload_parameters',
            'workload_params',
            'params'
        ], default=[]), [])

        # Aliased parameter not present in dict
        d_not_present = {"instruments": [2, 3, 4],
                         "some_other_param": 1234}
        assert_equal(get_aliased_param(d_not_present, [
            'workload_parameters',
            'workload_params',
            'params'
        ], default=1), 1)

        # Testing pop functionality
        assert_equal("workload_parameters" in d_correct, True)
        get_aliased_param(d_correct, [
            'workload_parameters',
            'workload_params',
            'params'
        ], default=[])
        assert_equal("workload_parameters" in d_correct, False)


class TestConfigParser(TestCase):

    def test_error_cases(self):
        wa_config = Mock(spec=WAConfiguration)
        wa_config.configuration = WAConfiguration.configuration
        run_config = Mock(spec=RunConfiguration)
        run_config.configuration = RunConfiguration.configuration
        config_parser = ConfigParser(wa_config,
                                     run_config,
                                     Mock(spec=JobsConfiguration),
                                     Mock(spec=PluginCache))

        # "run_name" can only be in agenda config sections
        #' and is handled by AgendaParser
        err = 'Error in "Unit test":\n' \
              '"run_name" can only be specified in the config section of an agenda'
        with self.assertRaisesRegexp(ConfigError, err):
            config_parser.load({"run_name": "test"}, "Unit test")

        # Instrument and result_processor lists in the same config cannot
        # have conflicting entries.
        err = 'Error in "Unit test":\n' \
              '"instrumentation" and "result_processors" have conflicting entries:'
        with self.assertRaisesRegexp(ConfigError, err):
            config_parser.load({"instruments": ["one", "two", "three"],
                                "result_processors": ["~one", "~two", "~three"]},
                               "Unit test")

    def test_config_points(self):
        wa_config = Mock(spec=WAConfiguration)
        wa_config.configuration = WAConfiguration.configuration

        run_config = Mock(spec=RunConfiguration)
        run_config.configuration = RunConfiguration.configuration

        jobs_config = Mock(spec=JobsConfiguration)
        plugin_cache = Mock(spec=PluginCache)
        config_parser = ConfigParser(wa_config, run_config, jobs_config, plugin_cache)

        cfg = {
            "assets_repository": "/somewhere/",
            "logging": "verbose",
            "project": "some project",
            "project_stage": "stage 1",
            "iterations": 9001,
            "workload_name": "name"
        }
        config_parser.load(cfg, "Unit test")
        wa_config.set.assert_has_calls([
            call("assets_repository", "/somewhere/"),
            call("logging", "verbose")
        ], any_order=True)
        run_config.set.assert_has_calls([
            call("project", "some project"),
            call("project_stage", "stage 1")
        ], any_order=True)
        print jobs_config.set_global_config.call_args_list
        jobs_config.set_global_config.assert_has_calls([
            call("iterations", 9001),
            call("workload_name", "name"),
            call("instrumentation", toggle_set()),
            call("instrumentation", toggle_set())
        ], any_order=True)

        # Test setting global instruments including a non-conflicting duplicate ("two")
        jobs_config.reset_mock()
        instruments_and_result_processors = {
            "instruments": ["one", "two"],
            "result_processors": ["two", "three"]
        }
        config_parser.load(instruments_and_result_processors, "Unit test")
        jobs_config.set_global_config.assert_has_calls([
            call("instrumentation", toggle_set(["one", "two"])),
            call("instrumentation", toggle_set(["two", "three"]))
        ], any_order=True)

        # Testing a empty config
        jobs_config.reset_mock()
        config_parser.load({}, "Unit test")
        jobs_config.set_global_config.assert_has_calls([], any_order=True)
        wa_config.set.assert_has_calls([], any_order=True)
        run_config.set.assert_has_calls([], any_order=True)


class TestEnvironmentVarsParser(TestCase):

    def test_environmentvarsparser(self):
        wa_config = Mock(spec=WAConfiguration)
        calls = [call('user_directory', '/testdir'),
                 call('plugin_paths', ['/test', '/some/other/path', '/testy/mc/test/face'])]

        # Valid env vars
        valid_environ = {"WA_USER_DIRECTORY": "/testdir",
                         "WA_PLUGIN_PATHS": "/test:/some/other/path:/testy/mc/test/face"}
        EnvironmentVarsParser(wa_config, valid_environ)
        wa_config.set.assert_has_calls(calls)

        # Alternative env var name
        wa_config.reset_mock()
        alt_valid_environ = {"WA_USER_DIRECTORY": "/testdir",
                             "WA_EXTENSION_PATHS": "/test:/some/other/path:/testy/mc/test/face"}
        EnvironmentVarsParser(wa_config, alt_valid_environ)
        wa_config.set.assert_has_calls(calls)

        # Test that WA_EXTENSION_PATHS gets merged with WA_PLUGIN_PATHS.
        # Also checks that other enviroment variables don't cause errors
        wa_config.reset_mock()
        calls = [call('user_directory', '/testdir'),
                 call('plugin_paths', ['/test', '/some/other/path']),
                 call('plugin_paths', ['/testy/mc/test/face'])]
        ext_and_plgin = {"WA_USER_DIRECTORY": "/testdir",
                         "WA_PLUGIN_PATHS": "/test:/some/other/path",
                         "WA_EXTENSION_PATHS": "/testy/mc/test/face",
                         "RANDOM_VAR": "random_value"}
        EnvironmentVarsParser(wa_config, ext_and_plgin)
        # If any_order=True then the calls can be in any order, but they must all appear
        wa_config.set.assert_has_calls(calls, any_order=True)

        # No WA enviroment variables present
        wa_config.reset_mock()
        EnvironmentVarsParser(wa_config, {"RANDOM_VAR": "random_value"})
        wa_config.set.assert_not_called()


class TestCommandLineArgsParser(TestCase):
    wa_config = Mock(spec=WAConfiguration)
    run_config = Mock(spec=RunConfiguration)
    jobs_config = Mock(spec=JobsConfiguration)

    cmd_args = MagicMock(
        verbosity=1,
        output_directory="my_results",
        instruments_to_disable=["abc", "def", "ghi"],
        only_run_ids=["wk1", "s1_wk4"],
        some_other_setting="value123"
    )
    CommandLineArgsParser(cmd_args, wa_config, run_config, jobs_config)
    wa_config.set.assert_has_calls([call("verbosity", 1)], any_order=True)
    run_config.set.assert_has_calls([call("output_directory", "my_results")], any_order=True)
    jobs_config.disable_instruments.assert_has_calls([
        call(toggle_set(["~abc", "~def", "~ghi"]))
    ], any_order=True)
    jobs_config.only_run_ids.assert_has_calls([call(["wk1", "s1_wk4"])], any_order=True)


class TestAgendaParser(TestCase):
    pass