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

import java.util.ArrayDeque ;
import java.util.Deque ;
import java.util.Iterator ;

import org.apache.jena.atlas.lib.StrUtils ;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.sparql.core.Prologue ;
import org.apache.jena.sparql.sse.Item ;
import org.apache.jena.sparql.sse.ItemList ;
import org.apache.jena.sparql.sse.builders.BuilderPrefixMapping ;

/** Resolve syntactic forms like (base ...) and (prefix...)
 *  where the syntax modifies the enclosed sub term.
 *  
 *  
 *  Forms:
 *    (FORM DECL... TERM) => where TERM is the result.
 *  Examples 
 *    (prefix (PREFIXES) TERM) => TERM with prefix names expanded
 *    (base IRI TERM) => TERM with IRIs resolved to absolute IRIs
 *  
 *  The DECL part can not itself have nested, independent forms
 *  unless a subclass (carefully) manages that. */

public class ParseHandlerResolver extends ParseHandlerForm
{
    private static final String prefixTag       = "prefix" ;
    private static final String baseTag         = "base" ;
    private PrefixMapping       topMap          = null ;
    private String              topBase         = null ;
    private Prologue            prologue        = null ; 
    private ItemList            declList        = null ;
    private Deque<Prologue>     state           = new ArrayDeque<>() ; // Previous prologues (not the current one)
    
    public ParseHandlerResolver(Prologue p)
    {
        prologue = p ;
    }
    
    @Override
    protected void declItem(ItemList list, Item item)
    {
        if ( list != declList )
            // Deeper
            return ;
        
        // Prefix - deeper than one.
        boolean isBase = list.get(0).isSymbol(baseTag) ; 
        boolean isPrefix = list.get(0).isSymbol(prefixTag) ;
        
        // Old state has already been saved.
        if ( isBase )
        {        
            if ( ! item.isNode() )
                throwException("(base ...): not an RDF node for the base.", item) ;
            if ( ! item.getNode().isURI() )
                throwException("(base ...): not an IRI for the base.", item) ;
    
            String baseIRI = item.getNode().getURI() ;
            prologue = prologue.sub(baseIRI) ;
            // Remeber first base seen 
            if ( topBase == null )
                topBase = baseIRI ;
            return ;
        }
        
        if ( isPrefix )
        {
            PrefixMapping newMappings = BuilderPrefixMapping.build(item) ; 
            prologue = prologue.sub(newMappings) ;
            // Remember first prefix mapping seen. 
            if( topMap == null )
                topMap = newMappings ;
            return ;
        }
        throwException("Inconsistent: "+list.shortString(), list) ;
    }
    
    @Override
    protected boolean endOfDecl(ItemList list, Item item)
    { 
        // Both (base...) and (prefix...) have one decl item 
        if ( declList == list && list.size() == 2 )
        {
            declList = null ;
            return true ;
        }
        return false ;
    }

    @Override
    protected boolean isForm(Item tag)
    {
        return tag.isSymbol(baseTag) || tag.isSymbol(prefixTag) ;
    }
    
    @Override
    protected void startForm(ItemList list)
    {
        // Remember the top of declaration
        declList = list ;
        state.push(prologue) ;
    }

    private void dump()
    {
        Iterator<Prologue> iter = state.iterator() ;
        for ( ; iter.hasNext() ; )
        {
            Prologue p = iter.next() ;
            System.out.println("  Prologue: "+p.getBaseURI()) ;
        }
    }
    
    @Override
    protected void finishForm(ItemList list)
    { 
        // Check list length
        prologue = state.pop() ;
        // Restore state 
        
        // Choose the result.
        if ( list.size() > 2 )
        {
            Item item = list.getLast() ;
            super.setFormResult(item) ;
        }
    }

    @Override
    public void emitIRI(int line, int column, String iriStr)
    { 
        iriStr = resolveIRI(iriStr, line, column) ;
        super.emitIRI(line, column, iriStr) ;
    }
    
    @Override
    public void emitPName(int line, int column, String pname)
    {
        if ( inFormDecl() )
        {
            // Record a faked PName.  Works with BuilderPrefixMapping
            Item item = Item.createSymbol(pname, line, column) ;
            listAdd(item) ;
            return ;
        }
        String iriStr = resolvePrefixedName(pname, line, column) ;
        super.emitIRI(line, column, iriStr) ;
    }

    @Override
    protected String resolvePrefixedName(String pname, int line, int column)
    { 
        if ( prologue.getPrefixMapping() == null )
            throwException("No prefix mapping for prefixed name: "+pname, line, column) ;
        
        if ( ! StrUtils.contains(pname, ":") )
            throwException("Prefixed name does not have a ':': "+pname, line, column) ;
        
        String uri =  prologue.expandPrefixedName(pname) ;
        if ( uri == null )
            throwException("Can't resolve prefixed name: "+pname, line, column) ;
        return uri ;
    }
    
    private String resolveIRI(String iriStr, int line, int column) 
    {
        if ( prologue.getResolver() != null )
            return prologue.getResolver().resolveToStringSilent(iriStr) ;
        return iriStr ;
    }
 
}
