# -*- 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
import time
import re
from ..cl_xml import xpath, xml_to_str, str_to_xml_doc
# import xml.dom.minidom
from lxml import etree
from .xml_xfce import xml_xfce
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_gconf(xml_xfce):
    """Класс для объединения gconf-xml файлов"""
    # root нода
    rootNode = False
    # body нода
    bodyNode = False
    # Документ
    doc = False
    # Текст шаблона
    text = ""
    # Текущее время в секундах 
    currentTime = ""
    # Комментарий
    _comment = ("<!--", "-->")
    # поддерживаемые аттрибуты тега entry. Пример <entry type="int"/>
    supportEntryTypes = ("int", "bool", "float", "string", "list", "pair")
    # регулярное выражения для поиска \t в начале строки (преобразование xml)
    reStartTabs = re.compile("^(\t+)(.*)$")

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

    def getCurrentTime(self):
        """Получение текущего времени в секундах"""
        return str(int(time.time()))

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

    def cmpListsNodesEntry(self, listXmlA, listXmlB):
        """Сравнение содержимого двух списков XML нод"""
        def getTextsNodes(listXml):
            for node in (deepcopy(x)
                         for x in listXml):# if x.nodeType == x.ELEMENT_NODE):
                if "mtime" in node.keys():
                    # node.removeAttribute("mtime")
                    del node.attrib["mtime"]
                text = node.toxml()
                yield text.replace(" ", "").replace("\t", "").replace("\n", "")
        if set(getTextsNodes(listXmlA)) == set(getTextsNodes(listXmlB)):
            return True
        return False

    def _join(self, xmlNewNode, xmlOldNode, flagRootNode=True, levelNumber=0):
        """Объединение корневой ноды шаблона и корневой ноды файла"""
        if levelNumber > 1:
            return True
        xmlNode = xmlNewNode
        childNodes = list(xmlNode)
        nextOldNode = xmlOldNode
    # if xmlNode.nodeType == xmlNode.ELEMENT_NODE:
        n = xmlNode
        tagName = n.tag
        nAction = ''
        nType = ''
        nValue = ''
        nSchema = ''
        attrName = ''
        if flagRootNode:
            if not tagName == "gconf":
                self.setError(_("The text is not a valid gconf-XML format \
(not found '<gconf>...</gconf>')"))
                return False
            flagType = False
            flagValue = False
            flagSchema = False
        else:
            if not tagName == "entry":
                self.setError(_("The text is not a valid gconf-XML format \
(found '<gconf><%(tag)s>..</%(tag)s></gconf>')") % {'tag': tagName})
                return False
            if "name" not in n.keys():
                self.setError(
                    _("Attribute \"name\" not found in the tag entry"))
                return False
            flagType = "type" in n.keys()
            flagValue = False
            flagSchema = "schema" in n.keys()
            if flagSchema:
                nSchema = n.get("schema")
            if not flagType and not flagSchema:
                self.setError(
                    _("Attribute \"type\" not found in the tag entry"))
                return False
            nName = n.get("name")
            attrName = "attribute::name='%s'" % nName
            if flagType:
                flagValue = "value" in n.keys()
                nType = n.get("type")
                # Проверка правильности аттрибута type
                if nType not in self.supportEntryTypes:
                    self.setError(_("Incorrect \"type\" attribute "
                                    ": <entry type=\"%s\"/>") % nType)
                    return False
                if flagValue:
                    nValue = n.get("value")
        if "action" in n.keys():
            nAction = n.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
        if xmlNewNode.getparent():
            findAttrStr = ""
            if attrName:
                findAttrStr = "[%s]" % attrName
            findPath = "child::%s%s" % (tagName, findAttrStr)
            # Рабочая нода
            if flagRootNode:
                workNode = xmlOldNode.getparent()
            else:
                workNode = xmlOldNode
            oldNodes = xpath.Evaluate(findPath, workNode)
            # По умолчанию - объединение
            flagJoin = True
            flagReplace = False
            flagDrop = False
            # Замещаем ноду
            if nType == "string" or nAction == "replace":
                flagJoin = False
                flagReplace = True
            # Замещаем ноду в случае массива
            elif nType == "list" or nType == "pair":
                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 flagJoin:
                    if flagType and flagValue:
                        flagChange = False
                        if "value" in nextOldNode.keys():
                            oValue = nextOldNode.get("value")
                            if nValue != oValue:
                                flagChange = True
                        else:
                            flagChange = True
                        if flagChange:
                            nextOldNode.set("mtime", self.currentTime)
                            nextOldNode.set("value", nValue)
                    elif flagSchema:
                        flagChange = False
                        if "schema" in nextOldNode.keys():
                            oSchema = nextOldNode.get("schema")
                            if nSchema != oSchema:
                                flagChange = True
                        else:
                            flagChange = True
                        if flagChange:
                            nextOldNode.set("mtime", self.currentTime)
                            nextOldNode.set("schema", nSchema)
                # Замещение ноды
                elif flagReplace:
                    replaceXmlNode = deepcopy(xmlNode)
                    # Сравнение содержимого нод
                    if not self.cmpListsNodesEntry([replaceXmlNode],
                                                    [nextOldNode]):
                        replaceXmlNode.set("mtime",
                                                    self.currentTime)
                        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)
                    appendXmlNode.set("mtime", self.currentTime)
                    if not self._removeDropNodesAndAttrAction(
                            appendXmlNode):
                        return False
                    workNode.append(appendXmlNode)
        if isinstance(childNodes, Iterable):
            for node in childNodes:
                levelNumber += 1
                if not self._join(node, nextOldNode, False, levelNumber):
                    return False
                levelNumber -= 1
        return True

    def join(self, xml_gconfObj):
        """Объединяем конфигурации"""
        # Получаем текущее время
        self.currentTime = self.getCurrentTime()
        if isinstance(xml_gconfObj, xml_gconf):
            try:
                self.joinDoc(xml_gconfObj.doc)
            except Exception:
                self.setError(_("Failed to join the template"))
                return False
        return True

    def getConfig(self):
        """Получение текстового файла из XML документа"""

        def expandStartTabs(s):
            if s.startswith("\t"):
                res = self.reStartTabs.search(s)
                if res:
                    return str(res.group(1)).replace("\t", "  ") + res.group(2)
                return s
            else:
                return s

        data = xml_to_str(self.doc).split("\n")
        data = [expandStartTabs(x) for x in data if x.strip()]
        dataOut = []
        z = 0
        lenData = len(data)
        lenM2 = lenData - 2
        for i in range(lenData):
            if z > 0:
                z -= 1
                continue
            if i < lenM2 and data[i].endswith(">") and "<" not in data[i + 1]:
                dataOut.append(
                    data[i] + data[i + 1].strip() + data[i + 2].strip())
                z = 2
                continue
            dataOut.append(data[i])
        #TODO test this
        dataOut.insert(0, '<?xml version="1.0" encoding="UTF-8"?>\n')
        return "\n".join(dataOut)
