/*
 * 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 static java.lang.System.lineSeparator;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.BreakIterator;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Handler;
import java.util.logging.Level;

import org.apache.geode.LogWriter;
import org.apache.geode.annotations.internal.MakeNotStatic;
import org.apache.geode.i18n.LogWriterI18n;
import org.apache.geode.i18n.StringId;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.process.StartupStatusListener;
import org.apache.geode.logging.internal.executors.LoggingThread;

/**
 * Abstract implementation of {@link InternalLogWriter}. Each logger has a level and it will only
 * print messages whose level is greater than or equal to the logger's level. The supported logger
 * level constants, in ascending order, are:
 * <ol>
 * <li>{@link #ALL_LEVEL}
 * <li>{@link #FINEST_LEVEL}
 * <li>{@link #FINER_LEVEL}
 * <li>{@link #FINE_LEVEL}
 * <li>{@link #CONFIG_LEVEL}
 * <li>{@link #INFO_LEVEL}
 * <li>{@link #WARNING_LEVEL}
 * <li>{@link #ERROR_LEVEL}
 * <li>{@link #SEVERE_LEVEL}
 * <li>{@link #NONE_LEVEL}
 * </ol>
 * <p>
 * Subclasses must implement:
 * <ol>
 * <li>{@link #getLogWriterLevel}
 * <li>{@link #put(int, String, Throwable)}
 * <li>{@link #put(int, StringId, Object[], Throwable)}
 * </ol>
 *
 * @deprecated Please use Log4J2 instead.
 */
@Deprecated
public abstract class LogWriterImpl implements InternalLogWriter {

  /**
   * A bit mask to remove any potential flags added to the msgLevel. Intended to be used in
   * {@link #getRealLogLevel}.
   */
  private static final int LOGGING_FLAGS_MASK = 0x00FFFFFF;

  /**
   * A flag to indicate the {@link SecurityLogWriter#SECURITY_PREFIX} should be appended to the log
   * level.
   */
  private static final int SECURITY_LOGGING_FLAG = 0x40000000;

  static {
    Assert.assertTrue(ALL_LEVEL == Level.ALL.intValue());
    Assert.assertTrue(NONE_LEVEL == Level.OFF.intValue());
    Assert.assertTrue(FINEST_LEVEL == Level.FINEST.intValue());
    Assert.assertTrue(FINER_LEVEL == Level.FINER.intValue());
    Assert.assertTrue(FINE_LEVEL == Level.FINE.intValue());
    Assert.assertTrue(CONFIG_LEVEL == Level.CONFIG.intValue());
    Assert.assertTrue(INFO_LEVEL == Level.INFO.intValue());
    Assert.assertTrue(WARNING_LEVEL == Level.WARNING.intValue());
    Assert.assertTrue(SEVERE_LEVEL == Level.SEVERE.intValue());
    int logLevels = FINEST_LEVEL | FINER_LEVEL | FINE_LEVEL | CONFIG_LEVEL | INFO_LEVEL
        | WARNING_LEVEL | SEVERE_LEVEL;
    Assert.assertTrue(logLevels == (logLevels & LOGGING_FLAGS_MASK));
    Assert.assertTrue(0 == (logLevels & SECURITY_LOGGING_FLAG));
  }

  /**
   * A listener which can be registered to be informed of startup events
   */
  @MakeNotStatic
  private static volatile StartupStatusListener startupListener;

  private final DateFormat timeFormatter;

  protected LogWriterImpl() {
    timeFormatter = DateFormatter.createDateFormat();
  }

  /**
   * Gets the writer's level.
   */
  @Override
  public abstract int getLogWriterLevel();

  @Override
  public boolean isSecure() {
    return false;
  }

  public static String allowedLogLevels() {
    StringBuilder sb = new StringBuilder(64);
    for (int i = 0; i < levelNames.size(); i++) {
      if (i != 0) {
        sb.append('|');
      }
      sb.append(levelNames.get(i));
    }
    return sb.toString();
  }

  /**
   * Gets the string representation for the given <code>level</code> int code.
   */
  public static String levelToString(int level) {
    switch (level) {
      case ALL_LEVEL:
        return "all";
      case FINEST_LEVEL:
        return "finest";
      case FINER_LEVEL:
        return "finer";
      case FINE_LEVEL:
        return "fine";
      case CONFIG_LEVEL:
        return "config";
      case INFO_LEVEL:
        return "info";
      case WARNING_LEVEL:
        return "warning";
      case ERROR_LEVEL:
        return "error";
      case SEVERE_LEVEL:
        return "severe";
      case NONE_LEVEL:
        return "none";
      default:
        return levelToStringSpecialCase(level);
    }
  }

