#  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.

# SCDL parsing functions

from xml.etree.cElementTree import iterparse
from sys import stderr
from os import environ
from util import *
from httputil import *

# Element tree utility functions, used to parse SCDL documents
def parse(file):
    return map(lambda x: x, iterparse(file, events=("start", "end")))

def evt(e):
    return car(e)

def elt(e):
    return cadr(e)

def att(e):
    return elt(e).attrib

def text(e):
    return elt(e).text

def match(e, ev, tag):
    return evt(e) == ev and elt(e).tag.find("}" + tag) != -1

# Make a callable component
class component:
    def __init__(self, name, impl, svcs, refs, props):
        self.name = name
        self.impl = impl
        self.mod = None
        self.svcs = svcs
        self.refs = refs
        self.props = props
        self.proxies = ()

    def __call__(self, func, *args):
        return self.mod.__getattribute__(func)(*(args + self.proxies))

    def __getattr__(self, name):
        if name[0] == '_':
            raise AttributeError()
        if name == "eval":
            return self
        l = lambda *args: self.__call__(name, *args)
        self.__dict__[name] = l
        return l

    def __repr__(self):
        return repr((self.name, self.impl, self.mod, self.svcs, self.refs, self.props, self.proxies))

def mkcomponent(name, impl, svcs, refs, props):
    return component(name, impl, svcs, refs, props)

# Return the Python module name of a component implementation
def implementation(e):
    if len(e) == 0 or match(car(e), "end", "component") == True:
        return ""
    if match(car(e), "start", "implementation.python") == False:
        return implementation(cdr(e))
    if "script" in att(car(e)):
        s = att(car(e))["script"]
        return s[0:len(s) - 3]
    return None

# Return the URI of a binding under a SCDL service or reference element
def binding(e):
    if len(e) == 0 or match(car(e), "end", "reference") == True or match(car(e), "end", "service") == True:
        return ()
    if match(car(e), "start", "binding.") == False:
        return binding(cdr(e))
    return att(car(e))["uri"]

# Return the list of references under a SCDL component element
def references(e):
    if len(e) == 0 or match(car(e), "end", "component") == True:
        return ()
    if match(car(e), "start", "reference") == False:
        return references(cdr(e))
    if "target" in att(car(e)):
        return cons((att(car(e))["name"], car(tokens(att(car(e))["target"]))), references(cdr(e)))
    return cons((att(car(e))["name"], binding(e)), references(cdr(e)))

# Return the list of properties under a SCDL component element
def properties(e):
    if len(e) == 0 or match(car(e), "end", "component") == True:
        return ()
    if match(car(e), "start", "property") == False:
        return properties(cdr(e))
    return cons((att(car(e))["name"], text(car(e))), properties(cdr(e)))

# Return the list of services under a SCDL component element
def services(e):
    if len(e) == 0 or match(car(e), "end", "component") == True:
        return ()
    if match(car(e), "start", "service") == False:
        return services(cdr(e))
    return cons(tokens(binding(e)), services(cdr(e)))

# Return the name attribute of a SCDL element
def name(e):
    return att(car(e))["name"]    

# Return the list of components under a SCDL composite element
def components(e):
    if len(e) == 0:
        return ()
    if match(car(e), "start", "component") == False:
        return components(cdr(e))
    n = name(e)
    return cons(mkcomponent(n, implementation(e), services(e), references(e), properties(e)), components(cdr(e)))

# Find a component with a given name
def nameToComponent(name, comps):
    if comps == ():
        return None
    if car(comps).name == name:
        return car(comps)
    return nameToComponent(name, cdr(comps))

# Find the URI matching a given URI in a list of service URIs
def matchingURI(u, svcs):
    if svcs == ():
        return None
    if car(svcs) == u[0:len(car(svcs))]:
        return car(svcs)
    return matchingURI(u, cdr(svcs))

# Return the (service URI, component) pair matching a given URI
def uriToComponent(u, comps):
    if car(u) == "components":
        return componentURIToComponent(u, comps)
    if car(u) == "references":
        return referenceURIToComponent(u, comps)
    return serviceURIToComponent(u, comps)

def serviceURIToComponent(u, comps):
    if comps == ():
        return (None, None)
    m = matchingURI(u, car(comps).svcs)
    if m != None:
        return (m, car(comps))
    return serviceURIToComponent(u, cdr(comps))

def componentURIToComponent(u, comps):
    comp = nameToComponent(cadr(u), comps)
    if comps == None:
        return (None, None)
    return (u[0:2], comp)

def referenceURIToComponent(u, comps):
    sc = nameToComponent(cadr(u), comps)
    if sc == None:
        return (None, None)
    
    def referenceToComponent(r, refs):
        if refs == ():
            return None
        if r == car(car(refs)):
            return cadr(car(refs))
        return referenceToComponent(r, cdr(refs))

    tn = referenceToComponent(caddr(u), sc.refs)
    if tn == None:
        return (None, None)
    tc = nameToComponent(tn, comps)
    if tc == None:
        return (None, None)
    return (u[0:3], tc)

# Evaluate a reference, return a proxy to the resolved component or an
# HTTP client configured with the reference target uri
def evalReference(r, comps):
    t = cadr(r)
    if t.startswith("http://") or t.startswith("https://"):
        return mkclient(t)
    return nameToComponent(t, comps)

# Make a callable property
class property:
    def __init__(self, name, l):
        self.name = name
        self.l = l

    def __call__(self, *args):
        return self.l(*args)

    def __getattr__(self, name):
        if name == "eval":
            return self
        raise AttributeError()

    def __repr__(self):
        return repr((self.name, self.l()))

def mkproperty(name, l):
    return property(name, l)

# Evaluate a property, return a lambda function returning the property
# value. The host, user, realm, nickname and email properties are configured
# with the values from the HTTP request, if any.
def evalProperty(p):
    if car(p) == "host":
        return mkproperty(car(p), lambda: hostProperty(cadr(p), environ))
    if car(p) == "user":
        return mkproperty(car(p), lambda: userProperty(cadr(p)))
    if car(p) == "realm":
        return mkproperty(car(p), lambda: hostProperty(cadr(p), environ))
    if car(p) == "nickname":
        return mkproperty(car(p), lambda: nicknameProperty(cadr(p)))
    if car(p) == "email":
        return mkproperty(car(p), lambda: emailProperty(cadr(p)))
    return mkproperty(car(p), lambda: cadr(p))

def currentUser():
    try:
        from google.appengine.api import users
        return users.get_current_user()
    except:
        return None

def userProperty(v):
    user = currentUser()
    return user.federated_identity() if user else v

def nicknameProperty(v):
    user = currentUser()
    return user.nickname() if user else v

def hostProperty(v, e):
    return e.get("HTTP_HOST", e.get("SERVER_NAME", v)).split(":")[0]

def emailProperty(v):
    user = currentUser()
    return user.email() if user else v

# Evaluate a component, resolve its implementation, references and
# properties
def evalComponent(comp, comps):
    comp.mod = __import__(comp.impl)

    # Make a list of proxy lambda functions for the component references and properties
    # A reference proxy is the callable lambda function of the component wired to the reference
    # A property proxy is a lambda function that returns the value of the property
    print >> stderr, "evalComponent", comp.impl, comp.svcs, comp.refs, comp.props
    comp.proxies = tuple(map(lambda r: evalReference(r, comps), comp.refs)) + tuple(map(lambda p: evalProperty(p), comp.props))

    return comp

# Evaluate a list of components
def evalComponents(comps):
    return tuple(map(lambda c: evalComponent(c, comps), comps))

