package org.aksw.sparqlify.core.cast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.aksw.commons.collections.generator.Generator;
import org.aksw.commons.collections.multimaps.IBiSetMultimap;
import org.aksw.jena_sparql_api.views.E_RdfTerm;
import org.aksw.jena_sparql_api.views.ExprCopy;
import org.aksw.jena_sparql_api.views.SqlTranslationUtils;
import org.aksw.jenax.arq.util.expr.ExprUtils;
import org.aksw.sparqlify.algebra.sql.exprs2.ExprSqlBridge;
import org.aksw.sparqlify.algebra.sql.exprs2.S_ColumnRef;
import org.aksw.sparqlify.algebra.sql.exprs2.S_Constant;
import org.aksw.sparqlify.algebra.sql.exprs2.S_Function;
import org.aksw.sparqlify.algebra.sql.exprs2.SqlExpr;
import org.aksw.sparqlify.algebra.sql.exprs2.SqlExprFunction;
import org.aksw.sparqlify.algebra.sql.nodes.Projection;
import org.aksw.sparqlify.core.TypeToken;
import org.aksw.sparqlify.core.algorithms.ExprSqlRewrite;
import org.aksw.sparqlify.core.datatypes.SparqlFunction;
import org.aksw.sparqlify.core.sql.expr.evaluation.SqlExprEvaluator;
import org.aksw.sparqlify.type_system.CandidateMethod;
import org.aksw.sparqlify.type_system.FunctionModel;
import org.aksw.sparqlify.type_system.FunctionModelMeta;
import org.aksw.sparqlify.type_system.MethodEntry;
import org.aksw.sparqlify.type_system.MethodSignature;
import org.aksw.sparqlify.type_system.TypeSystemUtils;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprFunction;
import org.apache.jena.sparql.expr.ExprVar;
import org.apache.jena.sparql.expr.NodeValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Multimap;



class ExprHolder {
    private Object expr;

    public ExprHolder(SqlExpr expr) {
        this.expr = expr;
    }

    public ExprHolder(Expr expr) {
        this.expr = expr;
    }

    public boolean isSqlExpr() {
        boolean result = this.expr instanceof SqlExpr;
        return result;
    }

    public boolean isExpr() {
        boolean result = this.expr instanceof Expr;
        return result;
    }

    public Expr getExpr() {
        return (Expr)expr;
    }

    public SqlExpr getSqlExpr() {
        return (SqlExpr) expr;
    }

    @Override
    public String toString() {
        return "ExprHolder: " + expr;
    }
}


class RewriteState {
    private Generator<String> genSym;
    private Projection projection = new Projection();

    public RewriteState() {
        this(Generator.create("s"));
    }

    public RewriteState(Generator genSym) {
        super();
        this.genSym = genSym;
        this.projection = projection;
    }

    public Generator<String> getGenSym() {
        return genSym;
    }
    public Projection getProjection() {
        return projection;
    }
}


/**
 * Computes the datatype of each expression node.
 *
 * TODO: I think this class should not actually transform expressions
 *
 * @author Claus Stadler <cstadler@informatik.uni-leipzig.de>
 *
 */
