# -*- coding: utf-8 -*-

# Copyright 2008-2016 Mir Calculate. http://www.calculate-linux.org
#
#  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
import copy
from ..cl_xml import xpath, xmlDoc, firstChild, insertBefore
from copy import deepcopy
from ..cl_template import blocText
from .samba import samba

class PlasmaArea():
    def __init__(self):
        self.header = False
        self.start = False
        self.fields = []
        self.end = ""


class plasmaBlocText(blocText):
    def findBloc(self, text, captionTxtBloc, bodyTxtBloc):
        """ Делит текст на блоки (с заголовками)

        captionTxtBloc  - регулярное выражение для заголовка блока
        bodyTxtBloc - регулярное выражение для тела блока
        возвращает два списка: первый - заголовки, второй - тела блоков
        """
        # Заголовки блоков
        headersTxt = []
        # Тексты блоков
        blocsTxt = []
        r = captionTxtBloc.search(text)
        if r:
            headersTxt.append(r.group(0))
            txtSpl = text.partition(r.group(0))
            blocTxt = txtSpl[0]
            txtWr = txtSpl[2]
            rb = bodyTxtBloc.search(blocTxt)
            if not blocTxt:
                blocsTxt.append(blocTxt)
            if rb:
                blocsTxt.append(rb.group(0))
            while r:
                r = captionTxtBloc.search(txtWr)
                if r:
                    headersTxt.append(r.group(0))
                    txtSpl = txtWr.partition(r.group(0))
                    blocTxt = txtSpl[0]
                    txtWr = txtSpl[2]
                    rb = bodyTxtBloc.search(blocTxt)
                    if rb:
                        blocsTxt.append(rb.group(0))
                else:
                    blocsTxt.append(txtWr)
        if headersTxt and blocsTxt:
            if len(headersTxt) > len(blocsTxt):
                blocsTxt.insert(0, "")
            elif len(headersTxt) < len(blocsTxt):
                headersTxt.insert(0, "")
            if len(headersTxt) != len(blocsTxt):
                return False
            return headersTxt, blocsTxt
        else:
            return False


