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

import static org.apache.jena.riot.SysRIOT.fmtMessage ;
import org.apache.jena.riot.RiotException ;
import org.apache.jena.riot.RiotParseException ;
import org.apache.jena.riot.SysRIOT ;
import org.slf4j.Logger ;

public class ErrorHandlerFactory
{
    static public final Logger stdLogger = SysRIOT.getLogger() ;
    static public final Logger noLogger = null ;

    /** Standard error handler - logs to stdLogger */
    static public final ErrorHandler errorHandlerStd          = errorHandlerStd(stdLogger) ;

    /** Error handler (no warnings) - logs to stdLogger */
    static public final ErrorHandler errorHandlerNoWarnings   = errorHandlerIgnoreWarnings(stdLogger) ;

    /** Strict error handler - logs to stdLogger - exceptions for warnings */
    static public final ErrorHandler errorHandlerStrict       = errorHandlerStrict(stdLogger) ;

    /** Warning error handler - logs to stdLogger - messages for warnings and some errors */
    static public final ErrorHandler errorHandlerWarn         = errorHandlerWarning(stdLogger) ;

    /** Silent error handler : ignores warnings, throws exceptions for errors */
    static public final ErrorHandler errorHandlerNoLogging    = errorHandlerSimple() ;

    /** Silent, strict error handler */
    static public final ErrorHandler errorHandlerStrictNoLogging    = errorHandlerStrictSilent() ;

    /** Silent, strict error handler, no logging */
    public static ErrorHandler errorHandlerStrictSilent()           { return new ErrorHandlerStrict(noLogger) ; }

    /** Strict error handler, with logging */
    public static ErrorHandler errorHandlerStrict(Logger log)       { return new ErrorHandlerStrict(log) ; }

    /** An error handler that logs messages, then throws exceptions for errors but not warnings */
    public static ErrorHandler errorHandlerStd(Logger log)          { return new ErrorHandlerStd(log) ; }

    /** An error handler that logs error and fatal messages, but not warnings */
    public static ErrorHandler errorHandlerIgnoreWarnings(Logger log)   { return new ErrorHandlerIgnoreWarnings(log) ; }

    /** An error handler that logs messages for errors and warnings and attempts to carry on */
    public static ErrorHandler errorHandlerWarning(Logger log)      { return new ErrorHandlerWarning(log) ; }

    /** Ignores warnings, throws exceptions for errors */
    public static ErrorHandler errorHandlerSimple()                 { return new ErrorHandlerSimple() ; }

    /** Logs warnings and errors while tracking the counts of each and optionally throwing exceptions when errors and/or warnings are encounted */
    public static ErrorHandlerTracking errorHandlerTracking(Logger log, boolean failOnError, boolean failOnWarning)
    { return new ErrorHandlerTracking(log, failOnError, failOnWarning); }

    /** @deprecated Use {#errorHandlerExceptionOnError} */
    @Deprecated
    public static ErrorHandler errorHandlerDetailed()               { return new ErrorHandlerRiotParseErrors() ; }

    /**
     * An error handler that throws a {@link RiotParseException}, hence it
     * exposes the details of errors.
     */
    public static ErrorHandler errorHandlerExceptionOnError()       { return new ErrorHandlerRiotParseErrors() ; }

    /**
     * An error handler that throws exceptions in all cases.
     */
    public static ErrorHandler errorHandlerExceptions()        { return new ErrorHandlerRiotParseException() ; }

    /**
     * An error handler that logs warnings and throws exceptions on error and fatal.
     */
    public static ErrorHandler errorHandlerWarnOrExceptions(Logger logger) { return new ErrorHandlerWarnOrExceptions(logger) ; }

    private static ErrorHandler defaultErrorHandler = errorHandlerStd ;
    /** Get the current default error handler */
    public static ErrorHandler getDefaultErrorHandler() { return defaultErrorHandler ; }

    /** Set the current default error handler - use carefully, mainly for use in testing */
    public static void setDefaultErrorHandler(ErrorHandler errorHandler) { defaultErrorHandler = errorHandler ; }

