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

import org.apache.jena.atlas.io.IndentedWriter ;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.sparql.ARQInternalErrorException ;
import org.apache.jena.sparql.algebra.OpAsQuery ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.expr.* ;
import org.apache.jena.sparql.syntax.Element ;

/** Output expressions in SPARQL syntax */

public class FmtExprSPARQL
{
    static final int INDENT = 2 ;
    
    FmtExprARQVisitor visitor ; 

    public FmtExprSPARQL(IndentedWriter writer, SerializationContext cxt)
    {
        visitor = new FmtExprARQVisitor(writer, cxt) ;
    }
    
    // Top level writing of an expression.
    public void format(Expr expr)
    { expr.visit(visitor) ; } 
    
    public static void format(IndentedWriter out,Expr expr)
    { format(out, expr, null) ; }
    
    public static void format(IndentedWriter out, Expr expr, SerializationContext cxt)
    {
        FmtExprSPARQL fmt = new FmtExprSPARQL(out, cxt) ;
        fmt.format(expr) ;
    }

    private static class FmtExprARQVisitor implements ExprVisitor
    {
        IndentedWriter out ;
        SerializationContext context ;

        public FmtExprARQVisitor(IndentedWriter writer, PrefixMapping pmap)
        {
            this(writer, new SerializationContext(pmap , null)) ;
        }

        public FmtExprARQVisitor(IndentedWriter writer, SerializationContext cxt)
        {
            out = writer ;
            context = cxt ;
            if ( context == null )
                context = new SerializationContext() ;
        }

        @Override
        public void visit(ExprFunction0 expr)
        {
            if ( expr.getOpName() == null )
            {
                printInFunctionForm(expr) ;
                return ;
            }
            out.print("( ") ;
            out.print( expr.getOpName() ) ;
            out.print(" ") ;
        }

        
        @Override
        public void visit(ExprFunction1 expr)
        {
            if ( expr.getOpName() == null )
            {
                printInFunctionForm(expr) ;
                return ;
            }
            out.print("( ") ;
            out.print( expr.getOpName() ) ;
            out.print(" ") ;
            expr.getArg().visit(this) ;
            out.print(" )");
        }

        @Override
        public void visit(ExprFunction2 expr)
        {
            if ( expr.getOpName() == null )
            {
                printInFunctionForm(expr) ;
                return ;
            }
            out.print("( ") ;
            expr.getArg1().visit(this) ;
            out.print(" ") ;
            out.print( expr.getOpName() ) ;
            out.print(" ") ;
            expr.getArg2().visit(this) ;
            out.print(" )");
        }

        @Override
        public void visit(ExprFunction3 expr)
        {
            printInFunctionForm(expr) ;
        }


        @Override
        public void visit(ExprFunctionN func)
        {
            if ( func instanceof E_OneOf )
            {
                E_OneOf oneOf = (E_OneOf)func ;
                out.print("( ") ;
                oneOf.getLHS().visit(this) ;
                out.print(" IN ") ;
                printExprList(oneOf.getRHS()) ;
                out.print(" )") ;
                return ;
            }

            if ( func instanceof E_NotOneOf )
            {
                E_NotOneOf oneOf = (E_NotOneOf)func ;
                out.print("( ") ;
                oneOf.getLHS().visit(this) ;
                out.print(" NOT IN ") ;
                printExprList(oneOf.getRHS()) ;
                out.print(" )") ;
                return ;
            }
            printInFunctionForm(func) ;
        }

        private void printInFunctionForm(ExprFunction func)
        {
            out.print( func.getFunctionPrintName(context) ) ;
            printExprList(func.getArgs()) ;
        }
        
        private void printExprList(Iterable<Expr> exprs)
        {
            out.print("(") ;
            boolean first = true ;
            for ( Expr expr : exprs )
            {
                if ( expr == null )
                    break ; 
                if ( ! first )
                    out.print(", ") ;
                first = false ;
                expr.visit(this) ;
            }  
            out.print(")");
        }
        
        @Override
        public void visit(ExprFunctionOp funcOp)
        {
            String fn = funcOp.getFunctionPrintName(context) ;
            if ( funcOp instanceof E_NotExists )
                fn = "NOT EXISTS" ;
            else if ( funcOp instanceof E_Exists )
                fn = "EXISTS" ;
            else
                throw new ARQInternalErrorException("Unrecognized ExprFunctionOp: "+fn) ;
            
            FormatterElement fmtElt = new FormatterElement(out, context) ;
            out.print(fn) ;
            out.print(" ") ;
            int indent = out.getAbsoluteIndent() ;
            int currentCol = out.getCol() ;
            try {
                out.setAbsoluteIndent(currentCol);
                Element el = funcOp.getElement() ; 
                if ( el == null )
                    el = OpAsQuery.asQuery(funcOp.getGraphPattern()).getQueryPattern() ;
                el.visit(fmtElt) ;
            } finally { out.setAbsoluteIndent(indent); }
        }

        @Override
        public void visit(NodeValue nv)
        {
            out.print(nv.asQuotedString(context)) ;
        }

        @Override
        public void visit(ExprNone none) {
            out.print("NONE");
        }

        @Override
        public void visit(ExprVar nv)
        {
            String s = nv.getVarName() ;
            if ( Var.isBlankNodeVarName(s) )
            {
                // Return to a bNode via the bNode mapping of a variable.
                Var v = Var.alloc(s) ;
                out.print(context.getBNodeMap().asString(v) ) ;
            }
            else
            {
                // Print in variable form or as an aggregator expression
                out.print(nv.asSparqlExpr()) ;
            }
        }

        @Override
        public void visit(ExprAggregator eAgg)
        {
            out.print(eAgg.asSparqlExpr(context)) ;
        }
    }
}