class xmlDocPlasma(xmlDoc):
    """Класс для замены метода joinArea в xmlDoc для plasma"""
    # заменяемый метод для xmlDoc
    def joinArea(self, baseNode, xmlNewArea):
        """Объединяет область c областью Body (xmlNewArea c baseNode)"""

        def appendArea(baseNode, xmlNewArea):
            fieldsRemove = xpath.Evaluate(
                "descendant::field[child::action='drop']", xmlNewArea)
            for rmNode in fieldsRemove:
                parentNode = rmNode.getparent()
                parentNode.remove(rmNode)
            captionAreasRemove = xpath.Evaluate(
                "descendant::area/child::caption[child::action='drop']",
                xmlNewArea)
            for rmNodeCapt in captionAreasRemove:
                rmNode = rmNodeCapt.getparent()
                parentNode = rmNode.getparent()
                parentNode.remove(rmNode)
            self.setActionArea(xmlNewArea, "append")
            # Добавляем разделитель областей во вложенные области
            areaNodes = xpath.Evaluate('descendant::area', xmlNewArea)
            for areaNode in areaNodes:
                self.setActionArea(areaNode, "append")
                parentNode = areaNode.getparent()
                insertBefore(parentNode, deepcopy(self.sepAreas), areaNode)
            baseNode.append(xmlNewArea)
            # Добавляем разделитель областей
            insertBefore(baseNode, deepcopy(self.sepAreas), xmlNewArea)

        nodesNames = xpath.Evaluate('child::area/caption/name', baseNode)
        nodesNewArea = xpath.Evaluate('child::caption/name', xmlNewArea)
        if not nodesNames:
            # Добавляем область
            if nodesNewArea:
                newAreaAction = self.getActionArea(xmlNewArea)
                if not (newAreaAction == "drop"):
                    appendArea(baseNode, xmlNewArea)
            return True
        if not nodesNames or not nodesNewArea:
            return False
        nameArea = ""
        if firstChild(nodesNewArea[0]) is not None:
            nameArea = firstChild(nodesNewArea[0]).text.strip()
        flagFindArea = False
        baseNodes = []
        newAreaAction = None
        for oName in nodesNames:
            newAreaAction = self.getActionArea(xmlNewArea)
            oArea = oName.getparent().getparent()
            oNameTxt = ""
            if firstChild(oName) is not None:
                oNameTxt = firstChild(oName).text
            if nameArea == oNameTxt:
                flagFindArea = True
                # При использовании удаления
                if newAreaAction == "drop":
                    prevNode = oName.getparent().getparent().getprevious()
                    removePrevNodes = []
                    while prevNode is not None\
                             and self.getTypeField(prevNode) == "br":
                        removePrevNodes.append(prevNode)
                        prevNode = prevNode.getprevious()
                    for removeNode in removePrevNodes:
                        baseNode.remove(removeNode)
                    baseNode.remove(oName.getparent().getparent())
                    continue
                elif newAreaAction == "replace":
                    oldAreaNode = oName.getparent().getparent()
                    newAreaCaption = xpath.Evaluate('child::caption',
                                                    xmlNewArea)[0]
                    oldAreaCaption = xpath.Evaluate('child::caption',
                                                    oldAreaNode)[0]
                    if newAreaCaption is not None and oldAreaCaption is not None:
                        xmlNewArea.replace(newAreaCaption, oldAreaCaption)
                        self.setActionArea(xmlNewArea, "replace")
                    baseNode.replace(oldAreaNode, xmlNewArea)
                    continue
                baseNodes.append(oName.getparent().getparent())

                # Заменяем QUOTE
                oldAreaNode = oName.getparent().getparent()
                oldAreaQuote = xpath.Evaluate('child::caption/quote',
                                              oldAreaNode)[0]
                if oldAreaQuote is not None and firstChild(oldAreaQuote) is None:
                    newAreaQuote = xpath.Evaluate('child::caption/quote',
                                                  xmlNewArea)[0]
                    oldAreaCaption = xpath.Evaluate('child::caption',
                                                    oldAreaNode)[0]
                    if newAreaQuote is not None and oldAreaCaption is not None:
                        oldAreaCaption.replace(oldAreaQuote, newAreaQuote)

                newFields = xpath.Evaluate('child::field', xmlNewArea)

                joinNewFields = xpath.Evaluate(
                    "child::field[child::action='join']", xmlNewArea)
                self.addNewFielsOldArea(newFields, joinNewFields, oArea)

        if not flagFindArea:
            # Добавляем область
            if not (newAreaAction == "drop"):
                appendArea(baseNode, xmlNewArea)
        else:
            tmpXmlNewAreas = xpath.Evaluate('child::area', xmlNewArea)
            for na in tmpXmlNewAreas:
                for bn in baseNodes:
                    self.joinArea(bn, na)
        return True


