/*
 * 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.geode.internal.logging;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.lookup.StrLookup;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.status.StatusLogger;

import org.apache.geode.internal.logging.log4j.AppenderContext;
import org.apache.geode.internal.logging.log4j.ConfigLocator;
import org.apache.geode.internal.logging.log4j.Configurator;
import org.apache.geode.internal.logging.log4j.FastLogger;
import org.apache.geode.internal.logging.log4j.LogWriterLogger;
import org.apache.geode.internal.logging.log4j.message.GemFireParameterizedMessageFactory;

/**
 * Centralizes log configuration and initialization.
 */
@SuppressWarnings("unused")
public class LogService extends LogManager {

  // This is highest point in the hierarchy for all Geode logging
  public static final String ROOT_LOGGER_NAME = "";
  public static final String BASE_LOGGER_NAME = "org.apache.geode";
  public static final String MAIN_LOGGER_NAME = "org.apache.geode";
  public static final String SECURITY_LOGGER_NAME = "org.apache.geode.security";

  public static final String GEODE_VERBOSE_FILTER = "{GEODE_VERBOSE}";
  public static final String GEMFIRE_VERBOSE_FILTER = "{GEMFIRE_VERBOSE}";
  public static final String DEFAULT_CONFIG = "/log4j2.xml";
  public static final String CLI_CONFIG = "/log4j2-cli.xml";

  protected static final String STDOUT = "STDOUT";

  private static final PropertyChangeListener propertyChangeListener =
      new PropertyChangeListenerImpl();

  /**
   * Name of variable that is set to "true" in log4j2.xml to indicate that it is the default geode
   * config xml.
   */
  private static final String GEMFIRE_DEFAULT_PROPERTY = "geode-default";

  /**
   * Protected by static synchronization. Used for removal and adding stdout back in.
   */
  private static Appender stdoutAppender;

  static {
    init();
  }

  private LogService() {
    // do not instantiate
  }

  private static void init() {
    LoggerContext context = ((org.apache.logging.log4j.core.Logger) LogManager
        .getLogger(BASE_LOGGER_NAME, GemFireParameterizedMessageFactory.INSTANCE)).getContext();
    context.removePropertyChangeListener(propertyChangeListener);
    context.addPropertyChangeListener(propertyChangeListener);
    context.reconfigure(); // propertyChangeListener invokes configureFastLoggerDelegating
    configureLoggers(false, false);
  }

  public static void initialize() {
    new LogService();
  }

  public static void reconfigure() {
    init();
  }

  public static void configureLoggers(final boolean hasLogFile, final boolean hasSecurityLogFile) {
    Configurator.getOrCreateLoggerConfig(BASE_LOGGER_NAME, true, false);
    Configurator.getOrCreateLoggerConfig(MAIN_LOGGER_NAME, true, hasLogFile);
    final boolean useMainLoggerForSecurity = !hasSecurityLogFile;
    Configurator.getOrCreateLoggerConfig(SECURITY_LOGGER_NAME, useMainLoggerForSecurity,
        hasSecurityLogFile);
  }

  public static AppenderContext getAppenderContext() {
    return new AppenderContext();
  }

  public static AppenderContext getAppenderContext(final String name) {
    return new AppenderContext(name);
  }

  public static boolean isUsingGemFireDefaultConfig() {
    final Configuration config =
        ((org.apache.logging.log4j.core.Logger) LogManager.getLogger(ROOT_LOGGER_NAME,
            GemFireParameterizedMessageFactory.INSTANCE)).getContext().getConfiguration();

    final StrSubstitutor sub = config.getStrSubstitutor();
    final StrLookup resolver = sub.getVariableResolver();

    final String value = resolver.lookup(GEMFIRE_DEFAULT_PROPERTY);

    return "true".equals(value);
  }

  public static String getConfigInformation() {
    return getConfiguration().getConfigurationSource().toString();
  }

  /**
   * Finds a Log4j configuration file in the current directory. The names of the files to look for
   * are the same as those that Log4j would look for on the classpath.
   *
   * @return A File for the configuration file or null if one isn't found.
   */
  public static File findLog4jConfigInCurrentDir() {
    return ConfigLocator.findConfigInWorkingDirectory();
  }

  /**
   * Returns a Logger with the name of the calling class.
   *
   * @return The Logger for the calling class.
   */
  public static Logger getLogger() {
    return new FastLogger(
        LogManager.getLogger(getClassName(2), GemFireParameterizedMessageFactory.INSTANCE));
  }

  public static Logger getLogger(final String name) {
    return new FastLogger(LogManager.getLogger(name, GemFireParameterizedMessageFactory.INSTANCE));
  }

