/*
 * 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.
 */

package org.apache.jena.sparql.sse.builders;

import java.util.HashMap ;
import java.util.Map ;

import org.apache.jena.graph.Node ;
import org.apache.jena.sparql.ARQInternalErrorException ;
import org.apache.jena.sparql.algebra.Op ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.core.VarExprList ;
import org.apache.jena.sparql.expr.* ;
import org.apache.jena.sparql.expr.aggregate.Aggregator ;
import org.apache.jena.sparql.expr.aggregate.AggregatorFactory ;
import org.apache.jena.sparql.sse.Item ;
import org.apache.jena.sparql.sse.ItemList ;
import org.apache.jena.sparql.sse.Tags ;
import org.apache.jena.sparql.util.NodeUtils ;

public class BuilderExpr
{
    // Build an expr list when they may also be a single expression.
    // Because expressions are themselves lists, this requires ExprLists to be explicitly tagged
    
    // (exprlist ...)
    // ((expr1) (expr2))
    // (expr))
    public static ExprList buildExprOrExprList(Item item)
    {
        if ( item.isTagged(Tags.tagExprList) )
            return buildExprList(item) ;
        
        if ( listOfLists(item) )
            return buildExprListUntagged(item.getList(), 0) ;
        
        Expr expr = buildExpr(item) ; 
        ExprList exprList = new ExprList(expr) ;
        return exprList ;
    }

    private static boolean listOfLists(Item item)
    {
        // Atom
        if ( ! item.isList() ) return false ;
        // List of atom (inc tagged)
        if ( ! item.getList().car().isList() ) return false ;
        // List of lists
        return true ;
    }

    public static ExprList buildExprList(Item item)
    {
        if ( ! item.isTagged(Tags.tagExprList) )
            BuilderLib.broken(item, "Not tagged exprlist") ;
        
        ItemList list = item.getList() ;
        return buildExprListUntagged(list, 1) ;
    }
    
    private static ExprList buildExprListUntagged(Item item)
    {
        return buildExprListUntagged(item.getList(), 0) ;
    }

    private static ExprList buildExprListUntagged(ItemList list, int idx)
    {
        ExprList exprList = new ExprList() ;
        for ( int i = idx ; i < list.size() ; i++ )
        {
            Item item = list.get(i) ;
            exprList.add(buildExpr(item)) ;
        }
        return exprList ;
    }
    
    public static Expr buildExpr(Item item)
    {
        // If this (bob) is stateless, we can have one and use always.
        BuilderExpr bob = new BuilderExpr() ;
        return bob.buildItem(item) ;
    }

    public static VarExprList buildNamedExprOrExprList(Item item)
    {
        if ( ! item.isList() )
            BuilderLib.broken(item, "Not a var expr list") ;
        
        ItemList list = item.getList() ;
        
        if ( list.isEmpty() )
            return new VarExprList() ;
        
        if ( list.car().isList() )
            // List of lists
            return buildNamedExprList(list) ;
        // One item
        return buildNamedExpr(item) ;
    }
    
    public static VarExprList buildNamedExprList(ItemList list)
    {
        VarExprList x = new VarExprList() ;
        for ( Item item : list )
            buildNamedExpr(item, x) ;
        return x ;
    }
    
    public static VarExprList buildNamedExpr(Item item)
    {
        VarExprList varExprList = new VarExprList() ;
        buildNamedExpr(item, varExprList) ;
        return varExprList ;
    }
    
    private static void buildNamedExpr(Item item, VarExprList varExprList)
    {
        if ( item.isNode() )
        {
            Var v = BuilderNode.buildVar(item) ;
            varExprList.add(v) ;
            return ;
        }
        if ( !item.isList() || item.getList().size() != 2 )
            BuilderLib.broken(item, "Not a var or var/expression pair") ;

        ItemList list = item.getList() ;

        if ( list.size() == 1 )
        {
            Var v = BuilderNode.buildVar(list.car()) ;
            varExprList.add(v) ;
            return ;
        }
        
        if ( list.size() != 2 )
            BuilderLib.broken(list, "Not a var or var/expression pair") ;
        Var var = BuilderNode.buildVar(list.get(0)) ;
        Expr expr = BuilderExpr.buildExpr(list.get(1)) ;
        varExprList.add(var, expr) ;  
    }
    
