/*
 * Copyright 2016 Merck Sharp & Dohme Corp. a subsidiary of Merck & Co.,
 * Inc., Kenilworth, NJ, USA.
 *
 * Licensed 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.
 */
package com.msd.gin.halyard.strategy;

import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.regex.Pattern;

import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.net.ParsedIRI;
import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
//if URI is removed from RDF4J it should be replaceable with org.eclipse.rdf4j.model.IRI
import org.eclipse.rdf4j.model.URI;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.datatypes.XMLDatatypeUtil;
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.XMLSchema;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.algebra.And;
import org.eclipse.rdf4j.query.algebra.BNodeGenerator;
import org.eclipse.rdf4j.query.algebra.Bound;
import org.eclipse.rdf4j.query.algebra.Coalesce;
import org.eclipse.rdf4j.query.algebra.Compare;
import org.eclipse.rdf4j.query.algebra.CompareAll;
import org.eclipse.rdf4j.query.algebra.CompareAny;
import org.eclipse.rdf4j.query.algebra.Datatype;
import org.eclipse.rdf4j.query.algebra.Exists;
import org.eclipse.rdf4j.query.algebra.FunctionCall;
import org.eclipse.rdf4j.query.algebra.IRIFunction;
import org.eclipse.rdf4j.query.algebra.If;
import org.eclipse.rdf4j.query.algebra.In;
import org.eclipse.rdf4j.query.algebra.IsBNode;
import org.eclipse.rdf4j.query.algebra.IsLiteral;
import org.eclipse.rdf4j.query.algebra.IsNumeric;
import org.eclipse.rdf4j.query.algebra.IsResource;
import org.eclipse.rdf4j.query.algebra.IsURI;
import org.eclipse.rdf4j.query.algebra.Label;
import org.eclipse.rdf4j.query.algebra.Lang;
import org.eclipse.rdf4j.query.algebra.LangMatches;
import org.eclipse.rdf4j.query.algebra.Like;
import org.eclipse.rdf4j.query.algebra.ListMemberOperator;
import org.eclipse.rdf4j.query.algebra.LocalName;
import org.eclipse.rdf4j.query.algebra.MathExpr;
import org.eclipse.rdf4j.query.algebra.Namespace;
import org.eclipse.rdf4j.query.algebra.Not;
import org.eclipse.rdf4j.query.algebra.Or;
import org.eclipse.rdf4j.query.algebra.Regex;
import org.eclipse.rdf4j.query.algebra.SameTerm;
import org.eclipse.rdf4j.query.algebra.Str;
import org.eclipse.rdf4j.query.algebra.ValueConstant;
import org.eclipse.rdf4j.query.algebra.ValueExpr;
import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.query.algebra.evaluation.ValueExprEvaluationException;
import org.eclipse.rdf4j.query.algebra.evaluation.function.Function;
import org.eclipse.rdf4j.query.algebra.evaluation.function.FunctionRegistry;
import org.eclipse.rdf4j.query.algebra.evaluation.function.datetime.Now;
import org.eclipse.rdf4j.query.algebra.evaluation.util.MathUtil;
import org.eclipse.rdf4j.query.algebra.evaluation.util.QueryEvaluationUtil;

/**
 * Evaluates "value" expressions (low level language functions and operators, instances of {@link ValueExpr}) from SPARQL such as 'Regex', 'IsURI', math expressions etc.
 *
 * @author Adam Sotona (MSD)
 */
class HalyardValueExprEvaluation {

    private final HalyardEvaluationStrategy parentStrategy;
    private final ValueFactory valueFactory;

    HalyardValueExprEvaluation(HalyardEvaluationStrategy parentStrategy, ValueFactory valueFactory) {
        this.parentStrategy = parentStrategy;
        this.valueFactory = valueFactory;
    }