public class TypedExprTransformerImpl
    implements TypedExprTransformer
{
    private static final Logger logger = LoggerFactory.getLogger(TypedExprTransformerImpl.class);

    // TODO Get rid of the typeSystem here, and replace it by more fine granular
    // objects
    private TypeSystem typeSystem;
    // private constantConverter

    private SparqlFunctionProvider functionProvider;

    public TypedExprTransformerImpl(TypeSystem typeSystem) { //SparqlFunctionProvider functionProvider) {
        this.typeSystem = typeSystem;
        this.functionProvider = typeSystem;

        //this.constantSqlConverter =
        //this.functionProvider = functionProvider;
    }

    public TypeSystem getTypeSystem()
    {
        return typeSystem;
    }


    public static List<TypeToken> getTypes(Collection<SqlExpr> sqlExprs) {

        List<TypeToken> result = new ArrayList<TypeToken>(sqlExprs.size());
        for(SqlExpr sqlExpr : sqlExprs) {
            TypeToken typeName = sqlExpr.getDatatype();
            result.add(typeName);
        }

        return result;
    }

    public static boolean containsTypeError(Iterable<SqlExpr> exprs) {
        for(SqlExpr expr : exprs) {
            if(S_Constant.TYPE_ERROR.equals(expr)) {
                return true;
            }
        }

        return false;
    }

//	public List<ExprHolder> rewriteArgsDefault(ExprFunction fn, Map<String, TypeToken> typeMap, RewriteState state)
//	{
//		List<ExprHolder> evaledArgs = new ArrayList<ExprHolder>();
//		boolean isAllSqlExpr = true;
//		for(Expr arg : fn.getArgs()) {
//
//			ExprHolder evaledArg = rewrite(arg, typeMap, state);
//
//
//			isAllSqlExpr = isAllSqlExpr && evaledArg.isSqlExpr();
//
//			// If an argument evaluated to type error, return type error
//			// TODO: Distinguish between null and type error. Currently we use nvNothing which actually corresponds to NULL
//			// (currently represented with nvNothing - is that safe? - Rather no - see above)
//			/*
//			if(evaledArg.equals(NodeValue.nvNothing)) {
//				return NodeValue.nvNothing;
//			}
//			*/
//
//			evaledArgs.add(evaledArg);
//		}
//
//		return evaledArgs;
//	}
//
//	public List<ExprHolder> rewriteArgsRdfTerm(E_RdfTerm rdfTerm, Map<String, TypeToken> typeMap, RewriteState state) {
//
//		List<Expr> args = rdfTerm.getArgs();
//		List<ExprHolder> rewrittenArgs = new ArrayList<ExprHolder>();
//
//		for(Expr arg : args) {
//			ExprHolder rewrittenArg;
//			if(arg.isConstant()) {
//				rewrittenArg = new ExprHolder(arg); // Do not rewrite constants
//			} else {
//				rewrittenArg = rewrite(arg, typeMap, state);
//			}
//
//			rewrittenArgs.add(rewrittenArg);
//		}
//
//		return rewrittenArgs;
//	}


    public ExprVar allocateVariable(SqlExpr sqlExpr, RewriteState state) {
        String varName = state.getGenSym().next();
        Var var = Var.alloc(varName);
        ExprVar result = new ExprVar(var);

        state.getProjection().put(varName, sqlExpr);

        return result;
    }

    public ExprSqlRewrite rewrite(Expr expr, Map<String, TypeToken> typeMap) {

        E_RdfTerm rdfTerm;
        if(expr.isConstant()) {
            rdfTerm = SqlTranslationUtils.expandConstant(expr);
        } else {
            rdfTerm = SqlTranslationUtils.expandRdfTerm(expr);
        }


        RewriteState state = new RewriteState();

        ExprHolder rewrite = rewrite(rdfTerm, typeMap, state);

        Expr resultExpr;
        if(rewrite.isSqlExpr()) {
            SqlExpr sqlExpr = rewrite.getSqlExpr();
            resultExpr = allocateVariable(sqlExpr, state);
        } else {
            resultExpr = rewrite.getExpr();
        }

        ExprSqlRewrite result = new ExprSqlRewrite(resultExpr, state.getProjection());

        return result;
    }


    public ExprSqlRewrite rewriteOld(Expr expr, Map<String, TypeToken> typeMap) {
        RewriteState state = new RewriteState();

        ExprHolder rewrite = rewrite(expr, typeMap, state);

        Expr resultExpr;
        if(rewrite.isSqlExpr()) {
            resultExpr = new ExprSqlBridge(rewrite.getSqlExpr());
        } else {
            resultExpr = rewrite.getExpr();
        }

        ExprSqlRewrite result = new ExprSqlRewrite(resultExpr, state.getProjection());

        return result;
    }


    public ExprHolder rewrite(Expr expr, Map<String, TypeToken> typeMap, RewriteState state) {

        ExprHolder result;
        if(expr.isConstant()) {
            NodeValue nodeValue = expr.getConstant();
            SqlExpr sqlExpr = translate(nodeValue);
            result = new ExprHolder(sqlExpr);
        }
        else if(expr.isVariable()) {
            ExprVar var = expr.getExprVar();
            SqlExpr sqlExpr = translate(var, typeMap);
            result = new ExprHolder(sqlExpr);
        }
        else if(expr.isFunction()) {
            result = rewrite(expr.getFunction(), typeMap, state);
        } else {
            throw new RuntimeException("Should not happen: " + expr);
        }

        if(result.getSqlExpr().getDatatype().equals(TypeToken.TypeError)) {
            logger.debug("Got type error for " + expr);
        }

        return result;
    }



    public ExprHolder rewrite(ExprFunction fn, Map<String, TypeToken> typeMap, RewriteState state) {

        ExprHolder result;

        String functionId = ExprUtils.getFunctionId(fn);

        logger.debug("Processing: " + fn);
        /*
        if(containsTypeError(evaledArgs)) {
            logger.debug("Type error in argument (" + evaledArgs + ")");
            return S_Constant.TYPE_ERROR;
        }
        */
        boolean pushConstants = true;

        if(fn instanceof E_RdfTerm) {
            // We always push constants to make data access uniform
            //pushConstants = false;
        }


        List<ExprHolder> evaledArgs = new ArrayList<ExprHolder>();
        boolean isAllSqlExpr = true;
        for(Expr arg : fn.getArgs()) {

            ExprHolder evaledArg;
            if(arg.isConstant() && !pushConstants) {
                evaledArg = new ExprHolder(arg);
            } else {
                evaledArg = rewrite(arg, typeMap, state);
            }




            isAllSqlExpr = isAllSqlExpr && evaledArg.isSqlExpr();

            // If an argument evaluated to type error, return type error
            // TODO: Distinguish between null and type error. Currently we use nvNothing which actually corresponds to NULL
            // (currently represented with nvNothing - is that safe? - Rather no - see above)
            /*
            if(evaledArg.equals(NodeValue.nvNothing)) {
                return NodeValue.nvNothing;
            }
            */

            evaledArgs.add(evaledArg);
        }

        boolean isFnRewritable = isAllSqlExpr;

        if(fn instanceof E_RdfTerm) {
            isFnRewritable = false;
        }

        // Allocate variables for all SqlExprs, pass on Exprs
        if(!isFnRewritable) {

            List<Expr> newArgs = new ArrayList<Expr>(evaledArgs.size());

            for(ExprHolder holder : evaledArgs) {

                Expr arg;

                if(holder.isSqlExpr()) {

                    SqlExpr typedExpr = holder.getSqlExpr();
                    arg = allocateVariable(typedExpr, state);

                }  else {

                    arg = holder.getExpr();

                }

                newArgs.add(arg);
            }

            Expr expr = ExprCopy.getInstance().copy(fn, newArgs);

            result = new ExprHolder(expr);

        } else {
            // All arguments are SQL Exprs
            List<SqlExpr> newArgs = new ArrayList<SqlExpr>(evaledArgs.size());

            for(ExprHolder holder : evaledArgs) {
                SqlExpr sqlExpr = holder.getSqlExpr();
                newArgs.add(sqlExpr);
            }

            SqlExpr sqlExpr = processFunction(functionId, newArgs);

            result = new ExprHolder(sqlExpr);
        }


        return result;
    }


    public SqlExpr processFunction(String functionId, List<SqlExpr> newArgs) {

        // There must be a function registered for the argument types

//		String datatype = exprTypeEvaluator.evaluateType(fn);
//		if(datatype == null) {
//			throw new RuntimeException("No datatype could be obtained for " + fn);
//		}
//
//		//result = S_Function.create(datatype, functionId, evaledArgs);



        // Get the types of the evaluated arguments
        List<TypeToken> argTypes = new ArrayList<TypeToken>(newArgs.size());
        for(SqlExpr newArg : newArgs) {
            argTypes.add(newArg.getDatatype());
        }

        FunctionModel<TypeToken> functionModel = typeSystem.getSqlFunctionModel();
        Multimap<String, String> sparqlSqlDecls = typeSystem.getSparqlSqlDecls();

        CandidateMethod<TypeToken> candidate = TypeSystemUtils.lookupSqlCandidate(functionModel, sparqlSqlDecls, functionId, argTypes);


        SqlExpr result = null;
        if(candidate != null) {

            /* Check if we can apply an inverse function:
            /* The rules are:
             * - The function must be a comparator (these implicitly take 2 arguments)
             * - One of the arguments is a function expression whose symbol has an inverse defined
             * - The other operand is a constant
             */
            FunctionModelMeta sqlMetaModel = typeSystem.getSqlFunctionMetaModel();

            String opId = candidate.getMethod().getId();

            // TODO Perform short cut evaluation of logical operators
            if(sqlMetaModel.getLogicalAnds().contains(opId)) {
                SqlExpr a = newArgs.get(0);
                SqlExpr b = newArgs.get(1);

                if(b.isConstant()) {
                    SqlExpr tmp = a;
                    a = b;
                    b = tmp;
                }

                if(a.isConstant() && a.asConstant().equals(S_Constant.TRUE)) {
                    result = b;
                }

            }
//			else if(sqlMetaModel.getLogicalOrs().contains(opId)) {
//
//			}
//			else if(sqlMetaModel.getLogicalNots().contains(opId)) {
//
//			}
            else if(sqlMetaModel.getComparators().contains(opId)) {

                // TODO: We need the name of the comparator
                //typeSystem.getSparqlSqlDecls()

                SqlExpr a = newArgs.get(0);
                SqlExpr b = newArgs.get(1);

                if(b.isConstant()) {
                    SqlExpr tmp = a;
                    a = b;
                    b = tmp;
                }

                if(a.isConstant() && b.isFunction()) {
                    String fnId = b.asFunction().getName();

                    String invId = sqlMetaModel.getInverses().get(fnId);
                    if(invId != null) {
                        Map<String, SqlExprEvaluator> sqlImpls = typeSystem.getSqlImpls();
                        SqlExprEvaluator see = sqlImpls.get(invId);

                        SqlExpr c = b.getArgs().get(0);

                        if(see == null) {
                            throw new RuntimeException("Inverse " + invId + " of " + fnId + " declared, but no implementation provided");
                        }

                        SqlExpr d = see.eval(Collections.singletonList(a));

                        List<SqlExpr> aa = Arrays.asList(d, c);





                        result = processFunction(functionId, aa);
                        // Lookp an comparator for the new argument types

                    }

                }

                // Optimize expressions such as equals(str(int), str(int)) to simply equals(int, int)
                else if(a.isFunction() && b.isFunction()) {
                    SqlExprFunction fnA = a.asFunction();
                    SqlExprFunction fnB = b.asFunction();

                    String nameA = fnA.getName();
                    String nameB = fnB.getName();

                    if(nameA.equals(nameB)) { // TODO ... and if nameA and nameB are injective...


                        //String name = functionModel.getNameById(nameA);

                        List<SqlExpr> argsA = fnA.getArgs();
                        List<SqlExpr> argsB = fnB.getArgs();

                        List<SqlExpr> bb = new ArrayList<SqlExpr>(argsA.size() + argsB.size());
                        bb.addAll(argsA);
                        bb.addAll(argsB);
                        /*
                        //List<TypeToken>
                        List<TypeToken> types = SqlExprUtils.getTypes(fnA.getArgs());
                        List<TypeToken> typesB = SqlExprUtils.getTypes(fnB.getArgs());
                        types.addAll(typesB);

                        CandidateMethod<TypeToken> cd = TypeSystemImpl.lookupSqlCandidate(functionModel, sparqlSqlDecls, name, types);
                        */

                        result = processFunction(functionId, bb);
                    }
                }

            }

            if(result == null) {


                result = createSqlExpr(candidate, newArgs);
            }

        } else {
            // Type error
            logger.info("Yielding type error because no signature found for: " + functionId + " with argument types " + argTypes + " for arguments " + newArgs);
            result = S_Constant.TYPE_ERROR;
            //throw new RuntimeException("Type error.... needs to be handled - No function found: " + functionId + " with argtypes " + argTypes);
        }

        return result;
    }


//	SparqlFunction sparqlFunction = functionProvider.getSparqlFunction(functionId);
//	if(sparqlFunction == null) {
//		throw new RuntimeException("Sparql function not declared: " + functionId);
//	}
//
//	SqlExprEvaluator evaluator = sparqlFunction.getEvaluator();
//
//	logger.debug("Evaluator for '" + functionId + "': " + evaluator);
//
//	// If there is an evaluator, we can pass all arguments to it, and see if it yields a new expression
//	if(evaluator != null) {
//		SqlExpr tmp = evaluator.eval(newArgs);
//		if(tmp != null) {
//			result = new ExprHolder(tmp);
//			//return tmp;
//		} else {
//			throw new RuntimeException("Evaluator yeld null value");
//		}
//	} else {

                // If there is no evaluator, use the default behavior:
//				MethodSignature<TypeToken> signature = sparqlFunction.getSignature();
//				if(signature != null) {
//
//					TypeToken returnType = signature.getReturnType();
//					if(returnType != null) {



                        //SqlExpr tmp = S_Function.create(returnType, functionId, newArgs);

//					} else {
//						throw new RuntimeException("Return type is null: " + signature);
//					}
//
//				} else {
//					throw new RuntimeException("Neither evaluator nor signature found for " + functionId + " in " + fn);
//				}

//			}
            // Check if the functionProvider has a definition for the functionId


    public static SqlExpr createSqlExpr(CandidateMethod<TypeToken> candidate, SqlExpr ... args) {
        return createSqlExpr(candidate, Arrays.asList(args));
    }

    public static SqlExpr createSqlExpr(CandidateMethod<TypeToken> candidate, List<SqlExpr> args) {
        MethodEntry<TypeToken> method = candidate.getMethod();
        List<CandidateMethod<TypeToken>> coercions = candidate.getCoercions();

        List<SqlExpr> newArgs;
        if(coercions != null) {

            // Apply coercions
            newArgs = new ArrayList<SqlExpr>(args.size());
            for(int i = 0; i < args.size(); ++i) {
                SqlExpr arg = args.get(i);
                CandidateMethod<TypeToken> coercion = coercions.get(i);

                SqlExpr newArg;
                if(coercion != null) {
                    newArg = createSqlExpr(coercion, Collections.singletonList(arg));
                } else {
                    newArg = arg;
                }

                newArgs.add(newArg);
            }
        } else {
            newArgs = args;
        }

        TypeToken returnType = method.getSignature().getReturnType();
        String functionId = method.getId();

        SqlExpr result = S_Function.create(returnType, functionId, newArgs);

        return result;
    }

    /**
     * This function requires a type-constructor free expression as input:
     * That is an expression that can be translated directly to SQL -
     * i.e. all bnode/uri/literal type constructors have been removed from it
     *
     *
     * TODO How to pass the type error to SPARQL functions,
     * such as logical AND/OR/NOT, so they get a chance to deal with it?
     *
     * Using the SPARQL level evaluator is not really possible anymore, because we already translated to the SQL level.
     *
     * We could either:
     * . have special treatment for logical and/or/not
     *     But then we can't extend the system to our liking
     * . have an evaluator on the SqlExpr level, rather than the expr level
     *     Very generic, but can we avoid the duplication with Expr and SqlExpr?
     *     Probably we can't.
     *     The expr structure does not allow adding a custom datatype, and mapping it externally turned out to be quite a hassle.
     *
     *
     *
     * @param fn
     * @param binding
     * @param typeMap
     * @return
     */
    public SqlExpr translate(ExprFunction fn, Map<String, TypeToken> typeMap) {

        SqlExpr result;

        List<SqlExpr> evaledArgs = new ArrayList<SqlExpr>();

        logger.debug("Processing: " + fn);
        /*
        if(containsTypeError(evaledArgs)) {
            logger.debug("Type error in argument (" + evaledArgs + ")");
            return S_Constant.TYPE_ERROR;
        }
        */


        for(Expr arg : fn.getArgs()) {
            SqlExpr evaledArg = translate(arg, typeMap);

            // If an argument evaluated to type error, return type error
            // TODO: Distinguish between null and type error. Currently we use nvNothing which actually corresponds to NULL
            // (currently represented with nvNothing - is that safe? - Rather no - see above)
            /*
            if(evaledArg.equals(NodeValue.nvNothing)) {
                return NodeValue.nvNothing;
            }
            */

            evaledArgs.add(evaledArg);
        }


//		List<TypeToken> argTypes = getTypes(evaledArgs);

        // There must be a function registered for the argument types
        String functionId = ExprUtils.getFunctionId(fn);

//		String datatype = exprTypeEvaluator.evaluateType(fn);
//		if(datatype == null) {
//			throw new RuntimeException("No datatype could be obtained for " + fn);
//		}
//
//		//result = S_Function.create(datatype, functionId, evaledArgs);


        SparqlFunction sparqlFunction = functionProvider.getSparqlFunction(functionId);
        if(sparqlFunction == null) {
            throw new RuntimeException("Sparql function not declared: " + functionId);
        }

        SqlExprEvaluator evaluator = sparqlFunction.getEvaluator();

        logger.debug("Evaluator for '" + functionId + "': " + evaluator);

        // If there is an evaluator, we can pass all arguments to it, and see if it yields a new expression
        if(evaluator != null) {
            SqlExpr tmp = evaluator.eval(evaledArgs);
            if(tmp != null) {
                return tmp;
            } else {
                throw new RuntimeException("Evaluator yeld null value");
            }
        }

        // If there is no evaluator, use the default behaviour:
        MethodSignature<TypeToken> signature = sparqlFunction.getSignature();
        if(signature != null) {

            TypeToken returnType = signature.getReturnType();
            if(returnType != null) {

                result = S_Function.create(returnType, functionId, evaledArgs);

            }

        }

        // Check if the functionProvider has a definition for the functionId




        throw new RuntimeException("Neither evaluator nor signature found for " + fn);


        // If there was no evaluator, or if the evaluator returned null, continue here.

//		// TODO: New approach: There must always be an evaluator
//
//		// If one of the arguments is a type error, we must return a type error.
//		if(containsTypeError(evaledArgs)) {
//			return S_Constant.TYPE_ERROR;
//		}
//
//		SqlMethodCandidate castMethod = datatypeSystem.lookupMethod(functionId, argTypes);
//
//		if(castMethod == null) {
//			//throw new RuntimeException("No method found for " + fn);
//			logger.debug("No method found for " + fn);
//
//			return S_Constant.TYPE_ERROR;
//		}
//
//		// TODO: Invoke the SQL method's invocable if it exists and all arguments are constants
//
//		result = S_Method.createOrEvaluate(castMethod, evaledArgs);
//
//		logger.debug("[Result] " + result);
//
//		return result;
    }


    public SqlExpr translate(NodeValue expr) {
        SqlValue sqlValue = typeSystem.convertSql(expr);

        SqlExpr result = S_Constant.create(sqlValue);
        return result;
    }

    public SqlExpr translate(ExprVar expr, Map<String, TypeToken> typeMap) {

        String varName = expr.getVarName();
        TypeToken datatype = typeMap.get(varName);


        if(datatype == null) {
            throw new RuntimeException("No datatype found for " + varName);
        }

        IBiSetMultimap<TypeToken, TypeToken> ptm = typeSystem.getPhysicalTypeMap();
        Set<TypeToken> schematicTypes = ptm.get(datatype);

        TypeToken schematicType;
        if(schematicTypes.isEmpty()) {
            schematicType = datatype;
            //typeSystem.get
            //throw new RuntimeException("Physical type " + datatype + " not known");
        } else if(schematicTypes.size() > 1) {
            throw new RuntimeException("Multiple mappings for physical type " + datatype + ": " + schematicTypes);
        } else {
            schematicType = schematicTypes.iterator().next();
        }

        // We need to map the phyical datatype to a schematic one
        // e.g. varchar -> string

        SqlExpr result = new S_ColumnRef(schematicType, varName);
        return result;
    }

    /*
     * How to best add interceptors (callbacks with transformation) for certain functions?
     *
     * e.g.: concat(foo, concat(?x...)) -> concat(foo, ?x)
     * lang(rdfterm(2, ?x, ?y, '')) -> ?y
     *
     * The main question is, whether to apply to callback before or after the arguments are evaluated.
     *
     * -> After makes more sense: Then we have constant folder arguments
     */
    public SqlExpr translate(Expr expr, Map<String, TypeToken> typeMap) {

        //assert expr != null : "Null pointer exception";
        if(expr == null) {
            throw new NullPointerException();
        }

        //System.out.println(expr);

        SqlExpr result = null;
        if(expr.isConstant()) {

            result = translate(expr.getConstant());


        } else if(expr.isFunction()) {
            ExprFunction fn = expr.getFunction();

            result = translate(fn, typeMap);

        } else if(expr.isVariable()) {

            result = translate(expr.getExprVar(), typeMap);
        } else {
            throw new RuntimeException("Unknown expression type encountered: " + expr);
        }

        return result;
    }

}
