#  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.

import re

from .platform import PY3
from .robottypes import is_string
from ..version import ALIAS_MARKER


if PY3:
    unichr = chr

_CONTROL_WORDS = frozenset(('ELSE', 'ELSE IF', 'AND', ALIAS_MARKER, 'WITH NAME'))
_SEQUENCES_TO_BE_ESCAPED = ('\\', '${', '@{', '%{', '&{', '*{', '=')


def escape(item):
    if not is_string(item):
        return item
    if item in _CONTROL_WORDS:
        return '\\' + item
    for seq in _SEQUENCES_TO_BE_ESCAPED:
        if seq in item:
            item = item.replace(seq, '\\' + seq)
    return item


def unescape(item):
    if not (is_string(item) and '\\' in item):
        return item
    return Unescaper().unescape(item)


class Unescaper(object):

    def unescape(self, string):
        return ''.join(self._yield_unescaped(string))

    def _yield_unescaped(self, string):
        while '\\' in string:
            finder = EscapeFinder(string)
            yield finder.before + finder.backslashes
            if finder.escaped and finder.text:
                yield self._unescape(finder.text)
            else:
                yield finder.text
            string = finder.after
        yield string

    def _unescape(self, text):
        try:
            escape = str(text[0])
        except UnicodeError:
            return text
        try:
            unescaper = getattr(self, '_unescaper_for_' + escape)
        except AttributeError:
            return text
        else:
            return unescaper(text[1:])

    def _unescaper_for_n(self, text):
        # TODO: Deprecate ignoring space after newline in RF 3.2.
        if text.startswith(' '):
            text = text[1:]
        return '\n' + text

    def _unescaper_for_r(self, text):
        return '\r' + text

    def _unescaper_for_t(self, text):
        return '\t' + text

    def _unescaper_for_x(self, text):
        return self._unescape_character(text, 2, 'x')

    def _unescaper_for_u(self, text):
        return self._unescape_character(text, 4, 'u')

    def _unescaper_for_U(self, text):
        return self._unescape_character(text, 8, 'U')

    def _unescape_character(self, text, length, escape):
        try:
            char = self._get_character(text[:length], length)
        except ValueError:
            return escape + text
        else:
            return char + text[length:]

    def _get_character(self, text, length):
        if len(text) < length or not text.isalnum():
            raise ValueError
        ordinal = int(text, 16)
        # No Unicode code points above 0x10FFFF
        if ordinal > 0x10FFFF:
            raise ValueError
        # unichr only supports ordinals up to 0xFFFF with narrow Python builds
        if ordinal > 0xFFFF:
            return eval("u'\\U%08x'" % ordinal)
        return unichr(ordinal)


class EscapeFinder(object):
    _escaped = re.compile(r'(\\+)([^\\]*)')

    def __init__(self, string):
        res = self._escaped.search(string)
        self.before = string[:res.start()]
        escape_chars = len(res.group(1))
        self.backslashes = '\\' * (escape_chars // 2)
        self.escaped = bool(escape_chars % 2)
        self.text = res.group(2)
        self.after = string[res.end():]


def split_from_equals(string):
    if not is_string(string) or '=' not in string:
        return string, None
    index = _get_split_index(string)
    if index == -1:
        return string, None
    return string[:index], string[index+1:]

def _get_split_index(string):
    index = 0
    while '=' in string[index:]:
        index += string[index:].index('=')
        if _not_escaping(string[:index]):
            return index
        index += 1
    return -1

def _not_escaping(name):
    backslashes = len(name) - len(name.rstrip('\\'))
    return backslashes % 2 == 0