    /**
     * Determines the "effective boolean value" of the {@link Value} returned by evaluating the expression.
     * See {@link QueryEvaluationUtil#getEffectiveBooleanValue(Value)} for the definition of "effective boolean value.
     * @param expr
     * @param bindings the set of named value bindings
     * @return
     * @throws QueryEvaluationException
     */
    boolean isTrue(ValueExpr expr, BindingSet bindings) throws QueryEvaluationException {
        try {
            Value value = evaluate(expr, bindings);
            return QueryEvaluationUtil.getEffectiveBooleanValue(value);
        } catch (ValueExprEvaluationException e) {
            return false;
        }
    }

    /**
     * Determines which evaluate method to call based on the type of {@link ValueExpr}
     * @param expr the expression to evaluate
     * @param bindings the set of named value bindings the set of named value bindings
     * @return the {@link Value} resulting from the evaluation
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    Value evaluate(ValueExpr expr, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        if (expr instanceof Var) {
            return evaluate((Var) expr, bindings);
        } else if (expr instanceof ValueConstant) {
            return evaluate((ValueConstant) expr, bindings);
        } else if (expr instanceof BNodeGenerator) {
            return evaluate((BNodeGenerator) expr, bindings);
        } else if (expr instanceof Bound) {
            return evaluate((Bound) expr, bindings);
        } else if (expr instanceof Str) {
            return evaluate((Str) expr, bindings);
        } else if (expr instanceof Label) {
            return evaluate((Label) expr, bindings);
        } else if (expr instanceof Lang) {
            return evaluate((Lang) expr, bindings);
        } else if (expr instanceof LangMatches) {
            return evaluate((LangMatches) expr, bindings);
        } else if (expr instanceof Datatype) {
            return evaluate((Datatype) expr, bindings);
        } else if (expr instanceof Namespace) {
            return evaluate((Namespace) expr, bindings);
        } else if (expr instanceof LocalName) {
            return evaluate((LocalName) expr, bindings);
        } else if (expr instanceof IsResource) {
            return evaluate((IsResource) expr, bindings);
        } else if (expr instanceof IsURI) {
            return evaluate((IsURI) expr, bindings);
        } else if (expr instanceof IsBNode) {
            return evaluate((IsBNode) expr, bindings);
        } else if (expr instanceof IsLiteral) {
            return evaluate((IsLiteral) expr, bindings);
        } else if (expr instanceof IsNumeric) {
            return evaluate((IsNumeric) expr, bindings);
        } else if (expr instanceof IRIFunction) {
            return evaluate((IRIFunction) expr, bindings);
        } else if (expr instanceof Regex) {
            return evaluate((Regex) expr, bindings);
        } else if (expr instanceof Coalesce) {
            return evaluate((Coalesce) expr, bindings);
        } else if (expr instanceof Like) {
            return evaluate((Like) expr, bindings);
        } else if (expr instanceof FunctionCall) {
            return evaluate((FunctionCall) expr, bindings);
        } else if (expr instanceof And) {
            return evaluate((And) expr, bindings);
        } else if (expr instanceof Or) {
            return evaluate((Or) expr, bindings);
        } else if (expr instanceof Not) {
            return evaluate((Not) expr, bindings);
        } else if (expr instanceof SameTerm) {
            return evaluate((SameTerm) expr, bindings);
        } else if (expr instanceof Compare) {
            return evaluate((Compare) expr, bindings);
        } else if (expr instanceof MathExpr) {
            return evaluate((MathExpr) expr, bindings);
        } else if (expr instanceof In) {
            return evaluate((In) expr, bindings);
        } else if (expr instanceof CompareAny) {
            return evaluate((CompareAny) expr, bindings);
        } else if (expr instanceof CompareAll) {
            return evaluate((CompareAll) expr, bindings);
        } else if (expr instanceof Exists) {
            return evaluate((Exists) expr, bindings);
        } else if (expr instanceof If) {
            return evaluate((If) expr, bindings);
        } else if (expr instanceof ListMemberOperator) {
            return evaluate((ListMemberOperator) expr, bindings);
        } else if (expr == null) {
            throw new IllegalArgumentException("expr must not be null");
        } else {
            throw new QueryEvaluationException("Unsupported value expr type: " + expr.getClass());
        }
    }

    /**
     * Evaluate a {@link Var} query model node.
     * @param var
     * @param bindings the set of named value bindings
     * @return the result of {@link Var#getValue()} from either {@code var}, or if {@code null}, from the {@ bindings}
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Var var, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value value = var.getValue();
        if (value == null) {
            value = bindings.getValue(var.getName());
        }
        if (value == null) {
            throw new ValueExprEvaluationException();
        }
        return value;
    }

    /**
     * Evaluate a {@link ValueConstant} query model node.
     * @param valueConstant
     * @param bindings the set of named value bindings
     * @return the {@link Value} of {@code valueConstant}
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(ValueConstant valueConstant, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        return valueConstant.getValue();
    }

    /**
     * Evaluate a {@link BNodeGenerator} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return the value of the evaluation
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(BNodeGenerator node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        ValueExpr nodeIdExpr = node.getNodeIdExpr();
        if (nodeIdExpr != null) {
            Value nodeId = evaluate(nodeIdExpr, bindings);
            if (nodeId instanceof Literal) {
                String nodeLabel = ((Literal) nodeId).getLabel() + (bindings.toString().hashCode());
                return valueFactory.createBNode(nodeLabel);
            } else {
                throw new ValueExprEvaluationException("BNODE function argument must be a literal");
            }
        }
        return valueFactory.createBNode();
    }

    /**
     * Evaluate a {@link Bound} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return {@link BooleanLiteral#TRUE} if the node can be evaluated or {@link BooleanLiteral#FALSE} if an exception occurs
     * @throws QueryEvaluationException
     */
    private Value evaluate(Bound node, BindingSet bindings) throws QueryEvaluationException {
        try {
            evaluate(node.getArg(), bindings);
            return BooleanLiteral.TRUE;
        } catch (ValueExprEvaluationException e) {
            return BooleanLiteral.FALSE;
        }
    }