  /**
   * Handles the special cases for {@link #levelToString(int)} including such cases as the
   * SECURITY_LOGGING_PREFIX and an invalid log level.
   */
  private static String levelToStringSpecialCase(int levelWithFlags) {
    if ((levelWithFlags & SECURITY_LOGGING_FLAG) != 0) {
      // We know the flag is set so XOR will zero it out.
      int level = levelWithFlags ^ SECURITY_LOGGING_FLAG;
      return SecurityLogWriter.SECURITY_PREFIX + levelToString(level);
    } else {
      // Needed to prevent infinite recursion
      // This signifies an unknown log level was used
      return "level-" + levelWithFlags;
    }
  }

  protected static int getRealLogLevel(int levelWithFlags) {
    if (levelWithFlags == NONE_LEVEL) {
      return levelWithFlags;
    }
    return levelWithFlags & LOGGING_FLAGS_MASK;
  }

  public static String join(Object[] array) {
    return join(array, " ");
  }

  public static String join(Object[] array, String joinString) {
    return join(Arrays.asList(array), joinString);
  }

  public static String join(List<?> list) {
    return join(list, " ");
  }

  public static String join(List<?> list, String joinString) {
    StringBuilder result = new StringBuilder(80);
    boolean firstTime = true;
    for (Object object : list) {
      if (firstTime) {
        firstTime = false;
      } else {
        result.append(joinString);
      }
      result.append(object);
    }
    return result.toString();
  }

  /**
   * Gets the level code for the given <code>levelName</code>.
   *
   * @throws IllegalArgumentException if an unknown level name is given.
   */
  public static int levelNameToCode(String levelName) {
    if ("all".equalsIgnoreCase(levelName)) {
      return ALL_LEVEL;
    }
    if ("finest".equalsIgnoreCase(levelName) || "trace".equalsIgnoreCase(levelName)) {
      return FINEST_LEVEL;
    }
    if ("finer".equalsIgnoreCase(levelName)) {
      return FINER_LEVEL;
    }
    if ("fine".equalsIgnoreCase(levelName) || "debug".equalsIgnoreCase(levelName)) {
      return FINE_LEVEL;
    }
    if ("config".equalsIgnoreCase(levelName)) {
      return CONFIG_LEVEL;
    }
    if ("info".equalsIgnoreCase(levelName)) {
      return INFO_LEVEL;
    }
    if ("warning".equalsIgnoreCase(levelName) || "warn".equalsIgnoreCase(levelName)) {
      return WARNING_LEVEL;
    }
    if ("error".equalsIgnoreCase(levelName)) {
      return ERROR_LEVEL;
    }
    if ("severe".equalsIgnoreCase(levelName) || "fatal".equalsIgnoreCase(levelName)) {
      return SEVERE_LEVEL;
    }
    if ("none".equalsIgnoreCase(levelName)) {
      return NONE_LEVEL;
    }
    try {
      if (levelName.startsWith("level-")) {
        String levelValue = levelName.substring("level-".length());
        return Integer.parseInt(levelValue);
      }
    } catch (NullPointerException | NumberFormatException ignore) {
      // ignored
    }
    throw new IllegalArgumentException(
        "Unknown log-level \"" + levelName + "\". Valid levels are: " + join(levelNames) + ".");
  }

  /**
   * Gets a String representation of the current time.
   *
   * @return a String representation of the current time.
   */
  protected String getTimeStamp() {
    return formatDate(new Date());
  }

  /**
   * Convert a Date to a timestamp String.
   *
   * @param date a Date to format as a timestamp String.
   * @return a String representation of the current time.
   */
  protected String formatDate(Date date) {
    try {
      synchronized (timeFormatter) {
        // Need sync: see bug 21858
        return timeFormatter.format(date);
      }
    } catch (Exception e1) {
      // Fix bug 21857
      try {
        return date.toString();
      } catch (Exception e2) {
        try {
          return Long.toString(date.getTime());
        } catch (Exception e3) {
          return "timestampFormatFailed";
        }
      }
    }
  }

