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

# Copyright 2008-2012 Calculate Ltd. 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 xml import xpath
import xml.dom.minidom
from calculate.lib.format.xml_xfce import xml_xfce
from calculate.lib.cl_lang import setLocalTranslate
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 __init__(self, text):
        self.text = text
        # Создаем пустой объект
        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 = xml.dom.minidom.parseString(self.text)
        except:
            self.setError(_("The template content is not XML"))
            return False
        self.rootNode = self.doc.documentElement
        self.bodyNode = self.rootNode
        return self.doc

    def cmpListsNodesEntry(self, listXmlA, listXmlB):
        """Сравнение содержимого двух списков XML нод"""
        getTextsNodes = lambda y: map(lambda x:\
            x.toxml().replace(" ","").replace("\t","").replace("\n",""),
                    map(lambda x: x.hasAttribute("mtime") and\
                        x.removeAttribute("mtime") or x,
                        map(lambda x: x.cloneNode(True),
                            filter(lambda x: x.nodeType==x.ELEMENT_NODE, y))))
        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 = xmlNode.childNodes
        nextOldNode = xmlOldNode
        flagError = False
        if xmlNode.nodeType == xmlNode.ELEMENT_NODE:
            n = xmlNode
            tagName = n.tagName
            nAction = u''
            nName = u''
            nType = u''
            nValue = u''
            nSchema = u''
            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 not n.hasAttribute("name"):
                    self.setError(
                        _("Attribute \"name\" not found in the tag entry"))
                    return False
                flagType = n.hasAttribute("type")
                flagValue = False
                flagSchema = n.hasAttribute("schema")
                if flagSchema:
                    nSchema = n.getAttribute("schema")
                if not flagType and not flagSchema:
                    self.setError(
                        _("Attribute \"type\" not found in the tag entry"))
                    return False
                nName = n.getAttribute("name")
                attrName = u"attribute::name='%s'"%nName
                if flagType:
                    flagValue = n.hasAttribute("value")
                    nType = n.getAttribute("type")
                    # Проверка правильности аттрибута type
                    if not nType in self.supportEntryTypes:
                        self.setError(\
                        _("Incorrect \"type\" attribute : <entry type=\"%s\"/>")\
                          %nType)
                        return False
                    if flagValue:
                        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:
                findAttrStr = ""
                if attrName:
                    findAttrStr = "[%s]"%attrName
                findPath = u"child::%s%s"%(tagName,findAttrStr)
                # Рабочая нода
                if flagRootNode:
                    workNode = xmlOldNode.parentNode
                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
                            foundValue = nextOldNode.hasAttribute("value")
                            if foundValue:
                                oValue = nextOldNode.getAttribute("value")
                                if nValue != oValue:
                                    flagChange = True
                            else:
                                flagChange = True
                            if flagChange:
                                nextOldNode.setAttribute("mtime",
                                                         self.currentTime)
                                nextOldNode.setAttribute("value",nValue)
                        elif flagSchema:
                            flagChange = False
                            foundValue = nextOldNode.hasAttribute("schema")
                            if foundValue:
                                oSchema = nextOldNode.getAttribute("schema")
                                if nSchema != oSchema:
                                    flagChange = True
                            else:
                                flagChange = True
                            if flagChange:
                                nextOldNode.setAttribute("mtime",
                                                         self.currentTime)
                                nextOldNode.setAttribute("schema",nSchema)
                    # Замещение ноды
                    elif flagReplace:
                        replaceXmlNode = xmlNode.cloneNode(True)
                        # Сравнение содержимого нод
                        if not self.cmpListsNodesEntry([replaceXmlNode],
                                                       [nextOldNode]):
                            replaceXmlNode.setAttribute("mtime",
                                                        self.currentTime)
                            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)
                        appendXmlNode.setAttribute("mtime", self.currentTime)
                        if not\
                            self._removeDropNodesAndAttrAction(appendXmlNode):
                                return False
                        workNode.appendChild(appendXmlNode)
            if childNodes:
                for node in childNodes:
                    levelNumber +=1
                    if not self._join(node, nextOldNode, False, levelNumber):
                        flagError = True
                        break
                    levelNumber -= 1
        if flagError:
            return False
        return True

    def join(self, xml_gconfObj):
        """Объединяем конфигурации"""
        # Получаем текущее время
        self.currentTime = self.getCurrentTime()
        if isinstance(xml_gconfObj, xml_gconf):
            try:
                self.joinDoc(xml_gconfObj.doc)
            except:
                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)
                return res.group(1).replace("\t","  ") + res.group(2)
            else:
                return s
        data = self.doc.toprettyxml().split("\n")
        data = map(lambda x: expandStartTabs(x),
                filter(lambda x: x.strip(), data))
        dataOut = []
        z = 0
        lenData = len(data)
        lenM2 = lenData - 2
        for i in xrange(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])
        return "\n".join(dataOut)