    /**
     * Evaluate a {@link Str} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return a literal representation of the evaluation: a URI, the value of a simple literal or the label of any other literal
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Str node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        if (argValue instanceof URI) {
            return valueFactory.createLiteral(argValue.toString());
        } else if (argValue instanceof Literal) {
            Literal literal = (Literal) argValue;
            if (QueryEvaluationUtil.isSimpleLiteral(literal)) {
                return literal;
            } else {
                return valueFactory.createLiteral(literal.getLabel());
            }
        } else {
            throw new ValueExprEvaluationException();
        }
    }

    /**
     * Evaluate a {@link Label} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return the {@link Literal} resulting from the evaluation of the argument of the {@code Label}.
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Label node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        // FIXME: deprecate Label in favour of Str(?)
        Value argValue = evaluate(node.getArg(), bindings);
        if (argValue instanceof Literal) {
            Literal literal = (Literal) argValue;
            if (QueryEvaluationUtil.isSimpleLiteral(literal)) {
                return literal;
            } else {
                return valueFactory.createLiteral(literal.getLabel());
            }
        } else {
            throw new ValueExprEvaluationException();
        }
    }

    /**
     * Evaluate a {@link Lang} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return a {@link Literal} of the language tag of the {@code Literal} returned by evaluating the argument of the {@code node} or a
     * {code Literal} representing an empty {@code String} if there is no tag
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Lang node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        if (argValue instanceof Literal) {
            Literal literal = (Literal) argValue;
            Optional<String> langTag = literal.getLanguage();
            if (langTag.isPresent()) {
                return valueFactory.createLiteral(langTag.get());
            }
            return valueFactory.createLiteral("");
        }
        throw new ValueExprEvaluationException();
    }

    /**
     * Evaluate a {@link Datatype} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return a {@link Literal} representing the evaluation of the argument of the {@link Datatype}.
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Datatype node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value v = evaluate(node.getArg(), bindings);
        if (v instanceof Literal) {
            Literal literal = (Literal) v;
            if (literal.getDatatype() != null) {
                // literal with datatype
                return literal.getDatatype();
            } else if (literal.getLanguage() != null) {
                return RDF.LANGSTRING;
            } else {
                // simple literal
                return XMLSchema.STRING;
            }
        }
        throw new ValueExprEvaluationException();
    }

    /**
     * Evaluate a {@link Namespace} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return the {@link Literal} of the URI of {@link URI} returned by evaluating the argument of the {@code node}
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Namespace node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        if (argValue instanceof URI) {
            URI uri = (URI) argValue;
            return valueFactory.createURI(uri.getNamespace());
        } else {
            throw new ValueExprEvaluationException();
        }
    }

    /**
     * Evaluate a LocalName node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return the {@link Literal} of the  {@link URI} returned by evaluating the argument of the {@code node}
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(LocalName node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        if (argValue instanceof URI) {
            URI uri = (URI) argValue;
            return valueFactory.createLiteral(uri.getLocalName());
        } else {
            throw new ValueExprEvaluationException();
        }
    }

    /**
     * Determines whether the operand (a variable) contains a Resource.
     *
     * @return <tt>true</tt> if the operand contains a Resource, <tt>false</tt>
     * otherwise.
     */
    private Value evaluate(IsResource node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        return BooleanLiteral.valueOf(argValue instanceof Resource);
    }

