# vim: fileencoding=utf-8
#
# ToDo: написать счетчик скобок для финальной оценки корректности
# документа.
#
from .base_format import Format
from ..template_engine import ParametersContainer
from collections import OrderedDict
from pyparsing import originalTextFor, Literal, Word, printables,\
                      OneOrMore, alphanums, ParseException, Regex,\
                      Group, Optional, alphas, lineEnd, lineStart, Keyword


class DovecotFormat(Format):
    FORMAT = 'dovecot'
    EXECUTABLE = False

    _initialized = False

    comment_symbol = '#'

    def __new__(cls, *args, **kwargs):
        if not cls._initialized:
            cls._initialize_parser()
        return super().__new__(cls)

    def __init__(self, document_text: str,
                 template_path,
                 ignore_comments=False,
                 join_before=False,
                 add_header=False,
                 already_changed=False,
                 parameters=ParametersContainer()):
        processing_methods = [self._parse_comment_line,
                              self._parse_section_start_line,
                              self._parse_include_line,
                              self._parse_section_end_line,
                              self._parse_parameter_line,
                              self._parse_parameter_to_delete_line]

        super().__init__(processing_methods)
        self._ignore_comments = ignore_comments
        self._comments_processing = True
        self._need_finish = True
        self._join_before = join_before

        self._section_stack = OrderedDict()
        self._current_section_name = ''

        self._last_comments_list = []

        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 = ''

        document_text = document_text.strip()
        if document_text == '':
            self._document_dictionary = OrderedDict()
        else:
            document_lines = self._get_list_of_logic_lines(document_text)
            self._lines_to_dictionary(document_lines)

    @classmethod
    def _initialize_parser(cls):
        # Знаки пунктуации и действий.
        left_brace = Literal('{')
        right_brace = Literal('}')
        action_symbols = (Literal('!') | Literal('-'))

        cls._comment_line_parser = originalTextFor(
                                   Literal(cls.comment_symbol)
                                   + Regex(r'.*'))('comment')

        # Для парсинга строк с началом секций.
        section = Word(alphas, alphanums+'-_', excludeChars='{}')

        section_name = Word(printables, excludeChars='{}')

        cls._section_start_parser = Group(Optional(action_symbols,
                                                   default='')('action')
                                          + section
                                          + Optional(section_name)
                                          + left_brace.suppress())('name')

        # Для парсинга строк, указывающих конец секций.
        cls._section_end_parser = lineStart() + right_brace + lineEnd()

        # Для парсинга строк, содержащих параметры.
        parameter_name = Word(alphas, alphanums+'_-', excludeChars='{}=')

        parameter_value = Regex(r'\S+(\s+\S+)*')

        cls._parameter_line_parser = (Group(Optional(action_symbols,
                                                     default='')('action')
                                            + parameter_name)('name')
                                      + Literal('=').suppress()
                                      + originalTextFor(
                                                           parameter_value
                                                       )('value'))

        # Для парсинга строк с параметрами, подлежащими удалению.
        cls._parameter_to_delete_parser = (
                                        action_symbols('action')
                                        + parameter_name
                                        + Optional(Literal('=')).suppress()
                                           )

        # Для парсинга строк, содержащих директиву !include.
        include = Keyword('!include') | Keyword('!include_try')

        include_line_plain = (Optional(~action_symbols, default='')('action')
                              + include('keyword') + Regex(r'\S+')('value'))

        include_line_to_delete = (action_symbols('action')
                                  + include('keyword')
                                  + Regex(r'\S+')('value'))

        cls._include_line_parser = (include_line_plain |
                                    include_line_to_delete)
        cls._initialized = True

    def _parse_section_start_line(self, line):
        try:
            parsing_result = self._section_start_parser.parseString(line)
            self._match = True

            section_name = tuple(parsing_result.name.asList())

            if not self._last_comments_list == []:
                section_content = OrderedDict({'#': self._last_comments_list})
                self._last_comments_list = []
            else:
                section_content = OrderedDict()

            new_section = OrderedDict({section_name: section_content})

            if self._section_stack == OrderedDict():
                if section_name in self._document_dictionary:
                    new_section = OrderedDict(
                                      {section_name:
                                       self._document_dictionary[section_name]}
                                             )
            else:
                if section_name in \
                        self._section_stack[self._current_section_name]:
                    new_section = OrderedDict({
                      section_name:
                      self._section_stack[self._current_section_name]
                      [section_name]
                                              })
                else:
                    self._section_stack[self._current_section_name].update(
                                                                    new_section
                                                                          )
            self._section_stack.update(new_section)
            self._current_section_name = section_name
        except ParseException:
            return

    def _parse_section_end_line(self, line):
        try:
            self._section_end_parser.parseString(line)
            self._match = True

            last_section_name, last_section = self._section_stack.popitem()

            if self._section_stack == OrderedDict():
                self._item_to_add = OrderedDict({last_section_name:
                                                 last_section})
                self._ready_to_update = True
                self._current_section_name = ''
            else:
                self._current_section_name = next(reversed(
                                                           self._section_stack
                                                          ))
        except ParseException:
            return

    def _parse_parameter_line(self, line):
        try:
            parsing_result = self._parameter_line_parser.parseString(line)
            self._match = True

            parameter_name = tuple(parsing_result.name.asList())
            parameter_value = (self._last_comments_list
                               + [parsing_result.value.strip()])
            self._last_comments_list = []
            parameter = OrderedDict({parameter_name: parameter_value})

            if self._section_stack == OrderedDict():
                self._item_to_add = parameter
                self._ready_to_update = True
            else:
                self._section_stack[self._current_section_name].update(
                                                                      parameter
                                                                      )
        except ParseException:
            return

    def _parse_parameter_to_delete_line(self, line):
        try:
            parsing_result = self._parameter_to_delete_parser.parseString(line)
            self._match = True

            parameter_name = tuple(parsing_result.asList())
            parameter_value = self._last_comments_list + ['']
            self._last_comments_list = []
            parameter = OrderedDict({parameter_name: parameter_value})

            if self._section_stack == OrderedDict():
                self._item_to_add = parameter
                self._ready_to_update = True
            else:
                self._section_stack[self._current_section_name].update(
                                                                      parameter
                                                                      )
        except ParseException:
            return

    def _parse_include_line(self, line):
        try:
            parsing_result = self._include_line_parser.parseString(line)
            self._match = True

            parameter_name = tuple(parsing_result.asList())

            if parsing_result.action == '-':
                return

            parameter_value = self._last_comments_list + ['']
            self._last_comments_list = []
            include_item = OrderedDict({parameter_name: parameter_value})

            if self._section_stack == OrderedDict():
                self._item_to_add = include_item
                self._ready_to_update = True
            else:
                self._section_stack[self._current_section_name].update(
                                                                   include_item
                                                                      )
        except ParseException:
            return

    def _parse_comment_line(self, line):
        try:
            parsing_result = self._comment_line_parser.parseString(line)
            self._match = True

            if not self._ignore_comments:
                self._last_comments_list.append(parsing_result.comment)
        except ParseException:
            return