    protected Map<String, Build> dispatch = new HashMap<>() ;
    public Expr buildItem(Item item)
    {
        Expr expr = null ;
    
        if ( item.isList() )
        {
            ItemList list = item.getList() ;
            
            if ( list.size() == 0 )
                BuilderLib.broken(item, "Empty list for expression") ;
            
            Item head = list.get(0) ;
            
            if ( head.isNode() )
            {
                if ( head.getNode().isVariable() && list.size() == 1 )
                {
                    // The case of (?z)
                    return new ExprVar(Var.alloc(head.getNode())) ;
                }
                return buildFunctionCall(list) ;
            }
            else if ( head.isList() )
                BuilderLib.broken(item, "Head is a list") ;
            else if ( head.isSymbol() )
            {
                if ( item.isTagged(Tags.tagExpr) )
                {
                    BuilderLib.checkLength(2, list, "Wrong length: "+item.shortString()) ;
                    item = list.get(1) ;
                    return buildItem(item) ;
                }
                
                return buildKnownFunction(list) ;                
            }
            throw new ARQInternalErrorException() ;
        }
    
        if ( item.isNode() )
        {
            if ( Var.isVar(item.getNode()) )
                return new ExprVar(Var.alloc(item.getNode())) ;
            return NodeValue.makeNode(item.getNode()) ;
        }
    
        if ( item.isSymbolIgnoreCase(Tags.tagTrue) )
            return NodeValue.TRUE ;
        if ( item.isSymbolIgnoreCase(Tags.tagFalse) )
            return NodeValue.FALSE ;
        
        BuilderLib.broken(item, "Not a list or a node or recognized symbol: "+item) ;
        return null ;
    }