    /**
     * Determines whether the operand (a variable) contains a URI.
     *
     * @return <tt>true</tt> if the operand contains a URI, <tt>false</tt>
     * otherwise.
     */
    private Value evaluate(IsURI node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        return BooleanLiteral.valueOf(argValue instanceof URI);
    }

    /**
     * Determines whether the operand (a variable) contains a BNode.
     *
     * @return <tt>true</tt> if the operand contains a BNode, <tt>false</tt>
     * otherwise.
     */
    private Value evaluate(IsBNode node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        return BooleanLiteral.valueOf(argValue instanceof BNode);
    }

    /**
     * Determines whether the operand (a variable) contains a Literal.
     *
     * @return <tt>true</tt> if the operand contains a Literal, <tt>false</tt>
     * otherwise.
     */
    private Value evaluate(IsLiteral node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        return BooleanLiteral.valueOf(argValue instanceof Literal);
    }

    /**
     * Determines whether the operand (a variable) contains a numeric datatyped literal, i.e. a literal with datatype xsd:float, xsd:double, xsd:decimal, or a
     * derived datatype of xsd:decimal.
     *
     * @return <tt>true</tt> if the operand contains a numeric datatyped literal,
     * <tt>false</tt> otherwise.
     */
    private Value evaluate(IsNumeric node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        if (argValue instanceof Literal) {
            Literal lit = (Literal) argValue;
            IRI datatype = lit.getDatatype();
            return BooleanLiteral.valueOf(XMLDatatypeUtil.isNumericDatatype(datatype));
        } else {
            return BooleanLiteral.FALSE;
        }
    }

