# vim: fileencoding=utf-8
#
from .base_format import Format, FormatError
from ..template_engine import ParametersContainer
from collections import OrderedDict
import re
try:
    from lxml.etree.ElementTree import fromstring
except ImportError:
    from xml.etree.ElementTree import fromstring


class RegexFormat(Format):
    FORMAT = 'regex'
    EXECUTABLE = False
    FORMAT_PARAMETERS = {'multiline', 'dotall', 'comment'}

    comment_symbol = '#'

    def __init__(self, document_text: str,
                 template_path,
                 ignore_comments=False,
                 join_before=False,
                 add_header=False,
                 already_changed=False,
                 parameters=ParametersContainer()):
        processing_methods = OrderedDict()
        super().__init__(processing_methods)

        self._multiline_flag = parameters.multiline
        self._dotall_flag = parameters.dotall
        self._parsed_patch = None

        if add_header and not ignore_comments:
            self.header, document_text = self._get_header_and_document_text(
                                               document_text,
                                               template_path,
                                               already_changed=already_changed)
        else:
            self.header = ''
        self._document_text = document_text
        self._FLAG_VALUES = {'True': True,
                             'False': False,
                             'true': True,
                             'false': False,
                             '1': True,
                             '0': False}

        self._XML_ROOT_LINE = '<?xml version="1.0" encoding="utf-8"?>\
                               <patch>{0}</patch>'

    def _parse_patch(self, patch_text):
        '''Метод, составляющий из текста шаблона xml документ и разбирающий его
        с помощью lxml.'''
        xml_patch = self._XML_ROOT_LINE.format(patch_text.strip())
        try:
            self._parsed_patch = fromstring(xml_patch)
        except Exception:
            raise FormatError('can not parse patch document')

    def join_template(self, template):
        tmp_multiline = template._multiline_flag
        tmp_dotall = template._dotall_flag

        template._multiline_flag = self._multiline_flag
        template._dotall_flag = self._dotall_flag

        self._document_text = template._join(self._document_text)

        template._multiline_flag = tmp_multiline
        template._dotall_flag = tmp_dotall

    def _join(self, input_text):
        '''Метод для запуска наложения патча.'''
        self._parse_patch(self._document_text)

        if not input_text.strip() == '':
            self._document_to_patch = input_text
        else:
            return input_text

        if not self._patch_document(input_text):
            raise FormatError('Error: Can not run patch.')
        else:
            after_patch = self._document_to_patch
            self._document_to_patch = ''

            return after_patch

    def _patch_document(self, document_to_patch):
        '''Метод, обходящий теги шаблона и использующий указанные в нем
        регулярные выражения.'''
        patch_iterator = self._parsed_patch.iter()
        PATCH_DOCUMENT_TAGS = ('reg', 'text')

        patch_element = next(patch_iterator, False)

        if not patch_element or not patch_element.tag == 'patch':
            raise FormatError('incorrect text of the template')

        while True:
            for patch_tag in PATCH_DOCUMENT_TAGS:
                patch_element = next(patch_iterator, None)

                if patch_element is None:
                    if patch_tag == 'text':
                        raise FormatError('last <text>Text</text> '
                                          'object is missed.')
                    else:
                        break

                if patch_element.tag == patch_tag:
                    if patch_element.text is not None:
                        element_text = patch_element.text.strip()
                        if element_text == '':
                            raise FormatError(
                                        ("Error: Incorrect text of the "
                                         "template: <{0}>%s</{0}>").format(
                                                                      patch_tag
                                                                           ))
                    else:
                        raise FormatError("Error: Incorrect text of the "
                                          "template: <{0}></{0}>").format(
                                                                      patch_tag
                                                                         )

                    if patch_tag == 'reg':
                        dotall = patch_element.attrib.get('dotall', False)
                        regex_flags = 0

                        if 'multiline' in patch_element.attrib:
                            multiline = patch_element.attrib['multiline']
                            if multiline not in self._FLAG_VALUES:
                                raise FormatError('invalid multiline value')
                            else:
                                multiline = self._FLAG_VALUES[multiline]

                            # Если глобально флаг MULTILINE включен, но в
                            # атрибутах тэга <reg> этот флаг присутствует со
                            # значением False -- для этого регулярного
                            # выражения этот флаг также будет False.
                            multiline_global = self._multiline_flag & multiline
                        else:
                            multiline = False
                            multiline_global = self._multiline_flag

                        if multiline_global or multiline:
                            regex_flags |= re.MULTILINE

                        if 'dotall' in patch_element.attrib:
                            dotall = patch_element.attrib['dotall']
                            if dotall not in self._FLAG_VALUES:
                                raise FormatError('invalid dotall value')
                            else:
                                dotall = self._FLAG_VALUES[dotall]

                            # Если глобально флаг DOTALL включен, но в
                            # атрибутах тэга <reg> этот флаг присутствует со
                            # значением False -- для этого регулярного
                            # выражения этот флаг также будет False.
                            dotall_global = self._dotall_flag & dotall
                        else:
                            dotall = False
                            dotall_global = self._dotall_flag

                        if dotall_global or dotall:
                            regex_flags |= re.DOTALL

                        regex_expression = re.compile(element_text,
                                                      regex_flags)
                    else:
                        text_for_replace = element_text
                else:
                    if patch_element.tag in PATCH_DOCUMENT_TAGS:
                        error_message = '<{0}> is expected, <{1}> instead.'.\
                                            format(patch_tag,
                                                   patch_element.tag)
                    else:
                        error_message = 'unknown tag: {0}'.format(
                                                              patch_element.tag
                                                                 )
                    raise ("incorrect text of the template: {}".format(
                                                                error_message))
            else:
                self._document_to_patch = re.sub(regex_expression,
                                                 text_for_replace,
                                                 self._document_to_patch)
                continue
            return True

    @property
    def document_text(self):
        return '{}{}'.format(self.header, self._document_text)