    public BuilderExpr()
    {
        dispatch.put(Tags.tagRegex, buildRegex) ;
        dispatch.put(Tags.symEQ, buildEQ) ;
        dispatch.put(Tags.tagEQ, buildEQ) ;
        dispatch.put(Tags.symNE, buildNE) ;
        dispatch.put(Tags.tagNE, buildNE) ;
        dispatch.put(Tags.symGT, buildGT) ;
        dispatch.put(Tags.tagGT, buildGT) ;
        dispatch.put(Tags.symLT, buildLT) ;
        dispatch.put(Tags.tagLT, buildLT) ;
        dispatch.put(Tags.symLE, buildLE) ;
        dispatch.put(Tags.tagLE, buildLE) ;
        dispatch.put(Tags.symGE, buildGE) ;
        dispatch.put(Tags.tagGE, buildGE) ;
        dispatch.put(Tags.symOr, buildOr) ;
        dispatch.put(Tags.tagOr, buildOr) ;
        dispatch.put(Tags.symAnd, buildAnd) ;
        dispatch.put(Tags.tagAnd, buildAnd) ;
        dispatch.put(Tags.symPlus, buildPlus) ;
        dispatch.put(Tags.tagAdd,  buildPlus) ;
        dispatch.put(Tags.symMinus, buildMinus) ;
        dispatch.put(Tags.tagSubtract, buildMinus) ;
        dispatch.put(Tags.tagMinus, buildMinus) ;    // Not to be confused with Op for SPARQL MINUS

        dispatch.put(Tags.tagUnaryPlus, buildUnaryPlus) ;
        dispatch.put(Tags.tagUnaryMinus, buildUnaryMinus) ;
        
        dispatch.put(Tags.symMult, buildMult) ;
        dispatch.put(Tags.tagMultiply, buildMult) ;
        
        dispatch.put(Tags.symDiv, buildDiv) ;
        dispatch.put(Tags.tagDivide, buildDiv) ;
        
        dispatch.put(Tags.tagNot, buildNot) ;   // Same builders for (not ..) and (! ..)
        dispatch.put(Tags.symNot, buildNot) ;
        
        dispatch.put(Tags.tagStr, buildStr) ;
        dispatch.put(Tags.tagStrLang, buildStrLang) ;
        dispatch.put(Tags.tagStrDatatype, buildStrDatatype) ;

        dispatch.put(Tags.tagYear, buildYear) ;
        dispatch.put(Tags.tagMonth, buildMonth) ;
        dispatch.put(Tags.tagDay, buildDay) ;
        dispatch.put(Tags.tagHours, buildHours) ;
        dispatch.put(Tags.tagMinutes, buildMinutes) ;
        dispatch.put(Tags.tagSeconds, buildSeconds) ;
        dispatch.put(Tags.tagTimezone, buildTimezone) ;
        dispatch.put(Tags.tagTZ, buildTZ) ;
        
        dispatch.put(Tags.tagRand, buildRand) ;
        dispatch.put(Tags.tagNow, buildNow) ;
        dispatch.put(Tags.tagUUID, buildUUID) ;
        dispatch.put(Tags.tagStrUUID, buildStrUUID) ;
        dispatch.put(Tags.tagVersion, buildVersion) ;
        
        dispatch.put(Tags.tagMD5, buildMD5) ;
        dispatch.put(Tags.tagSHA1, buildSHA1) ;
        dispatch.put(Tags.tagSHA224, buildSHA224) ;
        dispatch.put(Tags.tagSHA256, buildSHA256) ;
        dispatch.put(Tags.tagSHA384, buildSHA384) ;
        dispatch.put(Tags.tagSHA512, buildSHA512) ;

        dispatch.put(Tags.tagStrlen, buildStrlen) ;
        dispatch.put(Tags.tagSubstr, buildSubstr) ;
        dispatch.put(Tags.tagReplace, buildStrReplace) ;
        dispatch.put(Tags.tagStrUppercase, buildStrUppercase) ;
        dispatch.put(Tags.tagStrLowercase, buildStrLowercase) ;
        dispatch.put(Tags.tagStrEnds, buildStrEnds) ;
        dispatch.put(Tags.tagStrStarts, buildStrStarts) ;
        dispatch.put(Tags.tagStrBefore, buildStrBefore) ;
        dispatch.put(Tags.tagStrAfter, buildStrAfter) ;
        dispatch.put(Tags.tagStrContains, buildStrContains) ;
        dispatch.put(Tags.tagStrEncodeForURI, buildStrEncode) ;
        dispatch.put(Tags.tagConcat, buildConcat) ;
                
        dispatch.put(Tags.tagNumAbs, buildNumAbs) ;
        dispatch.put(Tags.tagNumRound, buildNumRound) ;
        dispatch.put(Tags.tagNumCeiling, buildNumCeiling) ;
        dispatch.put(Tags.tagNumFloor, buildNumFloor) ;
        dispatch.put(Tags.tagIsNumeric, buildIsNumeric) ;
        
        dispatch.put(Tags.tagLang, buildLang) ;
        dispatch.put(Tags.tagLangMatches, buildLangMatches) ;
        dispatch.put(Tags.tagSameTerm, buildSameTerm) ;
        dispatch.put(Tags.tagDatatype, buildDatatype) ;
        dispatch.put(Tags.tagBound, buildBound) ;
        dispatch.put(Tags.tagCoalesce, buildCoalesce) ;
        dispatch.put(Tags.tagConcat, buildConcat) ;
        dispatch.put(Tags.tagIf, buildConditional) ;
        dispatch.put(Tags.tagIsIRI, buildIsIRI) ;
        dispatch.put(Tags.tagIsURI, buildIsURI) ;
        dispatch.put(Tags.tagIsBlank, buildIsBlank) ;
        dispatch.put(Tags.tagIsLiteral, buildIsLiteral) ;
        dispatch.put(Tags.tagExists, buildExists) ;
        dispatch.put(Tags.tagNotExists, buildNotExists) ;
        
        dispatch.put(Tags.tagBNode, buildBNode) ;
        dispatch.put(Tags.tagIri, buildIri) ;
        dispatch.put(Tags.tagUri, buildUri) ;
        
        dispatch.put(Tags.tagIn, buildIn) ;
        dispatch.put(Tags.tagNotIn, buildNotIn) ;
        
        dispatch.put(Tags.tagCall, buildCall) ;
        
        dispatch.put(Tags.tagCount, buildCount) ;
        dispatch.put(Tags.tagSum, buildSum) ;
        dispatch.put(Tags.tagMin, buildMin) ;
        dispatch.put(Tags.tagMax, buildMax) ;
        dispatch.put(Tags.tagAvg, buildAvg) ;
        dispatch.put(Tags.tagSample, buildSample) ;
        dispatch.put(Tags.tagGroupConcat, buildGroupConcat) ;
        dispatch.put(Tags.tagAgg,  buildCustomAggregate) ;
    }