  /**
   * Returns true if "severe" log messages are enabled. Returns false if "severe" log messages are
   * disabled.
   */
  @Override
  public boolean severeEnabled() {
    return getLogWriterLevel() <= SEVERE_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "severe".
   */
  @Override
  public void severe(String message, Throwable throwable) {
    if (severeEnabled()) {
      put(SEVERE_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "severe".
   */
  @Override
  public void severe(String message) {
    severe(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "severe".
   */
  @Override
  public void severe(Throwable throwable) {
    severe("", throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "severe".
   *
   * @since GemFire 6.0
   */
  @Override
  public void severe(StringId messageId, Object[] parameters, Throwable throwable) {
    if (severeEnabled()) {
      put(SEVERE_LEVEL, messageId, parameters, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "severe".
   *
   * @since GemFire 6.0
   */
  @Override
  public void severe(StringId messageId, Object parameter, Throwable throwable) {
    if (severeEnabled()) {
      put(SEVERE_LEVEL, messageId, new Object[] {parameter}, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "severe".
   *
   * @since GemFire 6.0
   */
  @Override
  public void severe(StringId messageId, Throwable throwable) {
    severe(messageId, null, throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "severe".
   *
   * @since GemFire 6.0
   */
  @Override
  public void severe(StringId messageId, Object[] parameters) {
    severe(messageId, parameters, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "severe".
   *
   * @since GemFire 6.0
   */
  @Override
  public void severe(StringId messageId, Object parameter) {
    severe(messageId, parameter, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "severe".
   *
   * @since GemFire 6.0
   */
  @Override
  public void severe(StringId messageId) {
    severe(messageId, null, null);
  }

  /**
   * @return true if "error" log messages are enabled.
   */
  @Override
  public boolean errorEnabled() {
    return getLogWriterLevel() <= ERROR_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "error".
   */
  @Override
  public void error(String message, Throwable throwable) {
    if (errorEnabled()) {
      put(ERROR_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "error".
   */
  @Override
  public void error(String message) {
    error(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "error".
   */
  @Override
  public void error(Throwable throwable) {
    error("", throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "error".
   *
   * @since GemFire 6.0
   */
  @Override
  public void error(StringId messageId, Object[] parameters, Throwable throwable) {
    if (errorEnabled()) {
      put(ERROR_LEVEL, messageId, parameters, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "error".
   *
   * @since GemFire 6.0
   */
  @Override
  public void error(StringId messageId, Object parameter, Throwable throwable) {
    if (errorEnabled()) {
      put(ERROR_LEVEL, messageId, new Object[] {parameter}, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "error".
   *
   * @since GemFire 6.0
   */
  @Override
  public void error(StringId messageId, Throwable throwable) {
    error(messageId, null, throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "error".
   *
   * @since GemFire 6.0
   */
  @Override
  public void error(StringId messageId, Object[] parameters) {
    error(messageId, parameters, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "error".
   *
   * @since GemFire 6.0
   */
  @Override
  public void error(StringId messageId, Object parameter) {
    error(messageId, parameter, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "error".
   *
   * @since GemFire 6.0
   */
  @Override
  public void error(StringId messageId) {
    error(messageId, null, null);
  }

  /**
   * @return true if "warning" log messages are enabled.
   */
  @Override
  public boolean warningEnabled() {
    return getLogWriterLevel() <= WARNING_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "warning".
   */
  @Override
  public void warning(String message, Throwable throwable) {
    if (warningEnabled()) {
      put(WARNING_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "warning".
   */
  @Override
  public void warning(String message) {
    warning(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "warning".
   */
  @Override
  public void warning(Throwable throwable) {
    warning("", throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "warning".
   *
   * @since GemFire 6.0
   */
  @Override
  public void warning(StringId messageId, Object[] parameters, Throwable throwable) {
    if (warningEnabled()) {
      put(WARNING_LEVEL, messageId, parameters, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "warning".
   *
   * @since GemFire 6.0
   */
  @Override
  public void warning(StringId messageId, Object parameter, Throwable throwable) {
    if (warningEnabled()) {
      put(WARNING_LEVEL, messageId, new Object[] {parameter}, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "warning".
   *
   * @since GemFire 6.0
   */
  @Override
  public void warning(StringId messageId, Throwable throwable) {
    warning(messageId, null, throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "warning".
   *
   * @since GemFire 6.0
   */
  @Override
  public void warning(StringId messageId, Object[] parameters) {
    warning(messageId, parameters, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "warning".
   *
   * @since GemFire 6.0
   */
  @Override
  public void warning(StringId messageId, Object parameter) {
    warning(messageId, parameter, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "warning".
   *
   * @since GemFire 6.0
   */
  @Override
  public void warning(StringId messageId) {
    warning(messageId, null, null);
  }

  /**
   * @return true if "info" log messages are enabled.
   */
  @Override
  public boolean infoEnabled() {
    return getLogWriterLevel() <= INFO_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "information".
   */
  @Override
  public void info(String message, Throwable throwable) {
    if (infoEnabled()) {
      put(INFO_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "information".
   */
  @Override
  public void info(String message) {
    info(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "information".
   */
  @Override
  public void info(Throwable throwable) {
    info("", throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "info".
   *
   * @since GemFire 6.0
   */
  @Override
  public void info(StringId messageId, Object[] parameters, Throwable throwable) {
    if (infoEnabled()) {
      put(INFO_LEVEL, messageId, parameters, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "info".
   *
   * @since GemFire 6.0
   */
  @Override
  public void info(StringId messageId, Object parameter, Throwable throwable) {
    if (infoEnabled()) {
      put(INFO_LEVEL, messageId, new Object[] {parameter}, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "info".
   *
   * @since GemFire 6.0
   */
  @Override
  public void info(StringId messageId, Throwable throwable) {
    info(messageId, null, throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "info".
   *
   * @since GemFire 6.0
   */
  @Override
  public void info(StringId messageId, Object[] parameters) {
    info(messageId, parameters, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "info".
   *
   * @since GemFire 6.0
   */
  @Override
  public void info(StringId messageId, Object parameter) {
    info(messageId, parameter, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "info".
   *
   * @since GemFire 6.0
   */
  @Override
  public void info(StringId messageId) {
    info(messageId, null, null);
  }

  /**
   * @return true if "config" log messages are enabled.
   */
  @Override
  public boolean configEnabled() {
    return getLogWriterLevel() <= CONFIG_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "config".
   */
  @Override
  public void config(String message, Throwable throwable) {
    if (configEnabled()) {
      put(CONFIG_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "config".
   */
  @Override
  public void config(String message) {
    config(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "config".
   */
  @Override
  public void config(Throwable throwable) {
    config("", throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "config".
   *
   * @since GemFire 6.0
   */
  @Override
  public void config(StringId messageId, Object[] parameters, Throwable throwable) {
    if (configEnabled()) {
      put(CONFIG_LEVEL, messageId, parameters, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "config".
   *
   * @since GemFire 6.0
   */
  @Override
  public void config(StringId messageId, Object parameter, Throwable throwable) {
    if (configEnabled()) {
      put(CONFIG_LEVEL, messageId, new Object[] {parameter}, throwable);
    }
  }

  /**
   * Writes both a message and exception to this writer. The message level is "config".
   *
   * @since GemFire 6.0
   */
  @Override
  public void config(StringId messageId, Throwable throwable) {
    config(messageId, null, throwable);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "config".
   *
   * @since GemFire 6.0
   */
  @Override
  public void config(StringId messageId, Object[] parameters) {
    config(messageId, parameters, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "config".
   *
   * @since GemFire 6.0
   */
  @Override
  public void config(StringId messageId, Object parameter) {
    config(messageId, parameter, null);
  }

  /**
   * Writes both a message and exception to this writer. The message level is "config".
   *
   * @since GemFire 6.0
   */
  @Override
  public void config(StringId messageId) {
    config(messageId, null, null);
  }

  /**
   * @return true if "fine" log messages are enabled.
   */
  @Override
  public boolean fineEnabled() {
    return getLogWriterLevel() <= FINE_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "fine".
   */
  @Override
  public void fine(String message, Throwable throwable) {
    if (fineEnabled()) {
      put(FINE_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "fine".
   */
  @Override
  public void fine(String message) {
    fine(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "fine".
   */
  @Override
  public void fine(Throwable throwable) {
    fine(null, throwable);
  }

  /**
   * Returns true if "finer" log messages are enabled. Returns false if "finer" log messages are
   * disabled.
   */
  @Override
  public boolean finerEnabled() {
    return getLogWriterLevel() <= FINER_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "finer".
   */
  @Override
  public void finer(String message, Throwable throwable) {
    if (finerEnabled()) {
      put(FINER_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "finer".
   */
  @Override
  public void finer(String message) {
    finer(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "finer".
   */
  @Override
  public void finer(Throwable throwable) {
    finer(null, throwable);
  }

  /**
   * Log a method entry.
   * <p>
   * The logging is done using the <code>finer</code> level. The string message will start with
   * <code>"ENTRY"</code> and include the class and method names.
   *
   * @param sourceClass Name of class that issued the logging request.
   * @param sourceMethod Name of the method that issued the logging request.
   */
  @Override
  public void entering(String sourceClass, String sourceMethod) {
    if (finerEnabled()) {
      finer("ENTRY " + sourceClass + ":" + sourceMethod);
    }
  }

  /**
   * Log a method return.
   * <p>
   * The logging is done using the <code>finer</code> level. The string message will start with
   * <code>"RETURN"</code> and include the class and method names.
   *
   * @param sourceClass Name of class that issued the logging request.
   * @param sourceMethod Name of the method that issued the logging request.
   */
  @Override
  public void exiting(String sourceClass, String sourceMethod) {
    if (finerEnabled()) {
      finer("RETURN " + sourceClass + ":" + sourceMethod);
    }
  }

  /**
   * Log throwing an exception.
   * <p>
   * Use to log that a method is terminating by throwing an exception. The logging is done using the
   * <code>finer</code> level.
   * <p>
   * This is a convenience method that could be done instead by calling
   * {@link #finer(String, Throwable)}. The string message will start with <code>"THROW"</code> and
   * include the class and method names.
   *
   * @param sourceClass Name of class that issued the logging request.
   * @param sourceMethod Name of the method that issued the logging request.
   * @param thrown The Throwable that is being thrown.
   */
  @Override
  public void throwing(String sourceClass, String sourceMethod, Throwable thrown) {
    if (finerEnabled()) {
      finer("THROW " + sourceClass + ":" + sourceMethod, thrown);
    }
  }

  /**
   * Returns true if "finest" log messages are enabled. Returns false if "finest" log messages are
   * disabled.
   */
  @Override
  public boolean finestEnabled() {
    return getLogWriterLevel() <= FINEST_LEVEL;
  }

  /**
   * Writes both a message and exception to this writer. The message level is "finest".
   */
  @Override
  public void finest(String message, Throwable throwable) {
    if (finestEnabled()) {
      put(FINEST_LEVEL, message, throwable);
    }
  }

  /**
   * Writes a message to this writer. The message level is "finest".
   */
  @Override
  public void finest(String message) {
    finest(message, null);
  }

  /**
   * Writes an exception to this writer. The exception level is "finest".
   */
  @Override
  public void finest(Throwable throwable) {
    finest(null, throwable);
  }

  /**
   * Writes both a message and exception to this writer. If a startup listener is registered, the
   * message will be written to the listener as well to be reported to a user.
   *
   * @since GemFire 7.0
   */
  public void startup(StringId msgID, Object[] params) {
    String message = msgID.toLocalizedString(params);

    StartupStatusListener listener = startupListener;
    if (listener != null) {
      listener.setStatus(message);
    }

    if (infoEnabled()) {
      put(INFO_LEVEL, message, null);
    }
  }

  /**
   * Logs a message and an exception of the given level.
   *
   * @param messageLevel the level code for the message to log
   * @param message the actual message to log
   * @param throwable the actual Exception to log
   */
  @Override
  public abstract void put(int messageLevel, String message, Throwable throwable);

  /**
   * Logs a message and an exception of the given level.
   *
   * @param messageLevel the level code for the message to log
   * @param messageId A locale agnostic form of the message
   * @param parameters the Object arguments to plug into the message
   * @param throwable the actual Exception to log
   */
  @Override
  public abstract void put(int messageLevel, StringId messageId, Object[] parameters,
      Throwable throwable);

  /**
   * formatText manipulates \n and \r chars but supports Windows and Linux/Unix/Mac
   */
  static void formatText(PrintWriter writer, String target, int initialLength) {
    BreakIterator boundary = BreakIterator.getLineInstance();
    boundary.setText(target);
    int start = boundary.first();
    int end = boundary.next();
    int lineLength = initialLength;

    while (end != BreakIterator.DONE) {
      // Look at the end and only accept whitespace breaks
      char endChar = target.charAt(end - 1);
      while (!Character.isWhitespace(endChar)) {
        int lastEnd = end;
        end = boundary.next();
        if (end == BreakIterator.DONE) {
          // give up. We are at the end of the string
          end = lastEnd;
          break;
        }
        endChar = target.charAt(end - 1);
      }
      int wordEnd = end;
      if (endChar == '\n') {
        // trim off the \n since println will do it for us
        wordEnd--;
        if (wordEnd > 0 && target.charAt(wordEnd - 1) == '\r') {
          wordEnd--;
        }
      } else if (endChar == '\t') {
        // figure tabs use 8 characters
        lineLength += 7;
      }
      String word = target.substring(start, wordEnd);
      lineLength += word.length();
      writer.print(word);
      if (endChar == '\n' || endChar == '\r') {
        // force end of line
        writer.println();
        writer.print("  ");
        lineLength = 2;
      }
      start = end;
      end = boundary.next();
    }
    if (lineLength != 0) {
      writer.println();
    }
  }

  /**
   * Check if a message of the given level would actually be logged by this logger. This check is
   * based on the Logger effective level.
   *
   * @param level a message logging level
   * @return true if the given message level is currently being logged.
   */
  private boolean isLoggable(int level) {
    return getLogWriterLevel() <= level;
  }

  /**
   * Log a message, with associated Throwable information. If the logger is currently enabled for
   * the given message level then the given message is logged.
   *
   * @param level One of the message level identifiers, e.g. SEVERE
   * @param message The string message
   * @param thrown - Throwable associated with log message.
   */
  public void log(int level, String message, Throwable thrown) {
    if (isLoggable(level)) {
      put(level, message, thrown);
    }
  }

  /**
   * Log a message. If the logger is currently enabled for the given message level then the given
   * message is logged.
   *
   * @param level One of the message level identifiers, e.g. SEVERE
   * @param message The string message
   */
  public void log(int level, String message) {
    if (isLoggable(level)) {
      put(level, message, null);
    }
  }

  @Override
  public Handler getHandler() {
    return new GemFireHandler(this);
  }

  /** Utility to get a stack trace as a string from a Throwable */
  public static String getStackTrace(Throwable throwable) {
    StringWriter sw = new StringWriter();
    throwable.printStackTrace(new PrintWriter(sw, true));
    return sw.toString();
  }

  /**
   * Utility to periodically log a stack trace for a thread. The thread should be alive when this
   * method is invoked.
   *
   * @param toStdout whether to log to stdout or to this log writer
   * @param targetThread the thread to snapshot
   * @param interval millis to wait betweeen snaps
   * @param done when to stop snapshotting (also ceases if targetThread dies)
   */
  public void logTraces(final boolean toStdout, final Thread targetThread, final int interval,
      final AtomicBoolean done) {
    if (targetThread == null) {
      return;
    }
    Thread watcherThread =
        new LoggingThread("Stack Tracer for '" + targetThread.getName() + "'", false, () -> {
          while (!done.get()) {
            try {
              Thread.sleep(500);
            } catch (InterruptedException e) {
              return;
            }
            if (!done.get() && targetThread.isAlive()) {
              StringBuilder sb = new StringBuilder(500);
              if (toStdout) {
                sb.append("[trace ").append(getTimeStamp()).append("] ");
              }
              StackTraceElement[] els = targetThread.getStackTrace();
              sb.append("Stack trace for '").append(targetThread).append("'")
                  .append(lineSeparator());
              if (els.length > 0) {
                for (final StackTraceElement el : els) {
                  sb.append("\tat ").append(el).append(lineSeparator());
                }
              } else {
                sb.append("    no stack").append(lineSeparator());
              }
              if (toStdout) {
                System.out.println(sb);
              } else {
                info(sb.toString());
              }
            }
          }
        });
    watcherThread.start();
  }

  /** Utility to get a stack trace for a thread */
  public static StringBuilder getStackTrace(Thread targetThread) {
    StringBuilder sb = new StringBuilder(500);
    StackTraceElement[] els = targetThread.getStackTrace();
    sb.append("Stack trace for '").append(targetThread).append("'").append(lineSeparator());
    if (els.length > 0) {
      for (final StackTraceElement el : els) {
        sb.append("\tat ").append(el).append(lineSeparator());
      }
    } else {
      sb.append("    no stack").append(lineSeparator());
    }
    return sb;
  }

  @Override
  public LogWriter convertToLogWriter() {
    return this;
  }

  @Override
  public LogWriterI18n convertToLogWriterI18n() {
    return this;
  }

  public static void setStartupListener(StartupStatusListener mainListener) {
    startupListener = mainListener;
  }

  public static StartupStatusListener getStartupListener() {
    return startupListener;
  }
}