  /**
   * Returns a LogWriterLogger that is decorated with the LogWriter and LogWriterI18n methods.
   * <p>
   * This is the bridge to LogWriter and LogWriterI18n that we need to eventually stop using in
   * phase 1. We will switch over from a shared LogWriterLogger instance to having every GemFire
   * class own its own private static GemFireLogger
   *
   * @return The LogWriterLogger for the calling class.
   */
  public static LogWriterLogger createLogWriterLogger(final String name,
      final String connectionName, final boolean isSecure) {
    return LogWriterLogger.create(name, connectionName, isSecure);
  }

  /**
   * Return the Log4j Level associated with the int level.
   *
   * @param intLevel The int value of the Level to return.
   *
   * @return The Level.
   *
   * @throws java.lang.IllegalArgumentException if the Level int is not registered.
   */
  public static Level toLevel(final int intLevel) {
    for (Level level : Level.values()) {
      if (level.intLevel() == intLevel) {
        return level;
      }
    }

    throw new IllegalArgumentException("Unknown int level [" + intLevel + "].");
  }

  /**
   * Gets the class name of the caller in the current stack at the given {@code depth}.
   *
   * @param depth a 0-based index in the current stack.
   *
   * @return a class name
   */
  public static String getClassName(final int depth) {
    return new Throwable().getStackTrace()[depth].getClassName();
  }

  public static Configuration getConfiguration() {
    final Configuration config =
        ((org.apache.logging.log4j.core.Logger) LogManager.getLogger(ROOT_LOGGER_NAME,
            GemFireParameterizedMessageFactory.INSTANCE)).getContext().getConfiguration();
    return config;
  }

  public static void configureFastLoggerDelegating() {
    final Configuration config =
        ((org.apache.logging.log4j.core.Logger) LogManager.getLogger(ROOT_LOGGER_NAME,
            GemFireParameterizedMessageFactory.INSTANCE)).getContext().getConfiguration();

    if (Configurator.hasContextWideFilter(config) || Configurator.hasAppenderFilter(config)
        || Configurator.hasDebugOrLower(config) || Configurator.hasLoggerFilter(config)
        || Configurator.hasAppenderRefFilter(config)) {
      FastLogger.setDelegating(true);
    } else {
      FastLogger.setDelegating(false);
    }
  }

  public static void setSecurityLogLevel(Level level) {
    Configurator.setLevel(SECURITY_LOGGER_NAME, level);
  }

  public static Level getBaseLogLevel() {
    return Configurator.getLevel(BASE_LOGGER_NAME);
  }

  public static void setBaseLogLevel(Level level) {
    if (isUsingGemFireDefaultConfig()) {
      Configurator.setLevel(ROOT_LOGGER_NAME, level);
    }
    Configurator.setLevel(BASE_LOGGER_NAME, level);
    Configurator.setLevel(MAIN_LOGGER_NAME, level);
  }

  public static LoggerConfig getRootLoggerConfig() {
    return Configurator.getLoggerConfig(LogManager.getRootLogger().getName());
  }

  /**
   * Removes STDOUT ConsoleAppender from ROOT logger. Only called when using the log4j2-default.xml
   * configuration. This is done when creating the LogWriterAppender for log-file. The Appender
   * instance is stored in stdoutAppender so it can be restored later using restoreConsoleAppender.
   */
  public static synchronized void removeConsoleAppender() {
    final AppenderContext appenderContext =
        LogService.getAppenderContext(LogService.ROOT_LOGGER_NAME);
    final LoggerConfig config = appenderContext.getLoggerConfig();
    Appender stdout = config.getAppenders().get(STDOUT);
    if (stdout != null) {
      config.removeAppender(STDOUT);
      stdoutAppender = stdout;
      appenderContext.getLoggerContext().updateLoggers();
    }
  }

  /**
   * Restores STDOUT ConsoleAppender to ROOT logger. Only called when using the log4j2-default.xml
   * configuration. This is done when the LogWriterAppender for log-file is destroyed. The Appender
   * instance stored in stdoutAppender is used.
   */
  public static synchronized void restoreConsoleAppender() {
    if (stdoutAppender == null) {
      return;
    }
    final AppenderContext appenderContext =
        LogService.getAppenderContext(LogService.ROOT_LOGGER_NAME);
    final LoggerConfig config = appenderContext.getLoggerConfig();
    Appender stdout = config.getAppenders().get(STDOUT);
    if (stdout == null) {
      config.addAppender(stdoutAppender, Level.ALL, null);
      appenderContext.getLoggerContext().updateLoggers();
    }
  }

  private static class PropertyChangeListenerImpl implements PropertyChangeListener {

    @Override
    @SuppressWarnings("synthetic-access")
    public void propertyChange(final PropertyChangeEvent evt) {
      StatusLogger.getLogger().debug(
          "LogService responding to a property change event. Property name is {}.",
          evt.getPropertyName());

      if (evt.getPropertyName().equals(LoggerContext.PROPERTY_CONFIG)) {
        configureFastLoggerDelegating();
      }
    }
  }
}