    /** Messages to a logger. This is not an ErrorHandler */
    private static class ErrorLogger {
        protected final Logger log ;

        public ErrorLogger(Logger log) {
            this.log = log ;
        }

        /** report a warning */
        public void logWarning(String message, long line, long col) {
            if ( log != null )
                log.warn(fmtMessage(message, line, col)) ;
        }

        /** report an error */
        public void logError(String message, long line, long col) {
            if ( log != null )
                log.error(fmtMessage(message, line, col)) ;
        }

        /** report a catastrophic error */
        public void logFatal(String message, long line, long col) {
            if ( log != null )
                logError(message, line, col) ;
        }
    }

    /** Ignores warnings, throws exceptions for errors */
    private static class ErrorHandlerSimple implements ErrorHandler {
        @Override
        public void warning(String message, long line, long col)
        {}
        @Override
        public void error(String message, long line, long col)
        { throw new RiotException(fmtMessage(message, line, col)) ; }

        @Override
        public void fatal(String message, long line, long col)
        { throw new RiotException(fmtMessage(message, line, col)) ; }
    }

    /** An error handler that logs message then throws exceptions for errors but not warnings */
    private static class ErrorHandlerStd extends ErrorLogger implements ErrorHandler {
        public ErrorHandlerStd(Logger log) {
            super(log) ;
        }

        /** report a warning */
        @Override
        public void warning(String message, long line, long col) {
            logWarning(message, line, col);
        }

        /** report an error */
        @Override
        public void error(String message, long line, long col) {
            logError(message, line, col) ;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }

        /** report a fatal error - does not return */
        @Override
        public void fatal(String message, long line, long col) {
            logFatal(message, line, col) ;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }
    }

    /** Log warnings, throws exceptions for errors */
    private static class ErrorHandlerWarnOrExceptions extends ErrorHandlerStd {
        public ErrorHandlerWarnOrExceptions(Logger log) {
            super(log);
        }

        @Override
        public void error(String message, long line, long col)
        { throw new RiotException(fmtMessage(message, line, col)) ; }

        @Override
        public void fatal(String message, long line, long col)
        { throw new RiotException(fmtMessage(message, line, col)) ; }
    }

    /** An error handler that logs message then throws exceptions for errors but not warnings */
    private static class ErrorHandlerIgnoreWarnings extends ErrorLogger implements ErrorHandler {
        public ErrorHandlerIgnoreWarnings(Logger log) {
            super(log) ;
        }

        /** report a warning */
        @Override
        public void warning(String message, long line, long col)
        { } //logWarning(message, line, col) ;

        /** report an error */
        @Override
        public void error(String message, long line, long col) {
            logError(message, line, col) ;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }

        /** report a fatal error - does not return */
        @Override
        public void fatal(String message, long line, long col) {
            logFatal(message, line, col) ;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }
    }

    /** An error handler that logs message for errors and warnings and throw exceptions on either */
    private static class ErrorHandlerStrict extends ErrorLogger implements ErrorHandler {
        public ErrorHandlerStrict(Logger log) {
            super(log) ;
        }

        /** report a warning - do not carry on */
        @Override
        public void warning(String message, long line, long col) {
            logWarning(message, line, col) ;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }

        /** report an error - do not carry on */
        @Override
        public void error(String message, long line, long col) {
            logError(message, line, col) ;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }

        @Override
        public void fatal(String message, long line, long col) {
            logFatal(message, line, col) ;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }
    }

    /**
     * An error handler that throw exceptions on warnings and errors but does
     * not log
     */
    private static class ErrorHandlerStrictSilent implements ErrorHandler {
        /** report a warning - do not carry on */
        @Override
        public void warning(String message, long line, long col) {
            throw new RiotException(fmtMessage(message, line, col)) ;
        }

        /** report an error - do not carry on */
        @Override
        public void error(String message, long line, long col) {
            throw new RiotException(fmtMessage(message, line, col)) ;
        }

        @Override
        public void fatal(String message, long line, long col) {
            throw new RiotException(fmtMessage(message, line, col)) ;
        }
    }