    /**
     * Creates a URI from the operand value (a plain literal or a URI).
     *
     * @param node the node to evaluate, represents an invocation of the SPARQL IRI function
     * @param bindings the set of named value bindings used to generate the value that the URI is based on
     * @return a URI generated from the given arguments
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private IRI evaluate(IRIFunction node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        if (argValue instanceof Literal) {
            final Literal lit = (Literal) argValue;
            String uriString = lit.getLabel();
            final String baseURI = node.getBaseURI();
            try {
                    ParsedIRI iri = ParsedIRI.create(uriString);
                    if (!iri.isAbsolute() && baseURI != null) {
                            // uri string may be a relative reference.
                            uriString = ParsedIRI.create(baseURI).resolve(iri).toString();
                    }
                    else if (!iri.isAbsolute()) {
                            throw new ValueExprEvaluationException("not an absolute IRI reference: " + uriString);
                    }
            }
            catch (IllegalArgumentException e) {
                    throw new ValueExprEvaluationException("not a valid IRI reference: " + uriString);
            }
            IRI result = null;
            try {
                result = valueFactory.createIRI(uriString);
            } catch (IllegalArgumentException e) {
                throw new ValueExprEvaluationException(e.getMessage());
            }
            return result;
        } else if (argValue instanceof IRI) {
            return ((IRI) argValue);
        }
        throw new ValueExprEvaluationException();
    }

    /**
     * Determines whether the two operands match according to the <code>regex</code> operator.
     *
     * @return <tt>true</tt> if the operands match according to the
     * <tt>regex</tt> operator, <tt>false</tt> otherwise.
     */
    private Value evaluate(Regex node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value arg = evaluate(node.getArg(), bindings);
        Value parg = evaluate(node.getPatternArg(), bindings);
        Value farg = null;
        ValueExpr flagsArg = node.getFlagsArg();
        if (flagsArg != null) {
            farg = evaluate(flagsArg, bindings);
        }
        if (QueryEvaluationUtil.isStringLiteral(arg) && QueryEvaluationUtil.isSimpleLiteral(parg)
                && (farg == null || QueryEvaluationUtil.isSimpleLiteral(farg))) {
            String text = ((Literal) arg).getLabel();
            String ptn = ((Literal) parg).getLabel();
            String flags = "";
            if (farg != null) {
                flags = ((Literal) farg).getLabel();
            }
            // TODO should this Pattern be cached?
            int f = 0;
            for (char c : flags.toCharArray()) {
                switch (c) {
                    case 's':
                        f |= Pattern.DOTALL;
                        break;
                    case 'm':
                        f |= Pattern.MULTILINE;
                        break;
                    case 'i':
                        f |= Pattern.CASE_INSENSITIVE;
                        f |= Pattern.UNICODE_CASE;
                        break;
                    case 'x':
                        f |= Pattern.COMMENTS;
                        break;
                    case 'd':
                        f |= Pattern.UNIX_LINES;
                        break;
                    case 'u':
                        f |= Pattern.UNICODE_CASE;
                        break;
                    default:
                        throw new ValueExprEvaluationException(flags);
                }
            }
            Pattern pattern = Pattern.compile(ptn, f);
            boolean result = pattern.matcher(text).find();
            return BooleanLiteral.valueOf(result);
        }
        throw new ValueExprEvaluationException();
    }

    /**
     * Determines whether the language tag or the node matches the language argument of the node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(LangMatches node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value langTagValue = evaluate(node.getLeftArg(), bindings);
        Value langRangeValue = evaluate(node.getRightArg(), bindings);
        if (QueryEvaluationUtil.isSimpleLiteral(langTagValue)
                && QueryEvaluationUtil.isSimpleLiteral(langRangeValue)) {
            String langTag = ((Literal) langTagValue).getLabel();
            String langRange = ((Literal) langRangeValue).getLabel();
            boolean result = false;
            if (langRange.equals("*")) {
                result = langTag.length() > 0;
            } else if (langTag.length() == langRange.length()) {
                result = langTag.equalsIgnoreCase(langRange);
            } else if (langTag.length() > langRange.length()) {
                // check if the range is a prefix of the tag
                String prefix = langTag.substring(0, langRange.length());
                result = prefix.equalsIgnoreCase(langRange) && langTag.charAt(langRange.length()) == '-';
            }
            return BooleanLiteral.valueOf(result);
        }
        throw new ValueExprEvaluationException();
    }

    /**
     * Determines whether the two operands match according to the <code>like</code> operator. The operator is defined as a string comparison with the possible
     * use of an asterisk (*) at the end and/or the start of the second operand to indicate substring matching.
     *
     * @return <tt>true</tt> if the operands match according to the
     * <tt>like</tt>
     * operator, <tt>false</tt> otherwise.
     */
    private Value evaluate(Like node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value val = evaluate(node.getArg(), bindings);
        String strVal = null;
        if (val instanceof URI) {
            strVal = ((URI) val).toString();
        } else if (val instanceof Literal) {
            strVal = ((Literal) val).getLabel();
        }
        if (strVal == null) {
            throw new ValueExprEvaluationException();
        }
        if (!node.isCaseSensitive()) {
            // Convert strVal to lower case, just like the pattern has been done
            strVal = strVal.toLowerCase(Locale.ROOT);
        }
        int valIndex = 0;
        int prevPatternIndex = -1;
        int patternIndex = node.getOpPattern().indexOf('*');
        if (patternIndex == -1) {
            // No wildcards
            return BooleanLiteral.valueOf(node.getOpPattern().equals(strVal));
        }
        String snippet;
        if (patternIndex > 0) {
            // Pattern does not start with a wildcard, first part must match
            snippet = node.getOpPattern().substring(0, patternIndex);
            if (!strVal.startsWith(snippet)) {
                return BooleanLiteral.FALSE;
            }
            valIndex += snippet.length();
            prevPatternIndex = patternIndex;
            patternIndex = node.getOpPattern().indexOf('*', patternIndex + 1);
        }
        while (patternIndex != -1) {
            // Get snippet between previous wildcard and this wildcard
            snippet = node.getOpPattern().substring(prevPatternIndex + 1, patternIndex);
            // Search for the snippet in the value
            valIndex = strVal.indexOf(snippet, valIndex);
            if (valIndex == -1) {
                return BooleanLiteral.FALSE;
            }
            valIndex += snippet.length();
            prevPatternIndex = patternIndex;
            patternIndex = node.getOpPattern().indexOf('*', patternIndex + 1);
        }
        // Part after last wildcard
        snippet = node.getOpPattern().substring(prevPatternIndex + 1);
        if (snippet.length() > 0) {
            // Pattern does not end with a wildcard.
            // Search last occurence of the snippet.
            valIndex = strVal.indexOf(snippet, valIndex);
            int i;
            while ((i = strVal.indexOf(snippet, valIndex + 1)) != -1) {
                // A later occurence was found.
                valIndex = i;
            }
            if (valIndex == -1) {
                return BooleanLiteral.FALSE;
            }
            valIndex += snippet.length();
            if (valIndex < strVal.length()) {
                // Some characters were not matched
                return BooleanLiteral.FALSE;
            }
        }
        return BooleanLiteral.TRUE;
    }

