# *************************************************************
#  
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you 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 globals import *
import srclexer

# simple name translation map
postTransMap = {"ok-button": "okbutton", 
                "cancel-button": "cancelbutton",
                "help-button": "helpbutton"}

def transName (name):
    """Translate a mixed-casing name to dash-separated name.

Translate a mixed-casing name (e.g. MyLongName) to a dash-separated name
(e.g. my-long-name).
"""
    def isUpper (c):
        return c >= 'A' and c <= 'Z'

    newname = ''
    parts = []
    buf = ''
    for c in name:
        if isUpper(c) and len(buf) > 1:
            parts.append(buf)
            buf = c
        else:
            buf += c

    if len(buf) > 0:
        parts.append(buf)

    first = True
    for part in parts:
        if first:
            first = False
        else:
            newname += '-'
        newname += part.lower()

    # special-case mapping ...
    if 0: #postTransMap.has_key(newname):
        newname = postTransMap[newname]

    return newname


def transValue (value):
    """Translate certain values.

Examples of translated values include TRUE -> true, FALSE -> false.
"""
    if value.lower() in ["true", "false"]:
        value = value.lower()
    return value


def renameAttribute (name, elemName):

    # TODO: all manner of evil special cases ...
    if elemName == 'metric-field' and name == 'spin-size':
        return 'step-size'

    return name


class Statement(object):
    """Container to hold information for a single statement.

Each statement consists of the left-hand-side token(s), and right-hand-side
tokens, separated by a '=' token.  This class stores the information on the
left-hand-side tokens.
"""
    def __init__ (self):
        self.leftTokens = []
        self.leftScope = None


class MacroExpander(object):
    def __init__ (self, tokens, defines):
        self.tokens = tokens
        self.defines = defines

    def expand (self):
        self.pos = 0
        while self.pos < len(self.tokens):
            self.expandToken()

    def expandToken (self):
        token = self.tokens[self.pos]
        if not self.defines.has_key(token):
            self.pos += 1
            return

        macro = self.defines[token]
        nvars = len(macro.vars.keys())
        if nvars == 0:
            # Simple expansion
            self.tokens[self.pos:self.pos+1] = macro.tokens
            return
        else:
            # Expansion with arguments.
            values, lastPos = self.parseValues()
            newtokens = []
            for mtoken in macro.tokens:
                if macro.vars.has_key(mtoken):
                    # variable
                    pos = macro.vars[mtoken]
                    valtokens = values[pos]
                    for valtoken in valtokens:
                        newtokens.append(valtoken)
                else:
                    # not a variable
                    newtokens.append(mtoken)

            self.tokens[self.pos:self.pos+lastPos+1] = newtokens


    def parseValues (self):
        """Parse tokens to get macro function variable values.

Be aware that there is an implicit quotes around the text between the open 
paren, the comma(s), and the close paren.  For instance, if a macro is defined 
as FOO(a, b) and is used as FOO(one two three, and four), then the 'a' must be 
replaced with 'one two three', and the 'b' replaced with 'and four'.  In other 
words, whitespace does not end a token.  

"""
        values = []
        i = 1
        scope = 0
        value = []
        while True:
            try:
                tk = self.tokens[self.pos+i]
            except IndexError:
                progress ("error parsing values (%d)\n"%i)
                for j in xrange(0, i):
                    print self.tokens[self.pos+j],
                print ''
                srclexer.dumpTokens(self.tokens)
                srclexer.dumpTokens(self.newtokens)
                print "tokens expanded so far:"
                for tk in self.expandedTokens:
                    print "-"*20
                    print tk
                    srclexer.dumpTokens(self.defines[tk].tokens)
                sys.exit(1)
            if tk == '(':
                value = []
                scope += 1
            elif tk == ',':
                values.append(value)
                value = []
            elif tk == ')':
                scope -= 1
                values.append(value)
                value = []
                if scope == 0:
                    break
                else:
                    raise ParseError ('')
            else:
                value.append(tk)
            i += 1

        return values, i

    def getTokens (self):
        return self.tokens


