# Copyright (c) 2004 Infrae. All rights reserved.
# See also LICENSE.txt

import sys
from pprint import pprint

import libxml2

import query

###


tag2op = { 'equal':query.Equal,
           'not-equal':query.NotEqual,
           'greater':query.Greater,
           'less':query.Less,
           'greater-equal':query.GreaterEqual,
           'less-equal':query.LessEqual,
           'match':query.Like,
           'between':query.Between,
           'like':query.Like,
##            'in':query.In,
##            'intersect':query.Intersect,
##            'union':query.Union,
           'and':query.Intersect,
           'or':query.Union }
###

class StateError ( Exception ):
    pass
#

class ParserHandler ( object ):

    def __init__ ( self, parent ):
        self._parent = parent
        self._state = None
        self._stack = []
        self._level = 0
        return

    def push ( self, arg ):
        self._stack.insert(0, arg)
        return

    def pop ( self ):
        return self._stack.pop(0)

    def top ( self ):
        if not self._stack:
            return None
        return self._stack[0]

    def startDocument(self):
        pass

    def endDocument(self):
        pass

    def startElement(self, tag, attrs):
##         print ' '*self._level, 'open', tag
        if tag in ('and', 'or'): #, 'intersect', 'union'):
            op = tag2op[tag]
            self.push( (op(), []) )
            self._level += 1
        elif tag in ('greater', 'less', 'equal', 'not-equal',
                   'greater-equal', 'less-equal', 'match'):
            op = tag2op[tag]
            t = query.Terminal('')
            self.push( (op(t,t), []) )
##         elif tag == 'in':
##             op = tag2op[tag]
##             t = query.Variable('')
##             self.push( (op(t), []) )
        elif tag == 'between':
            op = tag2op[tag]
            v = query.Variable('')
            t = query.Value('')
            self.push( (op(v,t,t), []) )
        elif tag == 'property':
            op, l = self.pop()
            lhs = '%s %s' % (attrs['namespace'], attrs['name'])
            if type(op) == query.In:
                op = query.In(query.Variable(lhs))
            elif type(op) == query.Between:
                l.append(query.Variable(lhs))
            else:
                op.set_lhs(query.Variable(lhs))
            self.push( (op, l) )
        elif tag == 'literal':
            op, l = self.pop()
##             pprint(attrs)
            v = query.Value(attrs['value'], attrs)
            l.append(v)
            self.push( (op, l) )
        return

    def endElement(self, tag):
##         print ' '*self._level, 'close', tag
        if tag in ('greater', 'less', 'equal', 'not-equal',
                   'greater-equal', 'less-equal', 'match'):
            cond, args = self.pop()
            cond.set_rhs(args[0])
            op, oargs = self.pop()
            if not op.clause in ('INTERSECT', 'UNION'):
                raise TypeError
            oargs.append(cond)
            self.push( (op, oargs) )
##         elif tag == 'in':
##             cond, args = self.pop()
##             for o in args:
##                 cond.append(o)
##             op, oargs = self.pop()
##             oargs.append(cond)
##             self.push( (op, oargs) )
        elif tag == 'between':
            cond, args = self.pop()
            args = args[:3]
            cond = query.Between(*args)
            op, oargs = self.pop()
            oargs.append(cond)
            self.push( (op, oargs) )
        elif tag in ('and', 'or'):
            op, args = self.pop()
            for o in args:
                op.append(o)
            if not self.top():
                self._parent.push_op(op)
            else:
                nop, nargs = self.pop()
                nargs.append(op)
                self.push( (nop, nargs) )
            self._level -= 1
        return
        
    def warning(self, msg):
        print "warning: %s:" % msg

    def error(self, msg):
        print "error: %s:" % msg

    def fatalError(self, msg):
        print "fatalError: %s:" % msg
#

class XmlQueryParser ( object ):

    def __init__ ( self ):
        self._stack = []
        self.renderer = query.SQLRenderer()
        return

    def parse_string ( self, xmlquery):
        self._stack = []
        handler = ParserHandler(self)
        parser = libxml2.createPushParser(handler, "", 0, "query.xml")
        try:
            parser.parseChunk(xmlquery, len(xmlquery), 1)
        except StateError:
            sys.exit(2)
        return

    def parse_file ( self, fname):
        self._stack = []
        handler = ParserHandler(self)
        parser = libxml2.createPushParser(handler, "", 0, "query.xml")
        fin = file(fname, 'rb')
        for line in fin:
            try:
                parser.parseChunk(line, len(xmlquery), 0)
            except StateError:
                sys.exit(2)
        fin.close()
        parser.parseChunk("", 0, 1)
        return

    def _error ( self, *args ):
        print 'ERRR', repr(args)

    def push_op ( self, op ):
        self._stack.append(op)
        return

    def get_prerendered ( self ):
        ret = []
        stack = self._stack
        op = stack.pop()
        stack.reverse()
        for o in stack:
            if o.clause in ('INTERSECT', 'UNION'):
                op.append(o)
            else:
                op.append(query.Terminal(o.render_with(self.renderer)))
        return op
        
#

class SqlBuilder ( object ):

    table = 'rr.properties'
    sel_prefix = 'SELECT resource_id FROM '

    def __init__ ( self, parsed ):
        self._parsed = parsed
        self.renderer = query.SQLRenderer()
        self._alias = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
        return

    def _get_next_alias ( self ):
        x = self._alias[0]
        self._alias = self._alias[1:]
        return x

    def make_select ( self, term, table='rr.properties' ):
        self._level += 1
        indent = '    '*self._level
        sel = indent + self.sel_prefix
        table = self.table
        where = ' WHERE '
        op = "\n%s%s\n" % (indent, term.clause)
        ret = []
        for c in term.iter_terms():
            if c.clause in ('INTERSECT', 'UNION'):
                x = '\n' + self.make_select(c)
                sql = sel + '(%s) as %s' % (x, self._get_next_alias())
            else:
                sql = sel + table + where + c.render_with(self.renderer)
            ret.append(sql)
        self._level -= 1
        return op.join(ret)

    def get_statement ( self ):
        ret = []
        self._level = -1
        o = self._parsed.get_prerendered()
        sql = self.make_select(o)
        return sql + ';'
###
