/*
 * 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.felix.scr.impl.logger;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;

import org.apache.felix.scr.impl.logger.InternalLogger.Level;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.Logger;

/**
 * Implements a SCR based log manager. This class was needed to not change the
 * whole codebase. It looks very similar to the old logging but leverages the
 * {@link LogManager}. This class implements the existing behavior and the
 * {@link ExtLogManager} implements the extension behavior. The {@link #scr()}
 * method makes this distinction based on the configuration.
 */
public class ScrLogManager extends LogManager
{

    private final Bundle bundle;
    private final LogConfiguration config;

    ScrLogManager(BundleContext context, LogConfiguration config)
    {
        super(context, config);
        this.config = config;
        this.bundle = context.getBundle();
    }

    /**
     * This logger is used for the main code of SCR. This will use the SCR
     * bundle & the {@link Logger#ROOT_LOGGER_NAME}
     * 
     * @return scr logger.
     */
    public ScrLogger scr()
    {
        ScrLoggerFacade scrl = super.getLogger(bundle, Logger.ROOT_LOGGER_NAME,
            ScrLoggerFacade.class);
        scrl.setPrefix(getBundleIdentifier(bundle));
        return scrl;
    }

    /**
     * This logger is used for the logging on a per bundle basis. This will use
     * the target bundle & the {@link Logger#ROOT_LOGGER_NAME}
     * 
     * @param bundle
     *                   the target bundle
     * @return a logger suitable to log bundle entries
     */
    public BundleLogger bundle(Bundle bundle)
    {
        ScrLoggerFacade logger = getLogger(bundle, Logger.ROOT_LOGGER_NAME,
            ScrLoggerFacade.class);
        logger.setPrefix(getBundleIdentifier(bundle));
        return logger;
    }

    /**
     * This logger is used for the logging on a per bundle basis. This will use
     * the target bundle & the implementation class as logger name.
     * 
     * @param bundle
     *                   the target bundle
     * @return a logger suitable to log bundle entries
     */
    public ComponentLogger component(Bundle bundle, String implementationClass,
        String name)
    {

        // assert bundle != null;
        // assert bundle.getSymbolicName() != null : "scr requires recent
        // bundles";
        // assert implementationClass != null;
        // assert name != null;

        ScrLoggerFacade facade = getLogger(bundle, implementationClass,
            ScrLoggerFacade.class);
        facade.setComponentId(-1);
        return (ComponentLogger) facade;
    }

    class ScrLoggerFacade extends LoggerFacade implements InternalLogger, ScrLogger, BundleLogger, ComponentLogger
    {
        ScrLoggerFacade(LogDomain logDomain, String name)
        {
            super(logDomain, name);
        }

        @Override
        public void setComponentId(long id)
        {
            setPrefix(componentPrefix(this, id));
        }

        public boolean isLogEnabled(Level level)
        {

            // assert !closed.get();

            Object checkLogger = getLogger();
            if (checkLogger != null)
            {
                Logger logger = (Logger) checkLogger;
                switch (level)
                {
                    case AUDIT:
                        return true;
                    case ERROR:
                        return logger.isErrorEnabled();
                    case WARN:
                        return logger.isWarnEnabled();
                    case INFO:
                        return logger.isInfoEnabled();
                    case TRACE:
                        return logger.isTraceEnabled();
                    case DEBUG:
                    default:
                        return logger.isDebugEnabled();
                }
            }
            else
            {
                return getLogLevel().implies(level);
            }
        }

        @Override
        public void log(Level level, String format, Throwable ex, Object... arguments)
        {
            if (isLogEnabled(level))
                log0(level, format(format, arguments), ex);
        }

        @Override
        public void log(Level level, String message, Throwable ex)
        {
            if (isLogEnabled(level))
                log0(level, message, ex);
        }

        void log0(Level level, String message, Throwable ex)
        {
            if (prefix != null && prefix.length() > 0)
            {
                message = prefix.concat(" ").concat(message);
            }
            Object checkLogger = getLogger();
            if (checkLogger != null)
            {
                Logger logger = (Logger) checkLogger;
                if (ex == null)
                {
                    switch (level)
                    {
                        case AUDIT:
                            logger.audit(message);
                            break;
                        case ERROR:
                            logger.error(message);
                            break;
                        case WARN:
                            logger.warn(message);
                            break;
                        case INFO:
                            logger.info(message);
                            break;
                        case TRACE:
                            logger.trace(message);
                            break;
                        case DEBUG:
                        default:
                            logger.debug(message);
                    }
                }
                else
                {
                    switch (level)
                    {
                        case AUDIT:
                            logger.audit(message, ex);
                            break;
                        case ERROR:
                            logger.error(message, ex);
                            break;
                        case WARN:
                            logger.warn(message, ex);
                            break;
                        case INFO:
                            logger.info(message, ex);
                            break;
                        case TRACE:
                            logger.trace(message, ex);
                            break;
                        case DEBUG:
                        default:
                            logger.debug(message, ex);
                    }
                }
            }
            else
            {
                StringWriter buf = new StringWriter();
                String l = String.format("%-5s", level);
                buf.append(l).append(" : ").append(message);
                if (ex != null)
                {
                    try (PrintWriter pw = new PrintWriter(buf))
                    {
                        pw.println();
                        ex.printStackTrace(pw);
                    }
                }

                @SuppressWarnings("resource")
                PrintStream out = level.err() ? System.err : System.out;
                out.println(buf);
            }
        }

        void setPrefix(String prefix)
        {
            this.prefix = prefix;
        }

        @Override
        public ComponentLogger component(Bundle bundle, String implementationClassName,
            String name)
        {
            // assert !closed.get();
            return ScrLogManager.this.component(bundle, implementationClassName, name);
        }

        @Override
        public BundleLogger bundle(Bundle bundle)
        {
            // assert !closed.get();
            return ScrLogManager.this.bundle(bundle);
        }

        @Override
        public void close()
        {
            // assert !closed.get();
            ScrLogManager.this.close();
        }
    };

    LoggerFacade createLoggerFacade(LogDomain logDomain, String name)
    {
        // assert !closed.get();
        return new ScrLoggerFacade(logDomain, name);
    }

    Level getLogLevel()
    {
        return config.getLogLevel();
    }

    String getBundleIdentifier(final Bundle bundle)
    {
        final StringBuilder sb = new StringBuilder("bundle ");

        if (bundle.getSymbolicName() != null)
        {
            sb.append(bundle.getSymbolicName());
            sb.append(':');
            sb.append(bundle.getVersion());
            sb.append(" (");
            sb.append(bundle.getBundleId());
            sb.append(")");
        }
        else
        {
            sb.append(bundle.getBundleId());
        }

        return sb.toString();
    }

    String componentPrefix(ScrLoggerFacade slf, long id)
    {
        if (id >= 0)
        {
            return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "(" + id
                + ")] :";
        }
        else
        {
            return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "] :";
        }
    }

    String format(final String pattern, final Object... arguments)
    {
        if (arguments == null || arguments.length == 0)
        {
            return pattern;
        }
        else
        {
            for (int i = 0; i < arguments.length; i++)
            {
                if (arguments[i] instanceof Bundle)
                {
                    arguments[i] = getBundleIdentifier((Bundle) arguments[i]);
                }
            }
            return MessageFormat.format(pattern, arguments);
        }
    }

}