    /** An error handler that counts errors and warnings.  */
    public static class ErrorHandlerRecorder implements ErrorHandler {
        private long warningCount = 0 ;
        private long errorCount = 0;
        private long fatalCount = 0;
        private final ErrorHandler other;
        public ErrorHandlerRecorder(ErrorHandler other) {
            this.other = other;
        }
        @Override
        public void warning(String message, long line, long col) {
            warningCount++;
            other.warning(message, line, col);
        }

        @Override
        public void error(String message, long line, long col) {
            errorCount++;
            other.error(message, line, col);
        }

        @Override
        public void fatal(String message, long line, long col) {
            fatalCount++;
            other.fatal(message, line, col);
        }

        public long getFatalCount() { return this.fatalCount; }
        public long getErrorCount() { return this.errorCount; }
        public long getWarningCount() { return this.warningCount; }
    }


    /** An error handler that logs message for errors and warnings and throw exceptions on either */
    public static class ErrorHandlerTracking extends ErrorLogger implements ErrorHandler {
        private final boolean failOnError, failOnWarning;
        private long errorCount, warningCount;

        public ErrorHandlerTracking(Logger log, boolean failOnError, boolean failOnWarning) {
            super(log) ;
            this.failOnError = failOnError;
            this.failOnWarning = failOnWarning;
        }

        /** report a warning  */
        @Override
        public void warning(String message, long line, long col) {
            logWarning(message, line, col) ;
            this.warningCount++;
            if (this.failOnWarning)
                throw new RiotException(fmtMessage(message, line, col)) ;
        }

        /** report an error */
        @Override
        public void error(String message, long line, long col) {
            logError(message, line, col) ;
            this.errorCount++;
            if (this.failOnError)
                throw new RiotException(fmtMessage(message, line, col)) ;
        }

        @Override
        public void fatal(String message, long line, long col) {
            logFatal(message, line, col) ;
            this.errorCount++;
            throw new RiotException(fmtMessage(message, line, col)) ;
        }

        public long getErrorCount() {
            return this.errorCount;
        }

        public long getWarningCount() {
            return this.warningCount;
        }

        public boolean hadErrors() {
            return this.errorCount > 0;
        }

        public boolean hadWarnings() {
            return this.warningCount > 0;
        }

        public boolean hadIssues() {
            return hadErrors() || hadWarnings();
        }
    }

    /** An error handler that logs messages for errors and warnings and attempts to carry on */
    private static class ErrorHandlerWarning extends ErrorLogger implements ErrorHandler {
        public ErrorHandlerWarning(Logger log)
        { super(log) ; }

        @Override
        public void warning(String message, long line, long col)
        { logWarning(message, line, col) ; }

        /** report an error but continue */
        @Override
        public void error(String message, long line, long col)
        { logError(message, line, col) ; }

        @Override
        public void fatal(String message, long line, long col) {
            logFatal(message, line, col) ;
            throw new RiotException(SysRIOT.fmtMessage(message, line, col)) ;
        }
    }

    /** An error handler that throws a RiotParseException, hence it exposes the details of errors. */
    private static class ErrorHandlerRiotParseErrors implements ErrorHandler {

        public ErrorHandlerRiotParseErrors() {}

        @Override public void warning(String message, long line, long col) { }

        @Override public void error(String message, long line, long col) {
            throw new RiotParseException(message, line, col);
        }

        @Override public void fatal(String message, long line, long col) {
            throw new RiotParseException(message, line, col);
        }
    }

    /** An error handler that throws a RiotParseException in all cases. */
    private static class ErrorHandlerRiotParseException implements ErrorHandler {

        public ErrorHandlerRiotParseException() {}

        @Override public void warning(String message, long line, long col) {
            throw new RiotParseException(message, line, col);
        }

        @Override public void error(String message, long line, long col) {
            throw new RiotParseException(message, line, col);
        }

        @Override public void fatal(String message, long line, long col) {
            throw new RiotParseException(message, line, col);
        }
    }
}
