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

import java.util.Iterator ;

import org.apache.jena.atlas.lib.Cache ;
import org.apache.jena.atlas.lib.CacheFactory ;
import org.apache.jena.graph.Node ;
import org.apache.jena.iri.IRI ;
import org.apache.jena.iri.IRIFactory ;
import org.apache.jena.iri.Violation ;
import org.apache.jena.riot.system.ErrorHandler ;
import org.apache.jena.riot.system.ErrorHandlerFactory ;
import org.apache.jena.riot.system.IRIResolver ;

public class CheckerIRI implements NodeChecker
{
    private boolean allowRelativeIRIs = false ;

    private ErrorHandler handler ;
    private IRIFactory iriFactory ;

    public CheckerIRI() {
        this(ErrorHandlerFactory.getDefaultErrorHandler(), IRIResolver.iriFactory()) ;
    }

    public CheckerIRI(ErrorHandler handler, IRIFactory iriFactory) {
        this.handler = handler ;
        this.iriFactory = iriFactory ;
    }
    
    @Override
    public boolean check(Node node, long line, long col)
    { return node.isURI() && checkURI(node, line, col) ; }

    // An LRU cache is slower.
    // An unbounded cache is fastest but does not scale.
    private final Cache<Node, IRI> cache = CacheFactory.createSimpleCache(5000) ;

    // abstract
    public final boolean checkURI(Node node, long line, long col) {
        if ( cache != null && cache.containsKey(node) )
            return true ;

        IRI iri = iriFactory.create(node.getURI()) ; // always works - no exceptions.
        boolean b = checkIRI(iri, line, col) ;
        // If OK, put in cache.
        if ( cache != null && b )
            cache.put(node, iri) ;
        return b ;
    }

    final public boolean checkIRI(IRI iri, long line, long col) {
        iriViolations(iri, handler, allowRelativeIRIs, true, line, col) ;
        return !iri.hasViolation(true) ;
    }

    /** Process violations on an IRI
     *  Calls the errorhandler on all errors and warnings (as warning).
     *  Assumes error handler throws exceptions on errors if needbe
     *  @param iri  IRI to check
     *  @param errorHandler The error handler to call on each warning or error.
     *   
     */
    public static void iriViolations(IRI iri, ErrorHandler errorHandler) {
        iriViolations(iri, errorHandler, -1L, -1L) ;
    }
    
    /** Process violations on an IRI
     *  Calls the errorhandler on all errors and warnings (as warning).
     *  Assumes error handler throws exceptions on errors if needbe
     *  @param iri  IRI to check
     *  @param errorHandler The error handler to call on each warning or error.
     *   
     */
    public static void iriViolations(IRI iri, ErrorHandler errorHandler, long line, long col) {
        iriViolations(iri, errorHandler, false, true, line, col) ;
    }
    
    /** Process violations on an IRI
     *  Calls the errorhandler on all errors and warnings (as warning).
     *  Assumes error handler throws exceptions on errors if needbe
     *  @param iri  IRI to check
     *  @param errorHandler The error handler to call on each warning or error.
     *  @param allowRelativeIRIs Allow relative URIs (discouraged)
     */
    private static void iriViolations(IRI iri, ErrorHandler errorHandler, boolean allowRelativeIRIs) {
        iriViolations(iri, errorHandler, allowRelativeIRIs, -1, -1) ;
    }

    /** Process violations on an IRI
     *  Calls the errorhandler on all errors and warnings (as warning).
     *  Assumes error handler throws exceptions on errors if needbe
     *  @param iri  IRI to check
     *  @param errorHandler The error handler to call on each warning or error.
     *  @param allowRelativeIRIs Allow relative URIs (discouraged)
     */
    private static void iriViolations(IRI iri, ErrorHandler errorHandler, boolean allowRelativeIRIs, long line, long col) {
        iriViolations(iri, errorHandler, allowRelativeIRIs, true, line, col) ;
    }

    /** Process violations on an IRI
     *  Calls the errorhandler on all errors and warnings (as warning).
     *  Assumes error handler throws exceptions on errors if needbe 
     */
    public static void iriViolations(IRI iri, ErrorHandler errorHandler, 
                                     boolean allowRelativeIRIs, 
                                     boolean includeIRIwarnings,
                                     long line, long col) {
        if ( !allowRelativeIRIs && iri.isRelative() )
            errorHandler.error("Relative IRI: " + iri, line, col) ;

        if ( iri.hasViolation(includeIRIwarnings) ) {
            Iterator<Violation> iter = iri.violations(includeIRIwarnings) ;

            boolean errorSeen = false ;
            boolean warningSeen = false ;

            // What to finally report.
            Violation vError = null ;
            Violation vWarning = null ;
            Violation xvSub = null ;

            for ( ; iter.hasNext() ; ) {
                Violation v = iter.next() ;
                int code = v.getViolationCode() ;
                boolean isError = v.isError() ;

                // Ignore these.
                if ( code == Violation.LOWERCASE_PREFERRED
                     || code == Violation.PERCENT_ENCODING_SHOULD_BE_UPPERCASE
                     || code == Violation.SCHEME_PATTERN_MATCH_FAILED )
                    continue ;

                // Anything we want to reprioritise?
                // [nothing at present]

                // Remember first error and first warning.
                if ( isError ) {
                    errorSeen = true ;
                    if ( vError == null )
                                         // Remember first error
                                         vError = v ;
                } else {
                    warningSeen = true ;
                    if ( vWarning == null )
                        vWarning = v ;
                }

                String msg = v.getShortMessage() ;
                String iriStr = iri.toString() ;

                // Ideally, we might want to output all messages relating to this IRI
                // then cause the error or continue.
                // But that's tricky given the current errorhandler architecture.
                
//                // Put out warnings for all IRI issues - later, exception for errors.
//                if (v.getViolationCode() == ViolationCodes.REQUIRED_COMPONENT_MISSING &&
//                    v.getComponent() == IRIComponents.SCHEME)
//                {
//                    if (! allowRelativeIRIs )
//                        handler.error("Relative URIs are not permitted in RDF: <"+iriStr+">", line, col);
//                } 
//                else
                {
                    if ( isError )
                        // IRI errors are warning at the level of parsing - they got through syntax checks.  
                        errorHandler.warning("Bad IRI: "+msg, line, col);
                    else
                        errorHandler.warning("Not advised IRI: "+msg, line, col);
                }
            }
            
//            // and report our choosen error.
//            if ( errorSeen || (warningsAreErrors && warningSeen) )
//            {
//                String msg = null ;
//                if ( vError != null ) msg = vError.getShortMessage() ;
//                if ( msg == null && vWarning != null ) msg = vWarning.getShortMessage() ;
//                if ( msg == null )
//                    handler.error("Bad IRI: <"+iri+">", line, col) ;
//                else
//                    handler.error("Bad IRI: "+msg, line, col) ;
//            }
        }
    }
}