    // See exprbuilder.rb

    static public interface Build { Expr make(ItemList list) ; }
    
    protected Build findBuild(String str)
    {
        for ( String key : dispatch.keySet() )
        {
            if ( str.equalsIgnoreCase( key ) )    // ???
            {
                return dispatch.get( key );
            }
        }
        return null ;
    }
    
    protected Expr buildKnownFunction(ItemList list)
    {
        if ( list.size() == 0 )
            BuilderLib.broken(list, "Empty list for expression") ;
    
        Item item = list.get(0) ;
        String tag = item.getSymbol() ;
        if ( tag == null )
            BuilderLib.broken(item, "Null tag") ;
    
        Build b = findBuild(tag) ;
        if ( b == null )
            BuilderLib.broken(item, "No known symbol for "+tag) ;
        return b.make(list) ;
    }

    protected static Expr buildFunctionCall(ItemList list)
    {
        Item head = list.get(0) ;
        Node node = head.getNode() ;
        if ( node.isBlank() )
            BuilderLib.broken(head, "Blank node for function call!") ;
        if ( node.isLiteral() )
            BuilderLib.broken(head, "Literal node for function call!") ;
        ExprList args = buildExprListUntagged(list, 1) ;
        // Args
        return new E_Function(node.getURI(), args) ;
    }

    // ---- Dispatch objects
    // Can assume the tag is right (i.e. dispatched correctly) 
    // Specials
    