class SrcParser(object):

    def __init__ (self, tokens, defines = None):
        self.tokens = tokens
        self.defines = defines
        self.debug = False
        self.onlyExpandMacros = False

    def init (self):
        self.elementStack = [RootNode()]
        self.stmtData = Statement()
        self.tokenBuf = []
        self.leftTokens = []

        # Expand defined macros.
        if self.debug:
            progress ("-"*68+"\n")
            for key in self.defines.keys():
                progress ("define: %s\n"%key)

        self.expandMacro()
        self.tokenSize = len(self.tokens)

    def expandMacro (self):
        macroExp = MacroExpander(self.tokens, self.defines)
        macroExp.expand()
        self.tokens = macroExp.getTokens()
        if self.onlyExpandMacros:
            srclexer.dumpTokens(self.tokens)
            sys.exit(0)

    def parse (self):
        """Parse it!

This is the main loop for the parser.  This is where it all begins and ends.
"""
        self.init()

        i = 0
        while i < self.tokenSize:
            tk = self.tokens[i]
            if tk == '{':
                i = self.openBrace(i)
            elif tk == '}':
                i = self.closeBrace(i)
            elif tk == ';':
                i = self.semiColon(i)
            elif tk == '=':
                i = self.assignment(i)
            else:
                self.tokenBuf.append(tk)

            i += 1

        return self.elementStack[0]

    #-------------------------------------------------------------------------
    # Token Handlers

    """
Each token handler takes the current token position and returns the position 
of the last token processed.  For the most part, the current token position 
and the last processed token are one and the same, in which case the handler 
can simply return the position value it receives without incrementing it.  

If you need to read ahead to process more tokens than just the current token, 
make sure that the new token position points to the last token that has been 
processed, not the next token that has not yet been processed.  This is 
because the main loop increments the token position when it returns from the 
handler.
""" 

    # assignment token '='
    def assignment (self, i):
        self.leftTokens = self.tokenBuf[:]
        if self.stmtData.leftScope == None:
            # Keep track of lhs data in case of compound statement.
            self.stmtData.leftTokens = self.tokenBuf[:]
            self.stmtData.leftScope = len(self.elementStack) - 1

        self.tokenBuf = []
        return i
    
    # open brace token '{'
    def openBrace (self, i):
        bufSize = len(self.tokenBuf)
        leftSize = len(self.leftTokens)
        obj = None
        if bufSize == 0 and leftSize > 0:
            # Name = { ...
            obj = Element(self.leftTokens[0])

        elif bufSize > 0 and leftSize == 0:
            # Type Name { ...
            wgtType = self.tokenBuf[0]
            wgtRID = None
            if bufSize >= 2:
                wgtRID = self.tokenBuf[1]
            obj = Element(wgtType, wgtRID)

        else:
            # LeftName = Name { ...
            obj = Element(self.leftTokens[0])
            obj.setAttr("type", self.tokenBuf[0])

        obj.name = transName(obj.name)

        if obj.name == 'string-list':
            i = self.parseStringList(i)
        elif obj.name == 'filter-list':
            i = self.parseFilterList(i, obj)
        else:
            self.elementStack[-1].appendChild(obj)
            self.elementStack.append(obj)

        self.tokenBuf = []
        self.leftTokens = []

        return i

    # close brace token '}'
    def closeBrace (self, i):
        if len(self.tokenBuf) > 0:
            if self.debug:
                print self.tokenBuf
            raise ParseError ('')
        self.elementStack.pop()
        return i

    # semi colon token ';'
    def semiColon (self, i):
        stackSize = len(self.elementStack)
        scope = stackSize - 1
        if len(self.tokenBuf) == 0:
            pass
        elif scope == 0:
            # We are not supposed to have any statment in global scope.  
            # Just ignore this statement.
            pass
        else:
            # Statement within a scope.  Import it as an attribute for the 
            # current element.
            elem = self.elementStack[-1]

            name = "none"
            if len(self.leftTokens) > 0:
                # Use the leftmost token as the name for now.  If we need to
                # do more complex parsing of lhs, add more code here.
                name = self.leftTokens[0]
                name = transName(name)

            if name == 'pos':
                i = self.parsePosAttr(i)
            elif name == 'size':
                i = self.parseSizeAttr(i)
            elif len (self.tokenBuf) == 1:
                # Simple value
                value = transValue(self.tokenBuf[0])
                name = renameAttribute(name, elem.name)
                elem.setAttr(name, value)

            if not self.stmtData.leftScope == None and self.stmtData.leftScope < scope:
                # This is a nested scope within a statement.  Do nothing for now.
                pass

        if self.stmtData.leftScope == scope:
            # end of (nested) statement.
            self.stmtData.leftScope = None

        self.tokenBuf = []
        self.leftTokens = []

        return i

    def parseStringList (self, i):

        i += 1
        while i < self.tokenSize:
            tk = self.tokens[i]
            if tk == '}':
                break
            i += 1

        return i

    def parseFilterList (self, i, obj):
        self.elementStack[-1].appendChild(obj)
        self.elementStack.append(obj)

        return i

    def parsePosAttr (self, i):

        # MAP_APPFONT ( 6 , 5 )
        elem = self.elementStack[-1]
        x, y = self.parseMapAppfont(self.tokenBuf)
        elem.setAttr("x", x)
        elem.setAttr("y", y)

        return i

    def parseSizeAttr (self, i):

        # MAP_APPFONT ( 6 , 5 )
        elem = self.elementStack[-1]
        width, height = self.parseMapAppfont(self.tokenBuf)
        elem.setAttr("width", width)
        elem.setAttr("height", height)

        return i

    def parseMapAppfont (self, tokens):
        values = []
        scope = 0
        val = ''
        for tk in tokens:
            if tk == '(':
                scope += 1
                if scope == 1:
                    val = ''
                else:
                    val += tk
            elif tk == ')':
                scope -= 1
                if scope == 0:
                    if len(val) == 0:
                        raise ParseError ('')
                    values.append(val)
                    break
                else:
                    val += tk
            elif tk == ',':
                if len(val) == 0:
                    raise ParseError ('')
                values.append(val)
                val = ''
            elif scope > 0:
                val += tk

        if len(values) != 2:
            raise ParseError ('')

        return eval(values[0]), eval(values[1])