class plasma(samba):
    """Класс для обработки конфигурационного файла типа plasma

    """
    _comment = "#"
    configName = "plasma"
    configVersion = "0.1"
    reHeader = re.compile("^[\t ]*\[[^\[\]]+\].*\n?", re.M)
    reBody = re.compile(".+", re.M | re.S)
    reComment = re.compile("^\s*%s.*" % _comment)
    reSeparator = re.compile("=")
    sepFields = "\n"
    reSepFields = re.compile(sepFields)

    def prepare(self):
        self.blocTextObj = plasmaBlocText()
        self._splitToFields = self.splitToFields
        # Объект документ
        self.docObj = self._textToXML()
        # XML документ
        self.doc = self.docObj.doc

    # Делим текст на области включая вложенные (areas массив областей)
    def splitToAllArea(self, text, areas):
        """Делит текст на области включая вложенные

        возвращает список объектов областей (переменная areas)
        """

        area = PlasmaArea

        def findPathArea(listPath, areaF):
            """Ищет путь в области

            areaF - объект area
            listPath - cписок названий областей
            """
            ret = False
            if not listPath:
                return ret
            flagList = False
            if isinstance(areaF, list):
                fields = areaF
                flagList = True
            else:
                fields = areaF.fields
                if areaF.header == listPath[0]:
                    ret = areaF
                else:
                    return ret
            for i in fields:
                if isinstance(i, PlasmaArea):
                    add = False
                    if not flagList:
                        add = listPath.pop(0)
                    if not listPath:
                        break
                    ret = False
                    if i.header == listPath[0]:
                        ret = findPathArea(listPath, i)
                        break
                    else:
                        if add:
                            listPath.insert(0, add)
            if ret == areaF and len(listPath) > 1:
                ret = False
            return ret

        blTmp = self.blocTextObj.findBloc(self.text, self.reHeader, self.reBody)
        blocs = self.getFullAreas(blTmp)
        if isinstance(blocs, list) and not blocs:
            return []
        reH = re.compile("\[([^\[\]]+)\]")
        # Список имен блоков
        namesBlockList = []
        # Временные поля
        fieldsTmp = []
        # Добавляем заголовки
        z = 0
        for h in blocs[0]:
            if not h:
                if blocs[1][z] == "":
                    fieldsTmp.append("")
                    # fieldsTmp.append("\n")
                else:
                    fieldsTmp.append(blocs[1][z])
                z += 1
                continue
            z += 1
            slpNamesBlock = reH.split(h)
            # Отступ слева для заголовка
            indentionLeft = slpNamesBlock[0]
            namesBlock = [x for x in slpNamesBlock if x.strip()]
            # namesBlock = map(lambda x: self.removeSymbolTerm(x), namesBlock)
            findArea = findPathArea(copy.copy(namesBlock), areas)
            findAreaPrev = None
            namesBlockList.append(namesBlock)
            if findArea:
                if len(namesBlock) > 1:
                    namesBlockView = (self.removeSymbolTerm(x) for x in namesBlock)
                else:
                    namesBlockView = namesBlock
                findArea.start = (indentionLeft + "[" +
                                  "][".join(namesBlockView) + "]")
            else:
                i = 0
                lenNamesBlock = len(namesBlock)
                namesBlockTmp = []
                for nameB in namesBlock:
                    namesBlockTmp.append(nameB)
                    findArea = findPathArea(copy.copy(namesBlockTmp), areas)
                    i += 1
                    if not findArea:
                        areaNew = area()
                        areaNew.header = nameB
                        if lenNamesBlock == i:
                            if len(namesBlock) > 1:
                                namesBlockView = (self.removeSymbolTerm(x) for x in namesBlock)
                            else:
                                namesBlockView = namesBlock
                            areaNew.start = (indentionLeft + "[" +
                                             "][".join(namesBlockView) + "]")
                        else:
                            areaNew.start = ""
                        areaNew.end = ""
                        if i == 1:
                            if lenNamesBlock == i:
                                areas += fieldsTmp
                            areas.append(areaNew)
                            findAreaPrev = areas[-1]
                        else:
                            if (lenNamesBlock == i and
                                    isinstance(findAreaPrev, area)):
                                findAreaPrev.fields += fieldsTmp
                            findAreaPrev.fields.append(areaNew)
                            findAreaPrev = findAreaPrev.fields[-1]
                    else:
                        findAreaPrev = findArea
                fieldsTmp = []
        i = 0
        delt = 0
        # Добавляем тела
        for body in blocs[1]:
            if not blocs[0][i]:
                i += 1
                delt += 1
                continue
            ## В случае последнего комментария не добавляем перевод строки
            # if self.reComment.search(body.splitlines()[-1]):
            body = "\n" + body

            namesBlock = namesBlockList[i - delt]
            findArea = findPathArea(copy.copy(namesBlock), areas)
            if isinstance(findArea, area):
                # if findArea.fields:
                # if type(findArea.fields[0]) == types.StringType:
                # findArea.fields.pop(0)
                findArea.fields.insert(0, body)
            i += 1
        return areas

    def createCaptionTerm(self, header, start, end, docObj):
        """Создание пустой области с заголовком

        при создании области проверяется первый символ заголовка
        и добавляется тег action
        "!" - <action>drop</action>
        "-" - <action>replace</action>
        """
        areaAction = False
        if header:
            if header[0] == "!":
                docObj.createCaption(header[1:], [start,
                                                  end.replace("\n", "")])
                areaAction = "drop"
            elif header[0] == "-":
                docObj.createCaption(header[1:], [start,
                                                  end.replace("\n", "")])
                areaAction = "replace"
            else:
                docObj.createCaption(header, [start,
                                              end.replace("\n", "")])
        else:
            docObj.createCaption(header, [start,
                                          end.replace("\n", "")])

        areaXML = docObj.createArea()
        if areaAction:
            docObj.setActionArea(areaXML, areaAction)
        return areaXML

    def createXML(self, areas, rootNode, docObj):
        """Создаем из массивов областей XML"""
        for i in areas:
            if isinstance(i, PlasmaArea):
                if i.header:
                    areaXML = self.createCaptionTerm(i.header, i.start,
                                                     i.end.replace("\n", ""),
                                                     docObj)
                for f in i.fields:
                    if isinstance(f, PlasmaArea):
                        if f.header:
                            areaXMLChild = self.createCaptionTerm(f.header,
                                                                  f.start,
                                                                  f.end.replace(
                                                                      "\n", ""),
                                                                  docObj)

                            self.createXML(f.fields, areaXMLChild, docObj)

                            areaXML.append(areaXMLChild)
                        else:
                            self.createXML(f.fields, areaXML, docObj)
                        if "\n" in f.end:
                            fieldXMLBr = docObj.createField("br", [],
                                                            "", [],
                                                            False, False)
                            areaXML.append(fieldXMLBr)
                    else:
                        if not f:
                            continue
                        fields = self.splitToFields(f)
                        for field in fields:
                            if field.name is not False:
                                fieldXML = self.createFieldTerm(
                                    field.name, field.value, field.br, docObj)
                                areaXML.append(fieldXML)
                                if field.br[-1] == "\n":
                                    fieldXMLBr = docObj.createField(
                                        "br", [], "", [], False, False)
                                    areaXML.append(fieldXMLBr)
                            elif field.comment is not False:
                                fieldXML = docObj.createField(
                                    "comment", [field.comment], "", [],
                                    False, False)
                                areaXML.append(fieldXML)
                            elif field.br is not False:
                                brText = field.br.replace("\n", "")
                                if brText:
                                    fieldXML = docObj.createField(
                                        'br', [brText], "", [], False, False)
                                else:
                                    fieldXML = docObj.createField(
                                        'br', [], "", [], False, False)
                                if areaXML is not None and hasattr(areaXML, "append"):
                                    areaXML.append(fieldXML)

                if i.header:
                    rootNode.append(areaXML)
                if "\n" in i.end:
                    fieldXMLBr = docObj.createField("br", [],
                                                    "", [],
                                                    False, False)
                    rootNode.append(fieldXMLBr)

            else:
                if not i:
                    continue
                fields = self.splitToFields(i)
                for field in fields:
                    if field.name is not False:
                        fieldXML = self.createFieldTerm(field.name,
                                                        field.value,
                                                        field.br, docObj)
                        rootNode.append(fieldXML)
                        if field.br[-1] == "\n":
                            fieldXMLBr = docObj.createField("br", [], "", [],
                                                            False, False)
                            rootNode.append(fieldXMLBr)
                    elif field.comment is not False:
                        fieldXML = docObj.createField(
                            "comment", [field.comment], "", [], False, False)
                        rootNode.append(fieldXML)
                    elif field.br is not False:
                        brText = field.br.replace("\n", "")
                        if brText:
                            fieldXML = docObj.createField(
                                'br', [brText], "", [], False, False)
                        else:
                            fieldXML = docObj.createField(
                                'br', [], "", [], False, False)
                        rootNode.append(fieldXML)
                        # rootNode.append(areaXML)

    def createTxtConfig(self, strHeader, dictVar):
        """Cоздает область с заголовком

        создает текст конфигурационного файла в формате samba из
        заголовка (строка) и словаря переменных
        """
        if not strHeader:
            return ""
        if type(strHeader) in (tuple, list):
            outTxt = "".join(("[" + x + "]" for x in strHeader))
            if not outTxt:
                return ""
            outTxt += "\n"
        else:
            outTxt = "[" + strHeader + "]\n"
        for key in dictVar.keys():
            outTxt += "%s=%s\n" % (key, dictVar[key])
        return outTxt

    def _textToXML(self):
        """Преобразуем текст в XML"""
        areas = []
        if self.text.strip():
            self.splitToAllArea(self.text, areas)
        # docObj = xmlDoc()
        # Создаем новый класс xmlDoc с измененным методом joinArea
        # Создаем экземпляр нового класса
        docObj = xmlDocPlasma()
        # Создание объекта документ c пустым разделителем между полями
        docObj.createDoc(self.configName, self.configVersion)
        if not areas:
            return docObj
        self.createXML(areas, docObj.getNodeBody(), docObj)
        return docObj

    def postXML(self):
        """Последующая постобработка XML"""
        # Для добавления перевода строки между областями если его нет
        def getQuotesArea(xmlArea):
            quotes = []
            xmlQuotes = xpath.Evaluate('child::caption/quote', xmlArea)
            for node in xmlQuotes:
                if firstChild(node) is not None:
                    quotes.append(firstChild(node).text)
            if len(quotes) == 0:
                quotes.append("")
                quotes.append("")
            elif len(quotes) == 1:
                quotes.append("")
            return quotes

        xmlAreas = xpath.Evaluate("descendant::area", self.docObj.body)
        for xmlArea in xmlAreas:
            # Перед пустой областью и после нее удаляем переводы строк
            if getQuotesArea(xmlArea) == ["", ""]:
                if (xmlArea.getprevious() is not None and
                            self.docObj.getTypeField(
                                xmlArea.getprevious()) == "br"):
                    parentNode = xmlArea.getprevious().getparent()
                    prev_prev_sbl = xmlArea.getprevious().getprevious()
                    if (prev_prev_sbl and
                                self.docObj.getTypeField(
                                    prev_prev_sbl) == "br"):
                        parentNode.remove(
                            xmlArea.getprevious().getprevious())
                    parentNode.remove(xmlArea.getprevious())
                if (xmlArea.getnext() is not None and
                            self.docObj.getTypeField(
                                xmlArea.getnext()) == "br"):
                    parentNode = xmlArea.getnext().getparent()
                    next_next_sbl = xmlArea.getnext().getnext()
                    if (next_next_sbl is not None and
                                self.docObj.getTypeField(
                                    next_next_sbl) == "br"):
                        parentNode.remove(xmlArea.getnext().getnext())
                    parentNode.remove(xmlArea.getnext())
                continue

            # Собираем поля в кучку
            xmlChildAreas = xpath.Evaluate("child::area", xmlArea)
            if xmlChildAreas:
                childNodes = self.docObj.getFieldsArea(xmlArea)
                firstChildArea = xmlChildAreas[0]

                if (firstChildArea.getprevious() and
                            self.docObj.getTypeField(
                                firstChildArea.getprevious()) == "br"):
                    prev_prev_sbl = (
                        firstChildArea.getprevious().getprevious())
                    if prev_prev_sbl:
                        if self.docObj.getTypeField(prev_prev_sbl) == "br":
                            firstChildArea = firstChildArea.getprevious()
                flagFoundArea = False
                it = 0
                lenChild = len(childNodes)
                for node in childNodes:
                    it += 1
                    if node.tag == "area":
                        flagFoundArea = True
                        continue
                    if flagFoundArea and node.tag == "field":
                        if self.docObj.getTypeField(node) == "var":
                            insertBefore(xmlArea, node, firstChildArea)
                            if it < lenChild:
                                node_type = self.docObj.getTypeField(
                                    childNodes[it])
                                if node_type == "br":
                                    insertBefore(xmlArea, childNodes[it],
                                                         firstChildArea)
                    # Добавление перевода строк в если его нет между полями
                    if (self.docObj.getTypeField(node) == "var" and
                            node.getprevious() and
                            not (self.docObj.getTypeField(
                                node.getprevious()) in ("br", "comment"))):
                        insertBefore(xmlArea, self.docObj.createField(
                            "br", [], "", [], False, False), node)

            # Добавляем BR если его нет первым полем
            xmlFields = xpath.Evaluate("child::field", xmlArea)
            if not (xmlFields and
                        (self.docObj.getTypeField(xmlFields[0]) == "br" or
                                 self.docObj.getTypeField(
                                     xmlFields[0]) == "comment")):
                if xmlFields:
                    insertBefore(xmlArea, self.docObj.createField("br",
                                                                 [], "", [],
                                                                 False, False),
                                         xmlFields[0])
            # Добавление переводов строк между полями
            if xmlFields:
                for node in xmlFields:
                    # Добавление перевода строк в если его нет между полями
                    if (self.docObj.getTypeField(node) == "var" and
                            node.getprevious() is not None and
                            not (self.docObj.getTypeField(
                                node.getprevious()) in ("br", "comment"))):
                        insertBefore(xmlArea, self.docObj.createField("br",
                                                                     [], "", [],
                                                                     False,
                                                                     False),
                                             node)
            # Если последним полем BR, удаляем его
            if xmlFields and self.docObj.getTypeField(xmlFields[-1]) == "br":
                if not xmlFields[-1].getnext():
                    xmlArea.remove(xmlFields[-1])

            # Если предыдущим полем  не (BR или комментарий) - добавляем BR
            if (xmlArea.getprevious() is not None and
                    not (self.docObj.getTypeField(
                        xmlArea.getprevious()) == "br" or
                                 self.docObj.getTypeField(
                                     xmlArea.getprevious()) == "comment")):
                parentNode = xmlArea.getparent()
                insertBefore(parentNode, self.docObj.createField(
                    "br", [], "", [], False, False), xmlArea)
            # Если есть предыдущее поле, и поле предыдущеее предыдущему
            # не равно BR или комментарий то добавляем BR
            if xmlArea.getprevious() is not None:
                prPrSibling = xmlArea.getprevious().getprevious()
                if (prPrSibling is not None and
                        not (self.docObj.getTypeField(
                            prPrSibling) == "br" or
                                     self.docObj.getTypeField(
                                         prPrSibling) == "comment")):
                    parentNode = xmlArea.getparent()
                    insertBefore(parentNode, self.docObj.createField(
                        "br", [], "", [], False, False), xmlArea)
            # Если после есть BR а за ним ничего нет, удаляем BR
            if (xmlArea.getnext() is not None and
                        self.docObj.getTypeField(xmlArea.getnext()) == "br"):
                if xmlArea.getnext().getnext() is None:
                    parentNode = xmlArea.getnext().getparent()
                    parentNode.remove(xmlArea.getnext())

    def join(self, kdeObj):
        """Объединяем конфигурации"""
        if isinstance(kdeObj, plasma):
            self.docObj.joinDoc(kdeObj.doc)
            self.postXML()
