#  Copyright 2008-2015 Nokia Solutions and Networks
#
#  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.

import re

from robotide import robotapi, utils
from robotide.utils import variablematcher
from robotide.controller.basecontroller import _BaseController
from robotide.controller.cellinfo import (CellPosition, CellType, CellInfo,
                                          CellContent, ContentType)
from robotide.namespace.local_namespace import LocalNamespace


class StepController(_BaseController):

    _GIVEN_WHEN_THEN_MATCHER = re.compile(r'^(given|when|then|and)\s*', re.I)

    def __init__(self, parent, step):
        self._init(parent, step)
        self._step.args = self._change_last_empty_to_empty_var(
            self._step.args, self._step.comment)

    def _init(self, parent, step):
        self.parent = parent
        self._step = step
        self._cell_info_cache = {}

    @property
    def display_name(self):
        return 'Step'

    @property
    def datafile_controller(self):
        return self.parent.datafile_controller

    def _change_last_empty_to_empty_var(self, args, comment):
        if comment:
            return args
        return args[:-1] + ['${EMPTY}'] if args and args[-1] ==
 '' else args

    def get_keyword_info(self, kw):
        if not kw:
            return None
        return self.parent.get_keyword_info(kw)

    def __eq__(self, other):
        if self is other:
            return True
        return self._steps_are_equal(self._step, other._step)

    def _steps_are_equa
l(self, fst, snd):
        if fst is snd:
            return True
        if not snd:
            return False
        return (fst.assign == snd.assign and
                fst.name == snd.name and
                fst.args == snd.args)

    def get_value(self, col):
        values = self.as_list()
        if len(values) <= col:
            return ''
        return values[col]

    def get_cell_info(self, col):
        if col not in self._cell_info_cache:
            position = self._get_cell_position(col)
            content = self._get_content_with_type(col, position)
            self._cell_info_cache[col] = self._build_cell_info(
                content, position)
        return self._cell_info_cache[col]

    @property
    def assignments(self):
        return self._step.assign

    def is_assigning(self, value):
        for assignment in self.assignments:
            if assignment.replace('=', '').strip() == \
                    value.replace('=', '').strip():
                return True
        return False

    def _build_cell_info(self, content, position):
        return CellInfo(content, position)

    def _get_cell_position(self, col):
        # TODO: refactor
        if self.parent.has_template():
            return CellPosition(CellType.UNKNOWN, None)
        col -= len(self._step.assign)
        if col < 0:
            return CellPosition(CellType.ASSIGN, None)
        if col == 0:
            return CellPosition(CellType.KEYWORD, None)
        info = self.get_keyword_info(self._step.name)
        if not info:
            return CellPosition(CellType.UNKNOWN, None)
        args = info.arguments
        args_amount = len(args)
        if args_amount == 0:
            return CellPosition(CellType.MUST_BE_EMPTY, None)
        if col >= args_amount and self._last_argument_is_varargs(args):
            return CellPosition(CellType.OPTIONAL, args[-1])
        if self._has_list_or_dict_var_value_before(col - 1):
            return CellPosition(CellType.UNKNOWN, None)
        if col > args_amount:
            return CellPosition(CellType.MUST_BE_EMPTY, None)
        if col <= self._number_of_mandatory_arguments(args, args_amount):
            return CellPosition(CellType.MANDATORY, args[col - 1])
        return CellPosition(CellType.OPTIONAL, args[col - 1])

    def _number_of_mandatory_arguments(self, args, args_amount):
        defaults = [arg for arg in args if '=' in arg]
        n = args_amount - len(defaults)
        if self._last_argument_is_varargs(args):
            n -= 1
        return n

    def _last_argument_is_varargs(self, args):
        return args[-1].startswith('*')

    def _has_list_or_dict_var_value_before(self, arg_index):
        for idx, value in enumerate(self.args):
            if idx > arg_index:
                return False
            if variablematcher.is_list_variable(value) and \
               not variablematcher.is_list_variable_subitem(value):
                return True
            if robotapi.is_dict_var(value) and \
               not variablematcher.is_dict_var_access(value):
                return True
        return False

    def _get_content_with_type(self, col, position):
        value = self.get_value(col)
        if self._is_commented(col):
            return CellContent(ContentType.COMMENTED, value)
        if self._get_last_none_empty_col_idx() < col:
            return CellContent(ContentType.EMPTY, value)
        if variablematcher.is_variable(value):
            if self._is_unknow_variable(value, position):
                return CellContent(ContentType.UNKNOWN_VARIABLE, value)
            return CellContent(ContentType.VARIABLE, value)
        if self.is_user_keyword(value):
            return CellContent(
                ContentType.USER_KEYWORD, value,
                self.get_keyword_info(value).source)
        if self.is_library_keyword(value):
            return CellContent(
                ContentType.LIBRARY_KEYWORD, value,
                self.get_keyword_info(value).source)
        return CellContent(ContentType.STRING, value)

    def _is_unknow_variable(self, value, position):
        if position.type == CellType.ASSIGN:
            return False
        is_known = self._get_local_namespace().has_name(value)
        if is_known:
            return False
        inner_value = value[2:-1]
        modified = re.split(r'\W', inner_value, 1)[0]
        return not self._get_local_namespace().has_name(
            '%s{%s}' % (value[0], modified))

    def _get_local_namespace(self):
        index = self.parent.index_of_step(self._step)
        return LocalNamespace(
            self.parent, self.datafile_controller._namespace, index)

    def _get_last_none_empty_col_idx(self):
        values = self.as_list()
        for i in reversed(range(len(values))):
            if values[i].strip() != '':
                return i
        return None

    def is_modifiable(self):
        return self.datafile_controller.is_modifiable()

    def is_user_keyword(self, value):
        return self.parent.is_user_keyword(value)

    def is_library_keyword(self, value):
        return self.parent.is_library_keyword(value)

    def as_list(self):
        return self._step.as_list()

    def contains_variable(self, name):
        return any(variablematcher.value_contains_variable(item, name)
                   for item in self.as_list())

    def contains_variable_assignment(self, name):
        return any(variablematcher.value_contains_variable(item, "%s=" % name)
                   for item in self.as_list())

    def contains_keyword(self, name):
        return any(self._kw_name_match(item, name)
                   for item in [self.keyword or ''] + self.args)

    def _kw_name_match(self, item, expected):
        if isinstance(expected, basestring):
            return utils.eq(item, expected) or (
                self._GIVEN_WHEN_THEN_MATCHER.match(item) and
                utils.eq(
                    self._GIVEN_WHEN_THEN_MATCHER.sub('', item), expected))
        return expected.match(item)

    def replace_keyword(self, new_name, old_name):
        if self._kw_name_match(self.key