    /**
     * Evaluates a function.
     */
    private Value evaluate(FunctionCall node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Optional<Function> function = FunctionRegistry.getInstance().get(node.getURI());
        if (!function.isPresent()) {
            throw new QueryEvaluationException("Unknown function '" + node.getURI() + "'");
        }
        // the NOW function is a special case as it needs to keep a shared return
        // value for the duration of the query.
        if (function.get() instanceof Now) {
            return evaluate((Now) function.get(), bindings);
        }
        List<ValueExpr> args = node.getArgs();
        Value[] argValues = new Value[args.size()];
        for (int i = 0; i < args.size(); i++) {
            argValues[i] = evaluate(args.get(i), bindings);
        }
        return function.get().evaluate(valueFactory, argValues);
    }

    /**
     * Evaluate an {@link And} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(And node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        try {
            Value leftValue = evaluate(node.getLeftArg(), bindings);
            if (QueryEvaluationUtil.getEffectiveBooleanValue(leftValue) == false) {
                // Left argument evaluates to false, we don't need to look any
                // further
                return BooleanLiteral.FALSE;
            }
        } catch (ValueExprEvaluationException e) {
            // Failed to evaluate the left argument. Result is 'false' when
            // the right argument evaluates to 'false', failure otherwise.
            Value rightValue = evaluate(node.getRightArg(), bindings);
            if (QueryEvaluationUtil.getEffectiveBooleanValue(rightValue) == false) {
                return BooleanLiteral.FALSE;
            } else {
                throw new ValueExprEvaluationException();
            }
        }
        // Left argument evaluated to 'true', result is determined
        // by the evaluation of the right argument.
        Value rightValue = evaluate(node.getRightArg(), bindings);
        return BooleanLiteral.valueOf(QueryEvaluationUtil.getEffectiveBooleanValue(rightValue));
    }

    /**
     * Evaluate an {@link And} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Or node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        try {
            Value leftValue = evaluate(node.getLeftArg(), bindings);
            if (QueryEvaluationUtil.getEffectiveBooleanValue(leftValue) == true) {
                // Left argument evaluates to true, we don't need to look any
                // further
                return BooleanLiteral.TRUE;
            }
        } catch (ValueExprEvaluationException e) {
            // Failed to evaluate the left argument. Result is 'true' when
            // the right argument evaluates to 'true', failure otherwise.
            Value rightValue = evaluate(node.getRightArg(), bindings);
            if (QueryEvaluationUtil.getEffectiveBooleanValue(rightValue) == true) {
                return BooleanLiteral.TRUE;
            } else {
                throw new ValueExprEvaluationException();
            }
        }
        // Left argument evaluated to 'false', result is determined
        // by the evaluation of the right argument.
        Value rightValue = evaluate(node.getRightArg(), bindings);
        return BooleanLiteral.valueOf(QueryEvaluationUtil.getEffectiveBooleanValue(rightValue));
    }

    /**
     * Evaluate a {@link Not} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Not node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value argValue = evaluate(node.getArg(), bindings);
        boolean argBoolean = QueryEvaluationUtil.getEffectiveBooleanValue(argValue);
        return BooleanLiteral.valueOf(!argBoolean);
    }

    /**
     * Evaluate a {@link Now} node. the value of 'now' is shared across the whole query and evaluation strategy
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Now node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        if (parentStrategy.sharedValueOfNow == null) {
            parentStrategy.sharedValueOfNow = node.evaluate(valueFactory);
        }
        return parentStrategy.sharedValueOfNow;
    }

    /**
     * Evaluate if the left and right arguments of the {@link SameTerm} node are equal
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(SameTerm node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value leftVal = evaluate(node.getLeftArg(), bindings);
        Value rightVal = evaluate(node.getRightArg(), bindings);
        return BooleanLiteral.valueOf(leftVal != null && leftVal.equals(rightVal));
    }

    /**
     * Evaluate a {@link Coalesce} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return the first {@link Value} that doesn't produce an error on evaluation
     * @throws ValueExprEvaluationException
     */
    private Value evaluate(Coalesce node, BindingSet bindings) throws ValueExprEvaluationException {
        for (ValueExpr expr : node.getArguments()) {
            try {
                Value result = evaluate(expr, bindings);
                if (result != null) return result;
                // return first result that does not produce an error on evaluation.
            } catch (QueryEvaluationException ignore) {
            }
        }
        throw new ValueExprEvaluationException("COALESCE arguments do not evaluate to a value: " + node.getSignature());
    }

