# -*- 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 sys
from ..cl_xml import xpath, xml_to_str, str_to_xml_doc
from ..cl_template import TemplateFormat
from ..cl_lang import setLocalTranslate
from collections.abc import Iterable
from copy import deepcopy
_ = lambda x: x
setLocalTranslate('cl_lib3', sys.modules[__name__])


class xml_xfce(TemplateFormat):
    """Класс для объединения xfce-xml файлов"""
    # root нода
    rootNode = False
    # body нода
    bodyNode = False
    # Документ
    doc = False
    # Текст шаблона
    text = ""
    # Комментарий
    _comment = ("<!--", "-->")

    def prepare(self):
        # Создаем пустой объект
        self.docObj = type("_empty_class", (object,), {})()
        # Названия аттрибутов для пустого объекта
        emptyMethods = ["getNodeBody", "removeComment", "insertBRtoBody",
                        "insertBeforeSepAreas"]
        # Добавляем необходимые аттрибуты пустому объекту
        for method in emptyMethods:
            setattr(self.docObj, method, self.emptyMethod)
        # Создаем XML документ
        self.doc = self.textToXML()

    def emptyMethod(self, *arg, **argv):
        """Пустой метод"""
        return True

    def setNameBodyNode(self, name):
        """Устанавливает название для корневой ноды документа"""
        if self.bodyNode is None:
            return False
        self.bodyNode.set("name", name)
        return True

    def textToXML(self):
        """Создание из текста XML документа
        Храним xml в своем формате
        """
        if not self.text.strip():
            self.text = '''<?xml version="1.0" encoding="UTF-8"?>
<channel version="1.0">
</channel>'''
        try:
            self.doc = str_to_xml_doc(self.text)
        except Exception as e:
            print(e)
            self.setError(_("The template content is not XML"))
            return False
        self.rootNode = self.doc.getroottree().getroot()
        self.bodyNode = self.rootNode
        return self.doc

    def join(self, xml_xfceObj):
        """Объединяем конфигурации"""
        if isinstance(xml_xfceObj, xml_xfce):
            try:
                self.joinDoc(xml_xfceObj.doc)
            except Exception:
                self.setError(_("Failed to join the template"))
            return False
        return True

    def _removeDropNodesAndAttrAction(self, xmlNode):
        """Удаляет ноды с аттрибутом action='drop'

        Также удаляет аттрибут action у всех нод
        """
        childNodes = list(xmlNode)
        if "action" in xmlNode.keys():
            nAction = xmlNode.get("action")
            if nAction not in ("join", "replace", "drop"):
                textError = _("In the text of the XML template, "
                                "reserved attribute 'action' comes with an "
                                "incorrect value.\n"
                                "Valid values of the 'action' attribute are: "
                                '(action="join", action="replace",'
                                'action="drop")')
                self.setError(textError)
                return False
            xmlNode.removeAttribute("action")
            if nAction == "drop":
                parentNode = xmlNode.getparent()
                if parentNode:
                    parentNode.remove(xmlNode)
        if childNodes:
            for node in childNodes:
                if not self._removeDropNodesAndAttrAction(node):
                    return False
        return True

    def postXML(self):
        """Последующая постобработка XML"""
        # Удаляем теги action и удаляемые ноды
        self._removeDropNodesAndAttrAction(self.bodyNode)

    def _join(self, xmlNewNode, xmlOldNode, flagRootNode=True):
        """Объединение корневой ноды шаблона и корневой ноды файла"""
        xmlNode = xmlNewNode
        childNodes = list(xmlNode)
        nextOldNode = xmlOldNode
        # if xmlNode.nodeType == xmlNode.ELEMENT_NODE:
        n = xmlNode
        nType = ''
        nValue = ''
        nAction = ''
        attrName = ''
        attrType = ''
        path = n.tag
        if "name" in n.keys():
            nName = n.get("name")
            attrName = "attribute::name='%s'" % nName
        if "type" in n.keys():
            nType = n.get("type")
            attrType = "attribute::type='%s'" % nType
        if "value" in n.keys():
            nValue = n.get("value")
        if "action" in n.keys():
            nAction = n.get("action")
            if not nAction in ("join", "replace", "drop"):
                textError = _(
                    "In the text of the XML template, "
                    "reserved attribute 'action' comes with an "
                    "incorrect value.\n"
                    "Valid values of the 'action' attribute are: "
                    '(action="join", action="replace", action="drop")')
                self.setError(textError)
                return False
        if xmlNewNode.getparent() is not None:
            strAttr = [attrName, attrType]
            findAttr = [x for x in strAttr if x]
            findAttrStr = ''
            if findAttr:
                strAttr = ' and '.join(findAttr)
                findAttrStr = "[%s]" % strAttr
            findPath = "child::%s%s" % (path, findAttrStr)
            # Рабочая нода
            if flagRootNode:
                workNode = xmlOldNode.getparent()
            else:
                workNode = xmlOldNode
            oldNodes = xpath.Evaluate(findPath, workNode)
            # Новая нода список
            flagArray = nType == "array"
            flagDrop = False
            flagJoin = True
            flagReplace = False
            if nAction == "replace":
                flagJoin = False
                flagReplace = True
            elif nAction == "drop":
                flagJoin = False
                flagDrop = True
                if flagRootNode:
                    textError = _('Incorrect action="drop" in '
                                    'the root node')
                    self.setError(textError)
                    return False
            if oldNodes:
                if len(oldNodes) > 1:
                    textError = _("Ambiguity in this template: "
                                    "the same nodes are on a same level")
                    self.setError(textError)
                    return False
                nextOldNode = oldNodes[0]
                # Замещаем ноду в случае массива
                if flagArray and not flagDrop:
                    replaceXmlNode = deepcopy(xmlNode)
                    if nAction:
                        del replaceXmlNode.attrib["action"]
                    workNode.replace(nextOldNode, replaceXmlNode)
                    flagJoin = False
                    flagReplace = False
                    childNodes = False
                # Объединение нод
                if flagJoin:
                    if "value" in nextOldNode.keys():
                        oValue = nextOldNode.getAttribute("value")
                        if nValue != oValue:
                            nextOldNode.setAttribute("value", nValue)
                # Замещение ноды
                elif flagReplace:
                    replaceXmlNode = deepcopy(xmlNode)
                    if not self._removeDropNodesAndAttrAction(
                            replaceXmlNode):
                        return False
                    workNode.replace(nextOldNode, replaceXmlNode)
                    childNodes = False
                # Удаление ноды
                elif flagDrop:
                    workNode.remove(nextOldNode)
                    childNodes = False
            else:
                # Добавление ноды
                childNodes = False
                if not flagDrop:
                    appendXmlNode = deepcopy(xmlNode)
                    if not self._removeDropNodesAndAttrAction(
                            appendXmlNode):
                        return False
                    workNode.append(appendXmlNode)
        if isinstance(childNodes, Iterable):
            for node in childNodes:
                if not self._join(node, nextOldNode, False):
                    return False
        return True

    def joinDoc(self, doc):
        """Объединение документа шаблона и документа файла"""
        if self.doc is None:
            self.setError(_("The text file is not XML"))
            return False
        if doc is None:
            self.setError(_("The text file is not XML"))
            return False
        # Импортируем корневую ноду нового документа в текущий документ
        # newImportBodyNode = self.doc.importNode(doc.documentElement, True)
        # Объединение корневой ноды шаблона и корневой ноды файла
        if not self._join(doc, self.bodyNode):
            return False
        return True

    def getConfig(self):
        """Получение текстового файла из XML документа"""
        data = xml_to_str(self.doc).split("\n")
        data = [x for x in data if x.strip()]
        data.insert(0, '<?xml version="1.0" encoding="UTF-8"?>\n')
        return "\n".join(data).replace("\t", "  ")
