from pyparsing import Literal, Word, ZeroOrMore, Group, Dict, Optional,\
                      restOfLine, empty, printables, OneOrMore, oneOf, nums,\
                      lineno, line, col, Keyword, SkipTo, LineEnd, Combine
from enum import Enum


class CalculateIniParser:
    '''Класс парсера calculate.ini файлов.'''
    class Define(Enum):
        assign = 0
        append = 1
        remove = 2

    def __init__(self):
        self.operations = {"=": self.Define.assign,
                      "+=": self.Define.append,
                      "-=": self.Define.remove}

        lbrack = Literal("[")
        rbrack = Literal("]")

        # comma = Literal(",").suppress()
        comment_symbol = Literal(';') | Literal('#')

        # Define = self.Define
        value_operation = (Literal("=") | Combine(Literal("+") + Literal("="))
                           | Combine(Literal("-") + Literal("=")))

        comment = comment_symbol + Optional(restOfLine)

        section_name = Word(printables+'\t', excludeChars='[]')

        value_name = Word(printables+'\t', excludeChars='=-+')

        # non_comma = Word(printables+'\t', excludeChars=',')
        clear_section = lbrack.suppress() + Group(empty) + rbrack.suppress()

        section_start = Group(OneOrMore(lbrack.suppress() + section_name
                              + rbrack.suppress())
                              + (clear_section | ~lbrack())
                              + LineEnd().suppress())

        # Если содержимое ini-файла не предваряется заголовком секции,
        # значит эта строка ошибочна.
        unexpected = Group(~section_start + SkipTo(LineEnd(),
                                                   include=True))("error")
        unexpected.setParseAction(self._unexpected_token)

        key_value = (~lbrack + value_name
                     + value_operation + empty
                     + restOfLine + LineEnd().suppress())

        def strip_key_value(tokens):
            tokens[0] = tokens[0].strip()
            tokens[1] = tokens[1].strip()

        key_value.setParseAction(strip_key_value)

        self.ini_section_parser = (section_start
                                   + Group(ZeroOrMore(
                                                Group(key_value | unexpected)))
                                   | unexpected)
        self.ini_section_parser.ignore(comment)

    def _unexpected_token(self, string, location, tokens):
        '''Метод вызываемый парсером, если обнаружена некорректная строка,
        предназначен для получения некорректной строки и ее дальнейшего
        разбора.'''
        error_line = line(location, string).strip()
        if error_line:
            return (error_line, lineno(location, string),
                    col(location, string))

    def parse(self, data):
        for tokens, start, end in self.ini_section_parser.scanString(data):
            if tokens.getName() == "error":
                print('error tokens: {}'.format(tokens))
                yield {'error': tokens}

            section, defkeys = tokens

            section_list = section.asList()
            if section_list[-1] == []:
                self.clear_section(section_list[:-1])
                yield {'clear_section': (section_list[:-1], )}

            self.start_section(section.asList())
            for defkey in defkeys:
                yield {'define_key': (section.asList(), defkey[0], defkey[2],
                                      self.operations[defkey[1]])}
