# SAX for parsing
from xml.sax import make_parser, handler, expatreader

# ElementTree for writing
import xml.etree.cElementTree as ET

import re, os

class Node(object):
    def __init__(self, name = '', index = 0, parent = None):
        self._parent = parent
        self._name = name
        self._value = None
        self._index = 0
        self._children = []
        
    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, v):
        self._value = v
    
    @property
    def name(self):
        return self._name
        
    @property
    def index(self):
        return self._index
        
    @property 
    def parent(self):
        return self._parent
        
    def getChild(self, n, i=None, create = False):
     
        if i is None:
            i = 0
            # parse name as foo[999] if necessary
            m = re.match(R"(\w+)\[(\d+)\]", n)
            if m is not None:
                n = m.group(1)
                i = int(m.group(2))
                
        for c in self._children:
            if (c.name == n) and (c.index == i):
                return c
                
        if create:
            c = Node(n, i, self)
            self._children.append(c)
            return c
        else:
            raise IndexError("no such child:" + str(n) + " index=" + str(i))
            
    def addChild(self, n):
        i = 0
        
        # find first free index
        usedIndices = frozenset(c.index for c in self.getChildren(n))
        while i < 1000:
            if i not in usedIndices:
                 break
            i += 1
        
        # create it via getChild
        return self.getChild(n, i, create=True)
            
    def hasChild(self, nm):
        for c in self._children:
            if (c.name == nm):
                return True
                
        return False
            
    def getChildren(self, n = None):
        if n is None:
            return self._children
        
        return [c for c in self._children if c.name == n]
            
    def getNode(self, path, cr = False):
        axes = path.split('/')
        nd = self
        for ax in axes:
            nd = nd.getChild(ax, create = cr)
            
        return nd
        
    def getValue(self, path, default = None):
        try:
            nd = self.getNode(path)
            return nd.value
        except:
            return default
            
    def write(self, path):
        root = self._createXMLElement('PropertyList')
        
        t = ET.ElementTree(root)
        t.write(path, 'utf-8')
        
    def _createXMLElement(self, nm = None):
        if nm is None:
            nm = self.name
            
        n = ET.Element(nm)
        
        # value and type specification 
        try:
            if self._value is not None:
                if isinstance(self._value, basestring):
                    # don't call str() on strings, breaks the
                    # encoding
                    n.text = self._value
                else:
                    # use str() to turn non-string types into text
                    n.text = str(self._value)
                    if isinstance(self._value, int):
                        n.set('type', 'int')
                    elif isinstance(self._value, float):
                        n.set('type', 'double')
                    elif isinstance(self._value, bool):
                        n.set('type', "bool")
        except UnicodeEncodeError:
            print "Encoding error with", self._value, type(self._value)
            
        # index in parent
        if (self.index != 0):
            n.set('n', self.index)
            
        # children
        for c in self._children:
            n.append(c._createXMLElement())
            
        return n;
        

class PropsHandler(handler.ContentHandler):
    def __init__(self, root = None, path = None, dataDirPath = None):
        self._root = root
        self._path = path
        self._basePath = os.path.dirname(path)
        self._dataDirPath = dataDirPath
        self._locator = None
        
        if root is None:
            # make a nameless root node
            self._root = Node("", 0)
        self._current = self._root
            
    def setDocumentLocator(self, loc):
        self._locator = loc    
        
    def startElement(self, name, attrs):    
        self._content = ''
        if (name == 'PropertyList'):
            return
            
        if 'n' in attrs.keys():
            index = int(attrs['n'])
            self._current = self._current.getChild(name, index, create=True)
        else:
            self._current = self._current.addChild(name)
            
            
        if 'include' in attrs.keys():
            self.handleInclude(attrs['include'])
            
        self._currentTy = None;
        if 'type' in attrs.keys():
            self._currentTy = attrs['type']
                     
    def handleInclude(self, includePath):
        if includePath.startswith('/'):
            includePath = includePath[1:]
   
        p = os.path.join(self._basePath, includePath)
        if not os.path.exists(p):
            p = os.path.join(self._dataDirPath, includePath)
            if not os.path.exists(p):
                raise RuntimeError("include file not found", includePath, "at line", self._locator.getLineNumber())
                
        readProps(p, self._current, self._dataDirPath)
            
    def endElement(self, name):
        if (name == 'PropertyList'):
            return
            
        try:
            # convert and store value
            self._current.value = self._content
            if self._currentTy == "int":
                self._current.value = int(self._content)
            if self._currentTy is "bool":
                self._current.value = bool(self._content)
            if self._currentTy is "double":
                self._current.value = float(self._content)
        except:
            print "Parse error for value:", self._content, "at line:", self._locator.getLineNumber(), "of:", self._path
            
        self._current = self._current.parent
            
    def characters(self, content):
        self._content += content
            
    def endDocument(self):
        pass
        
    @property
    def root(self):
        return self._root

    
def readProps(path, root = None, dataDirPath = None):
    parser = make_parser()
    locator = expatreader.ExpatLocator( parser )
    h = PropsHandler(root, path, dataDirPath)
    h.setDocumentLocator(locator)
    parser.setContentHandler(h)
    parser.parse(path)
    return h.root