    /**
     * Evaluates a Compare node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return the {@link Value} resulting from the comparison of the left and right arguments of the {@code node} using the comparison operator
     * of the {@code node}.
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Compare node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value leftVal = evaluate(node.getLeftArg(), bindings);
        Value rightVal = evaluate(node.getRightArg(), bindings);
        return BooleanLiteral.valueOf(QueryEvaluationUtil.compare(leftVal, rightVal, node.getOperator()));
    }

    /**
     * Evaluate a {@link MathExpr}
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return the {@link Value} of the math operation on the {@link Value}s return from evaluating the left and right arguments of the node
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(MathExpr node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        // Do the math
        Value leftVal = evaluate(node.getLeftArg(), bindings);
        Value rightVal = evaluate(node.getRightArg(), bindings);
        if (leftVal instanceof Literal && rightVal instanceof Literal) {
            return MathUtil.compute((Literal) leftVal, (Literal) rightVal, node.getOperator());
        }
        throw new ValueExprEvaluationException("Both arguments must be numeric literals");
    }

    /**
     * Evaluate an {@link If} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws QueryEvaluationException
     */
    private Value evaluate(If node, BindingSet bindings) throws QueryEvaluationException {
        Value result;
        boolean conditionIsTrue;
        try {
            Value value = evaluate(node.getCondition(), bindings);
            conditionIsTrue = QueryEvaluationUtil.getEffectiveBooleanValue(value);
        } catch (ValueExprEvaluationException e) {
            // in case of type error, if-construction should result in empty
            // binding.
            return null;
        }
        if (conditionIsTrue) {
            result = evaluate(node.getResult(), bindings);
        } else {
            result = evaluate(node.getAlternative(), bindings);
        }
        return result;
    }

