# -*- 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 calculate.lib.cl_xml import xpath
import xml.dom.minidom
from calculate.lib.cl_template import TemplateFormat
from calculate.lib.cl_lang import setLocalTranslate
from collections import Iterable

_ = 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 __init__(self, text):
        self.text = text
        self.prepare()

    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 not self.bodyNode:
            return False
        self.bodyNode.setAttribute("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 = xml.dom.minidom.parseString(self.text)
        except Exception:
            self.setError(_("The template content is not XML"))
            return False
        self.rootNode = self.doc.documentElement
        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 = xmlNode.childNodes
        if xmlNode.nodeType == xmlNode.ELEMENT_NODE:
            if xmlNode.hasAttribute("action"):
                nAction = xmlNode.getAttribute("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.parentNode
                    if parentNode:
                        parentNode.removeChild(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 = xmlNode.childNodes
        nextOldNode = xmlOldNode
        if xmlNode.nodeType == xmlNode.ELEMENT_NODE:
            n = xmlNode
            nType = u''
            nValue = u''
            nAction = u''
            attrName = ''
            attrType = ''
            path = n.tagName
            if n.hasAttribute("name"):
                nName = n.getAttribute("name")
                attrName = u"attribute::name='%s'" % nName
            if n.hasAttribute("type"):
                nType = n.getAttribute("type")
                attrType = u"attribute::type='%s'" % nType
            if n.hasAttribute("value"):
                nValue = n.getAttribute("value")
            if n.hasAttribute("action"):
                nAction = n.getAttribute("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 xmlOldNode.parentNode:
                strAttr = [attrName, attrType]
                findAttr = filter(lambda x: x, strAttr)
                findAttrStr = ''
                if findAttr:
                    strAttr = u' and '.join(findAttr)
                    findAttrStr = "[%s]" % strAttr
                findPath = u"child::%s%s" % (path, findAttrStr)
                # Рабочая нода
                if flagRootNode:
                    workNode = xmlOldNode.parentNode
                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 = xmlNode.cloneNode(True)
                        if nAction:
                            replaceXmlNode.removeAttribute("action")
                        workNode.replaceChild(replaceXmlNode,
                                              nextOldNode)
                        flagJoin = False
                        flagReplace = False
                        childNodes = False
                    # Объединение нод
                    if flagJoin:
                        if nextOldNode.hasAttribute("value"):
                            oValue = nextOldNode.getAttribute("value")
                            if nValue != oValue:
                                nextOldNode.setAttribute("value", nValue)
                    # Замещение ноды
                    elif flagReplace:
                        replaceXmlNode = xmlNode.cloneNode(True)
                        if not self._removeDropNodesAndAttrAction(
                                replaceXmlNode):
                            return False
                        workNode.replaceChild(replaceXmlNode,
                                              nextOldNode)
                        childNodes = False
                    # Удаление ноды
                    elif flagDrop:
                        workNode.removeChild(nextOldNode)
                        childNodes = False
                else:
                    # Добавление ноды
                    childNodes = False
                    if not flagDrop:
                        appendXmlNode = xmlNode.cloneNode(True)
                        if not self._removeDropNodesAndAttrAction(
                                appendXmlNode):
                            return False
                        workNode.appendChild(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 not self.doc:
            self.setError(_("The text file is not XML"))
            return False
        if not doc:
            self.setError(_("The text file is not XML"))
            return False
        # Импортируем корневую ноду нового документа в текущий документ
        # newImportBodyNode = self.doc.importNode(doc.documentElement, True)
        # Объединение корневой ноды шаблона и корневой ноды файла
        if not self._join(doc.documentElement, self.bodyNode):
            return False
        return True

    def getConfig(self):
        """Получение текстового файла из XML документа"""
        data = self.doc.toprettyxml(encoding='UTF-8').split("\n")
        data = filter(lambda x: x.strip(), data)
        return "\n".join(data).replace("\t", "  ").decode("UTF-8")