    final protected Build buildRegex = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, 4, list, "Regex: wanted 2 or 3 arguments") ;
            Expr expr = buildExpr(list.get(1)) ;
            Expr pattern = buildExpr(list.get(2)) ;
            Expr flags = null ;
            if ( list.size() != 3 )
                flags = buildExpr(list.get(3)) ;
            
            return new E_Regex(expr, pattern, flags) ;
        }
    };

    // Special
    final protected Build buildPlus = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, 3, list, "+: wanted 1 or 2 arguments") ;
            if ( list.size() == 2 )
            {
                Expr ex = buildExpr(list.get(1)) ;
                return new E_UnaryPlus(ex) ;
            }
            
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_Add(left, right) ;
        }
    };
    
    final protected Build buildUnaryPlus = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "unaryplus: wanted 1 argument") ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_UnaryPlus(ex) ;
        }
    };

    final protected Build buildMinus = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, 3, list, "-: wanted 1 or 2 arguments") ;
            if ( list.size() == 2 )
            {
                Expr ex = buildExpr(list.get(1)) ;
                return new E_UnaryMinus(ex) ;
            }
            
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_Subtract(left, right) ;
        }
    };
    
    final protected Build buildUnaryMinus = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "unaryminus: wanted 1 argument") ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_UnaryMinus(ex) ;
        }
    };

    private static int numArgs(ItemList list) { return list.size()-1 ; } 
    
    final protected Build buildEQ = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "=: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_Equals(left, right) ;
        }
    };

    final protected Build buildNE = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "!=: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_NotEquals(left, right) ;
        }
    };

    final protected Build buildGT = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, ">: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_GreaterThan(left, right) ;
        }
    };

    final protected Build buildLT = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "<: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_LessThan(left, right) ;
        }
    };

    final protected Build buildLE = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "<=: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_LessThanOrEqual(left, right) ;
        }
    };

    final protected Build buildGE = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, ">=: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_GreaterThanOrEqual(left, right) ;
        }
    };

    final protected Build buildOr = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "||: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_LogicalOr(left, right) ;
        }
    };

    final protected Build buildAnd = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "&&: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_LogicalAnd(left, right) ;
        }
    };

    final protected Build buildMult = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "*: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_Multiply(left, right) ;
        }
    };

    final protected Build buildDiv = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "/: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_Divide(left, right) ;
        }
    };

    final protected Build buildNot = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "!: wanted 1 arguments: got :"+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_LogicalNot(ex) ;
        }
    };

    final protected Build buildStr = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "str: wanted 1 arguments: got :"+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_Str(ex) ;
        }
    };

    final protected Build buildStrLang = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "strlang: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            return new E_StrLang(ex1, ex2) ;
        }
    };

    final protected Build buildStrDatatype = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "strlang: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            return new E_StrDatatype(ex1, ex2) ;
        }
    };
    
    final protected Build buildRand = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(1, list, "rand: wanted 0 arguments: got: "+numArgs(list)) ;
            return new E_Random() ;
        }
    };
    
    final protected Build buildYear = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "year: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeYear(ex) ; 
        }
    } ;
    
    final protected Build buildMonth = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "month: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeMonth(ex) ; 
        }
    } ;

    final protected Build buildDay = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "day: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeDay(ex) ; 
        }
    } ;

    final protected Build buildHours = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "hours: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeHours(ex) ; 
        }
    } ;

    final protected Build buildMinutes = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "minutes: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeMinutes(ex) ; 
        }
    } ;

    final protected Build buildSeconds = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "seconds: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeSeconds(ex) ; 
        }
    } ;
    
    final protected Build buildTimezone = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "timezone: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeTimezone(ex) ; 
        }
    } ;
    
    final protected Build buildTZ = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "TZ: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_DateTimeTZ(ex) ; 
        }
    } ;

    final protected Build buildNow = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(1, list, "now: wanted 0 arguments: got: "+numArgs(list)) ;
            return new E_Now() ; 
        }
    } ;
    
    final protected Build buildUUID = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(1, list, "uuid: wanted 0 arguments: got: "+numArgs(list)) ;
            return new E_UUID() ; 
        }
    } ;
    
    final protected Build buildStrUUID = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(1, list, "struuid: wanted 0 arguments: got: "+numArgs(list)) ;
            return new E_StrUUID() ; 
        }
    } ;
    
    final protected Build buildVersion = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(1, list, "version: wanted 0 arguments: got: "+numArgs(list)) ;
            return new E_Version() ; 
        }
    } ;

    final protected Build buildMD5 = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "md5: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_MD5(ex) ; 
        }
    } ;
    
    final protected Build buildSHA1 = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "md5: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_SHA1(ex) ; 
        }
    } ;
    
    final protected Build buildSHA224 = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "md5: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_SHA224(ex) ; 
        }
    } ;
    
    final protected Build buildSHA256 = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "md5: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_SHA256(ex) ; 
        }
    } ;
    
    final protected Build buildSHA384 = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "md5: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_SHA384(ex) ; 
        }
    } ;

    final protected Build buildSHA512 = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "md5: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_SHA512(ex) ; 
        }
    } ;
    
    final protected Build buildStrlen = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "strlen: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_StrLength(ex) ; 
        }
    } ;
    
    final protected Build buildSubstr = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3,4, list, "substr: wanted 2 or 3 arguments: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            Expr x = buildExpr(list.get(2)) ;
            Expr y = null ;
            if ( list.size() == 4 )
                y = buildExpr(list.get(3)) ;
            return new E_StrSubstring(ex, x, y) ; 
        }
    } ;
    
    final protected Build buildStrReplace = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(4, 5, list, "replace: wanted 3 or 4 arguments: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            Expr x = buildExpr(list.get(2)) ;
            Expr y = buildExpr(list.get(3)) ;
            Expr z = null ;
            if ( list.size() == 5 )
                z = buildExpr(list.get(4)) ;
            return new E_StrReplace(ex, x, y, z) ; 
        }
    } ;
    
    final protected Build buildStrUppercase = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "ucase: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_StrUpperCase(ex) ; 
        }
    } ;
    
    final protected Build buildStrLowercase = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "lcase: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_StrLowerCase(ex) ; 
        }
    } ;
    
    final protected Build buildStrEnds = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "strends: wanted 2 arguments: got: "+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            return new E_StrEndsWith(ex1, ex2) ; 
        }
    } ;
    
    final protected Build buildStrStarts = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {     
            BuilderLib.checkLength(3, list, "strstarts: wanted 2 arguments: got: "+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            return new E_StrStartsWith(ex1, ex2) ; 
        }
    } ;

    final protected Build buildStrBefore = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {     
            BuilderLib.checkLength(3, list, "strbefore: wanted 2 arguments: got: "+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            return new E_StrBefore(ex1, ex2) ; 
        }
    } ;
    
    final protected Build buildStrAfter = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {     
            BuilderLib.checkLength(3, list, "strafter: wanted 2 arguments: got: "+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            return new E_StrAfter(ex1, ex2) ; 
        }
    } ;
    

    
    final protected Build buildStrContains = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "contains: wanted 2 arguments: got: "+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            return new E_StrContains(ex1, ex2) ; 
        }
    } ;
    
    final protected Build buildStrEncode = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "encode: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_StrEncodeForURI(ex) ; 
        }
    } ;
    
    final protected Build buildNumAbs = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "abs: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_NumAbs(ex) ; 
        }
    } ;
    
    final protected Build buildNumRound = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "round: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_NumRound(ex) ; 
        }
    } ;
    
    final protected Build buildNumCeiling = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "ceiling: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_NumCeiling(ex) ; 
        }
    } ;
    
    final protected Build buildNumFloor = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "floor: wanted 1 argument: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_NumFloor(ex) ; }
    } ;

    final protected Build buildLang = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "lang: wanted 1 arguments: got :"+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_Lang(ex) ;
        }
    };

    final protected Build buildLangMatches = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "langmatches: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_LangMatches(left, right) ;
        }
    };

    final protected Build buildSameTerm = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(3, list, "sameterm: wanted 2 arguments: got :"+numArgs(list)) ;
            Expr left = buildExpr(list.get(1)) ;
            Expr right = buildExpr(list.get(2)) ;
            return new E_SameTerm(left, right) ;
        }
    };

    final protected Build buildDatatype = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "datatype: wanted 1 arguments: got :"+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_Datatype(ex) ;
        }
    };

    final protected Build buildBound = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "bound: wanted 1 arguments: got :"+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_Bound(ex) ;
        }
    };

    final protected Build buildCoalesce = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            ExprList exprs = buildExprListUntagged(list, 1) ;
            return new E_Coalesce(exprs) ;
        }
    };

    final protected Build buildConcat = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            ExprList exprs = buildExprListUntagged(list, 1) ;
            return new E_StrConcat(exprs) ;
        }
    };

    final protected Build buildConditional = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(4, list, "IF: wanted 3 arguments: got :"+numArgs(list)) ;
            Expr ex1 = buildExpr(list.get(1)) ;
            Expr ex2 = buildExpr(list.get(2)) ;
            Expr ex3 = buildExpr(list.get(3)) ;
            return new E_Conditional(ex1, ex2, ex3) ;
        }
    };

    final protected Build buildIsIRI = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "isIRI: wanted 1 arguments: got :"+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_IsIRI(ex) ;
        }
    };

    final protected Build buildIsURI = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "isURI: wanted 1 arguments: got :"+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_IsURI(ex) ;
        }
    };

    final protected Build buildIsBlank = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "isBlank: wanted 1 arguments: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_IsBlank(ex) ;
        }
    };

    final protected Build buildIsLiteral = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "isLiteral: wanted 1 arguments: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_IsLiteral(ex) ;
        }
    };
    
    final protected Build buildIsNumeric = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "isNumeric: wanted 1 arguments: got: "+numArgs(list)) ;
            Expr ex = buildExpr(list.get(1)) ;
            return new E_IsNumeric(ex) ;
        }
    };
    
    final protected Build buildExists = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "exists: wanted 1 arguments: got: "+numArgs(list)) ;
            Op op = BuilderOp.build(list.get(1)) ;
            return new E_Exists(op) ;
        }
    };
    
    final protected Build buildNotExists = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "notexists: wanted 1 arguments: got: "+numArgs(list)) ;
            Op op = BuilderOp.build(list.get(1)) ;
            return new E_NotExists(op) ;
        }
    };
    
    final protected Build buildBNode = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(1, 2, list, "bnode: wanted 0 or 1 arguments: got: "+numArgs(list)) ;
            if ( list.size() == 1 )
                return new E_BNode() ;
            
            Expr expr = buildExpr(list.get(1)) ;
            return new E_BNode(expr) ;
        }
    };
    
    final protected Build buildIri = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "iri: wanted 1 argument: got: "+numArgs(list)) ;
            Expr expr = buildExpr(list.get(1)) ;
            return new E_IRI(expr) ;
        }
    };
    
    final protected Build buildUri = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "uri: wanted 1 argument: got: "+numArgs(list)) ;
            Expr expr = buildExpr(list.get(1)) ;
            return new E_URI(expr) ;
        }
    };
    
    
    
    final protected Build buildIn = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLengthAtLeast(1, list, "in: wanted 1 or more arguments: got: "+numArgs(list)) ;
            Item lhs = list.car() ;
            Expr expr = buildExpr(list.get(1)) ;
            ExprList eList = buildExprListUntagged(list, 2) ;
            return new E_OneOf(expr, eList) ;
        }
    };
    
    final protected Build buildNotIn = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLengthAtLeast(1, list, "notin: wanted 1 or more arguments: got: "+numArgs(list)) ;
            Item lhs = list.car() ;
            Expr expr = buildExpr(list.get(1)) ;
            ExprList eList = buildExprListUntagged(list, 2) ;
            return new E_NotOneOf(expr, eList) ;
        }
    };
    
    
    final protected Build buildCall = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            ExprList args = buildExprListUntagged(list, 1) ;
            // Args
            return new E_Call(args) ;
        }
    };

    // ---- Aggregate functions
    // (count)
    // (count distinct)
    // (count ?var)
    // (count distinct ?var)
    // Need a canonical name for the variable that will be set by the aggregation.
    // Aggregator.getVarName.
    
    static boolean startsWithDistinct(ItemList x) 
    {
        if ( x.size() > 0 && x.car().isSymbol(Tags.tagDistinct) )
            return true ;
        return false ;
    }
    
    // All the one expression cases
    static abstract class BuildAggCommon implements Build
    {
        @Override
        public final Expr make(ItemList list)
        {
            ItemList x = list.cdr();    // drop "sum"
            boolean distinct = startsWithDistinct(x) ;
            if ( distinct )
                x = x.cdr();
            BuilderLib.checkLength(1, x, "Broken syntax: "+list.shortString()) ;
            // (sum ?var) 
            Expr expr = buildExpr(x.get(0)) ;
            return make(distinct, expr) ;
        }

        public abstract Expr make(boolean distinct, Expr expr) ;
    }
    
    final protected Build buildCount = new Build()
    {
        @Override
        public Expr make(final ItemList list)
        {
            ItemList x = list.cdr();    // drop "count"
            boolean distinct = startsWithDistinct(x) ;
            if ( distinct )
                x = x.cdr();
            
            BuilderLib.checkLength(0, 1, x, "Broken syntax: "+list.shortString()) ;
            
            Aggregator agg = null ;
            if ( x.size() == 0 )
                agg = AggregatorFactory.createCount(distinct) ;
            else
            {
                Expr expr = BuilderExpr.buildExpr(x.get(0)) ;
                agg = AggregatorFactory.createCountExpr(distinct, expr) ;
            }
            return new ExprAggregator(null, agg) ; 
        }
    };
    
    final protected Build buildSum = new BuildAggCommon()
    {
        @Override
        public Expr make(boolean distinct, Expr expr)
        {
            Aggregator agg = AggregatorFactory.createSum(distinct, expr) ;
            return new ExprAggregator(null, agg) ; 
        }
    };
    
    final protected Build buildMin = new BuildAggCommon()
    {
        @Override
        public Expr make(boolean distinct, Expr expr)
        {
            Aggregator agg = AggregatorFactory.createMin(distinct, expr) ;
            return new ExprAggregator(null, agg) ; 
        }
    };
    
    final protected Build buildMax = new BuildAggCommon()
    {
        @Override
        public Expr make(boolean distinct, Expr expr)
        {
            Aggregator agg = AggregatorFactory.createMax(distinct, expr) ;
            return new ExprAggregator(null, agg) ; 
        }
    };

    final protected Build buildAvg = new BuildAggCommon()
    {
        @Override
        public Expr make(boolean distinct, Expr expr)
        {
            Aggregator agg = AggregatorFactory.createAvg(distinct, expr) ;
            return new ExprAggregator(null, agg) ; 
        }
    };

    final protected Build buildSample = new BuildAggCommon()
    {
        @Override
        public Expr make(boolean distinct, Expr expr)
        {
            Aggregator agg = AggregatorFactory.createSample(distinct, expr) ;
            return new ExprAggregator(null, agg) ; 
        }
    };
    
    final protected Build buildGroupConcat = new Build()
    {
        @Override
        public Expr make(final ItemList list)
        {
            ItemList x = list.cdr();    // drop "group_concat"
            boolean distinct = startsWithDistinct(x) ;
            if ( distinct )
                x = x.cdr();
            
            // Complex syntax:
            // (groupConcat (separator "string) expr )
            if ( x.size() == 0 )
                BuilderLib.broken(list, "Broken syntax: "+list.shortString()) ;
            String separator = null ;
            if ( x.get(0).isTagged(Tags.tagSeparator))
            {
                // What about ORDERED BY
                ItemList y = x.get(0).getList() ;
                BuilderLib.checkLength(2, y, "Broken syntax: "+list) ;
                Node n = y.get(1).getNode() ;
                if ( ! NodeUtils.isSimpleString(n) )
                    BuilderLib.broken(y, "Need string for separator: "+y) ;
                separator = n.getLiteralLexicalForm() ;
                x = x.cdr();
            }
            
            Expr expr = buildExpr(x.get(0)) ;
            Aggregator agg = AggregatorFactory.createGroupConcat(distinct, expr, separator, null) ;
            return new ExprAggregator(null, agg) ; 
        }
    };
    
    final protected Build buildCustomAggregate = new Build() {
        @Override
        public Expr make(final ItemList list)
        {
            ItemList x = list.cdr();    // drop "agg"
            if ( x.size() == 0 )
                BuilderLib.broken(list, "Missing IRI for aggregate") ;
            
            Item z = x.car() ;
            if ( ! z.isNodeURI() )
                BuilderLib.broken(list, "Not an IRI for aggregate: "+z) ;
            x = x.cdr() ;
            boolean distinct = startsWithDistinct(x) ;
            if ( distinct )
                x = x.cdr();
            ExprList e = buildExprListUntagged(x, 0) ;
            Aggregator agg = AggregatorFactory.createCustom(z.getNode().getURI(), distinct, e) ;
            return new ExprAggregator(null, agg) ; 
        }
    } ;
    
    final protected Build buildAggNull = new Build()
    {
        @Override
        public Expr make(ItemList list)
        {
            BuilderLib.checkLength(1, list, "Broken syntax: "+list.shortString()) ;
            return new ExprAggregator(null, AggregatorFactory.createAggNull()) ;
        }
    };
}