    /**
     * Evaluate an {@link In} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(In node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value leftValue = evaluate(node.getArg(), bindings);
        // Result is false until a match has been found
        boolean result = false;
        // Use first binding name from tuple expr to compare values
        String bindingName = node.getSubQuery().getBindingNames().iterator().next();
        try (CloseableIteration<BindingSet, QueryEvaluationException> iter = parentStrategy.evaluate(node.getSubQuery(), bindings)) {
            while (result == false && iter.hasNext()) {
                BindingSet bindingSet = iter.next();
                Value rightValue = bindingSet.getValue(bindingName);
                result = leftValue == null && rightValue == null || leftValue != null
                        && leftValue.equals(rightValue);
            }
        }
        return BooleanLiteral.valueOf(result);
    }

    /**
     * Evaluate a {@link ListMemberOperator}
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(ListMemberOperator node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        List<ValueExpr> args = node.getArguments();
        Value leftValue = evaluate(args.get(0), bindings);
        boolean result = false;
        ValueExprEvaluationException typeError = null;
        for (int i = 1; i < args.size(); i++) {
            ValueExpr arg = args.get(i);
            try {
                Value rightValue = evaluate(arg, bindings);
                result = leftValue == null && rightValue == null;
                if (!result) {
                    result = QueryEvaluationUtil.compare(leftValue, rightValue, Compare.CompareOp.EQ);
                }
                if (result) {
                    break;
                }
            } catch (ValueExprEvaluationException caught) {
                typeError = caught;
            }
        }
        if (typeError != null && !result) {
            // cf. SPARQL spec a type error is thrown if the value is not in the
            // list and one of the list members caused a type error in the
            // comparison.
            throw typeError;
        }
        return BooleanLiteral.valueOf(result);
    }

    /**
     * Evaluate a {@link CompareAny} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(CompareAny node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value leftValue = evaluate(node.getArg(), bindings);
        // Result is false until a match has been found
        boolean result = false;
        // Use first binding name from tuple expr to compare values
        String bindingName = node.getSubQuery().getBindingNames().iterator().next();
        try (CloseableIteration<BindingSet, QueryEvaluationException> iter = parentStrategy.evaluate(node.getSubQuery(), bindings)) {
            while (result == false && iter.hasNext()) {
                BindingSet bindingSet = iter.next();
                Value rightValue = bindingSet.getValue(bindingName);
                try {
                    result = QueryEvaluationUtil.compare(leftValue, rightValue, node.getOperator());
                } catch (ValueExprEvaluationException e) {
                    // ignore, maybe next value will match
                }
            }
        }
        return BooleanLiteral.valueOf(result);
    }

    /**
     * Evaluate a {@link CompareAll} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(CompareAll node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        Value leftValue = evaluate(node.getArg(), bindings);
        // Result is true until a mismatch has been found
        boolean result = true;
        // Use first binding name from tuple expr to compare values
        String bindingName = node.getSubQuery().getBindingNames().iterator().next();
        try (CloseableIteration<BindingSet, QueryEvaluationException> iter = parentStrategy.evaluate(node.getSubQuery(), bindings)) {
            while (result == true && iter.hasNext()) {
                BindingSet bindingSet = iter.next();
                Value rightValue = bindingSet.getValue(bindingName);
                try {
                    result = QueryEvaluationUtil.compare(leftValue, rightValue, node.getOperator());
                } catch (ValueExprEvaluationException e) {
                    // Exception thrown by ValueCompare.isTrue(...)
                    result = false;
                }
            }
        }
        return BooleanLiteral.valueOf(result);
    }

    /**
     * Evaluate a {@link Exists} node
     * @param node the node to evaluate
     * @param bindings the set of named value bindings
     * @return
     * @throws ValueExprEvaluationException
     * @throws QueryEvaluationException
     */
    private Value evaluate(Exists node, BindingSet bindings) throws ValueExprEvaluationException, QueryEvaluationException {
        try (CloseableIteration<BindingSet, QueryEvaluationException> iter = parentStrategy.evaluate(node.getSubQuery(), bindings)) {
            return BooleanLiteral.valueOf(iter.hasNext());
        }
    }
}