#  Copyright 2008-2015 Nokia Networks
#  Copyright 2016-     Robot Framework Foundation
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from robot.utils import normalize, normalize_whitespace, RecommendationFinder

from .tokens import Token


class Settings(object):
    names = ()
    aliases = {}
    multi_use = (
        'Metadata',
        'Library',
        'Resource',
        'Variables'
    )
    single_value = (
        'Resource',
        'Test Timeout',
        'Test Template',
        'Timeout',
        'Template'
    )
    name_and_arguments = (
        'Metadata',
        'Suite Setup',
        'Suite Teardown',
        'Test Setup',
        'Test Teardown',
        'Test Template',
        'Setup',
        'Teardown',
        'Template',
        'Resource',
        'Variables'
    )
    name_arguments_and_with_name = (
        'Library',
    )

    def __init__(self):
        self.settings = {n: None for n in self.names}

    def lex(self, statement):
        setting = statement[0]
        name = self._format_name(setting.value)
        normalized = self._normalize_name(name)
        try:
            self._validate(name, normalized, statement)
        except ValueError as err:
            self._lex_error(setting, statement[1:], err.args[0])
        else:
            self._lex_setting(setting, statement[1:], normalized)

    def _format_name(self, name):
        return name

    def _normalize_name(self, name):
        name = normalize_whitespace(name).title()
        if name in self.aliases:
            return self.aliases[name]
        return name

    def _validate(self, name, normalized, statement):
        if normalized not in self.settings:
            message = self._get_non_existing_setting_message(name, normalized)
            raise ValueError(message)
        if self.settings[normalized] is not None and normalized not in self.multi_use:
            raise ValueError("Setting '%s' is allowed only once. "
                             "Only the first value is used." % name)
        if normalized in self.single_value and len(statement) > 2:
            raise ValueError("Setting '%s' accepts only one value, got %s."
                             % (name, len(statement) - 1))

    def _get_non_existing_setting_message(self, name, normalized):
        if normalized in TestCaseFileSettings.names:
            is_resource = isinstance(self, ResourceFileSettings)
            return "Setting '%s' is not allowed in %s file." % (
                name, 'resource' if is_resource else 'suite initialization'
            )
        return RecommendationFinder(normalize).find_and_format(
            name=normalized,
            candidates=tuple(self.settings) + tuple(self.aliases),
            message="Non-existing setting '%s'." % name
        )

    def _lex_error(self, setting, values, error):
        setting.set_error(error)
        for token in values:
            token.type = Token.COMMENT

    def _lex_setting(self, setting, values, name):
        self.settings[name] = values
        setting.type = name.upper()
        if name in self.name_and_arguments:
            self._lex_name_and_arguments(values)
        elif name in self.name_arguments_and_with_name:
            self._lex_name_arguments_and_with_name(values)
        else:
            self._lex_arguments(values)

    def _lex_name_and_arguments(self, tokens):
        if tokens:
            tokens[0].type = Token.NAME
        self._lex_arguments(tokens[1:])

    def _lex_name_arguments_and_with_name(self, tokens):
        self._lex_name_and_arguments(tokens)
        if len(tokens) > 1 and \
                normalize_whitespace(tokens[-2].value) == 'WITH NAME':
            tokens[-2].type = Token.WITH_NAME
            tokens[-1].type = Token.NAME

    def _lex_arguments(self, tokens):
        for token in tokens:
            token.type = Token.ARGUMENT


class TestCaseFileSettings(Settings):
    names = (
        'Documentation',
        'Metadata',
        'Suite Setup',
        'Suite Teardown',
        'Test Setup',
        'Test Teardown',
        'Test Template',
        'Test Timeout',
        'Force Tags',
        'Default Tags',
        'Library',
        'Resource',
        'Variables'
    )
    aliases = {
        'Task Setup': 'Test Setup',
        'Task Teardown': 'Test Teardown',
        'Task Template': 'Test Template',
        'Task Timeout': 'Test Timeout',
    }


class InitFileSettings(Settings):
    names = (
        'Documentation',
        'Metadata',
        'Suite Setup',
        'Suite Teardown',
        'Test Setup',
        'Test Teardown',
        'Test Timeout',
        'Force Tags',
        'Library',
        'Resource',
        'Variables'
    )


class ResourceFileSettings(Settings):
    names = (
        'Documentation',
        'Library',
        'Resource',
        'Variables'
    )


class TestCaseSettings(Settings):
    names = (
        'Documentation',
        'Tags',
        'Setup',
        'Teardown',
        'Template',
        'Timeout'
    )

    def __init__(self, parent):
        Settings.__init__(self)
        self.parent = parent

    def _format_name(self, name):
        return name[1:-1].strip()

    @property
    def template_set(self):
        template = self.settings['Template']
        if self._has_disabling_value(template):
            return False
        parent_template = self.parent.settings['Test Template']
        return self._has_value(template) or self._has_value(parent_template)

    def _has_disabling_value(self, setting):
        if setting is None:
            return False
        return setting == [] or setting[0].value.upper() == 'NONE'

    def _has_value(self, setting):
        return setting and setting[0].value


class KeywordSettings(Settings):
    names = (
        'Documentation',
        'Arguments',
        'Teardown',
        'Timeout',
        'Tags',
        'Return'
    )

    def _format_name(self, name):
        return name[1:-1].strip()
