/*
 *  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.isis.applib.services.exceprecog;

import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.services.i18n.TranslatableString;
import org.apache.isis.applib.services.i18n.TranslationService;

/**
 * Abstract implementation of {@link ExceptionRecognizer} that looks 
 * exceptions meeting the {@link Predicate} supplied in the constructor
 * and, if found anywhere in the {@link Throwables#getCausalChain(Throwable) causal chain},
 * then returns a non-null message indicating that the exception has been recognized.
 * 
 * <p>
 * If a messaging-parsing {@link Function} is provided through the constructor,
 * then the message can be altered.  Otherwise the exception's {@link Throwable#getMessage() message} is returned as-is.
 */
public abstract class ExceptionRecognizerAbstract implements ExceptionRecognizer2 {

    public static final Logger LOG = LoggerFactory.getLogger(ExceptionRecognizerAbstract.class);

    /**
     * Normally recognized exceptions are not logged (because they are expected and handled).  
     * 
     * <p>
     * This key is primarily for diagnostic purposes, to log the exception regardless.
     */
    private static final String KEY_LOG_RECOGNIZED_EXCEPTIONS = "isis.services.exceprecog.logRecognizedExceptions";


    /**
     * Convenience for subclass implementations that always return a fixed message.
     */
    protected static Function<String, String> constant(final String message) {
        return new Function<String, String>() {

            @Override
            public String apply(String input) {
                return message;
            }
        };
    }

    /**
     * Convenience for subclass implementations that always prefixes the exception message
     * with the supplied text
     */
    protected static Function<String, String> prefix(final String prefix) {
        return new Function<String, String>() {

            @Override
            public String apply(String input) {
                return prefix + ": " + input;
            }
        };
    }
    
    // //////////////////////////////////////


    private final Category category;
    private final Predicate<Throwable> predicate;
    private final Function<String,String> messageParser;
    
    private boolean logRecognizedExceptions;

    // //////////////////////////////////////

    public ExceptionRecognizerAbstract(final Category category, Predicate<Throwable> predicate, final Function<String,String> messageParser) {
        this.category = category;
        this.predicate = predicate;
        this.messageParser = messageParser != null? messageParser: Functions.<String>identity();
    }

    public ExceptionRecognizerAbstract(Predicate<Throwable> predicate, final Function<String,String> messageParser) {
        this(Category.OTHER, predicate, messageParser);
    }

    public ExceptionRecognizerAbstract(Category category, Predicate<Throwable> predicate) {
        this(category, predicate, null);
    }

    public ExceptionRecognizerAbstract(Predicate<Throwable> predicate) {
        this(Category.OTHER, predicate);
    }


    @PostConstruct
    public void init(Map<String, String> properties) {
        final String prop = properties.get(KEY_LOG_RECOGNIZED_EXCEPTIONS);
        this.logRecognizedExceptions = Boolean.parseBoolean(prop);
    }

    @PreDestroy
    public void shutdown() {
    }

    // //////////////////////////////////////

    @Programmatic
    public String recognize(Throwable ex) {
        List<Throwable> causalChain = Throwables.getCausalChain(ex);
        for (Throwable throwable : causalChain) {
            if(predicate.apply(throwable)) {
                if(logRecognizedExceptions) {
                    LOG.info("Recognized exception, stacktrace : ", throwable);
                }
                if(ex instanceof TranslatableException) {
                    final TranslatableException translatableException = (TranslatableException) ex;
                    final TranslatableString translatableMessage = translatableException.getTranslatableMessage();
                    final String translationContext = translatableException.getTranslationContext();
                    if(translatableMessage != null && translationContext != null) {
                        return translatableMessage.translate(translationService, translationContext);
                    }
                }
                final Throwable rootCause = Throwables.getRootCause(throwable);
                final String rootCauseMessage = rootCause.getMessage();
                final String parsedMessage = messageParser.apply(rootCauseMessage);
                return parsedMessage;
            }
        }
        return null;
    }

    @Programmatic
    @Override
    public Recognition recognize2(Throwable ex) {
        return Recognition.of(category, recognize(ex));
    }

    @javax.inject.Inject
    protected TranslationService translationService;

}
