/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions Copyright 2012-2015 ForgeRock AS.
 */
package org.opends.server.loggers;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.forgerock.i18n.LocalizableMessage;
import org.opends.server.admin.std.server.DebugLogPublisherCfg;

/**
 * This class defines the set of methods and structures that must be
 * implemented for a Directory Server debug log publisher.
 *
 * @param  <T>  The type of debug log publisher configuration handled
 *              by this log publisher implementation.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.VOLATILE,
     mayInstantiate=false,
     mayExtend=true,
     mayInvoke=false)
public abstract class DebugLogPublisher<T extends DebugLogPublisherCfg>
    implements LogPublisher<T>
{
  /** The default global settings key. */
  private static final String GLOBAL= "_global";

  /** The map of class names to their trace settings. */
  private Map<String,TraceSettings> classTraceSettings;

  /** The map of class names to their method trace settings. */
  private Map<String,Map<String,TraceSettings>> methodTraceSettings;



  /**
   * Construct a default configuration where the global scope will
   * only log at the ERROR level.
   */
  protected DebugLogPublisher()
  {
    classTraceSettings = null;
    methodTraceSettings = null;

    //Set the global settings so that nothing is logged.
    addTraceSettings(null, TraceSettings.DISABLED);
  }



  /** {@inheritDoc} */
  @Override
  public boolean isConfigurationAcceptable(T configuration,
                      List<LocalizableMessage> unacceptableReasons)
  {
    // This default implementation does not perform any special
    // validation. It should be overridden by debug log publisher
    // implementations that wish to perform more detailed validation.
    return true;
  }



  /**
   * Gets the method trace levels for a specified class.
   *
   * @param  className  The fully-qualified name of the class for
   *                    which to get the trace levels.
   *
   *@return  An unmodifiable map of trace levels keyed by method name,
   *         or {@code null} if no method-level tracing is configured
   *         for the scope.
   */
  final Map<String,TraceSettings> getMethodSettings(
                                              String className)
  {
    if(methodTraceSettings == null)
    {
      return null;
    }
    else
    {
      return methodTraceSettings.get(className);
    }
  }



  /**
   * Get the trace settings for a specified class.
   *
   * @param className
   *          The fully-qualified name of the class for which to get the trace
   *          levels.
   * @return The current trace settings for the class.
   */
  final TraceSettings getClassSettings(String className)
  {
    TraceSettings settings = null;
    if (classTraceSettings != null)
    {
      // Find most specific trace setting
      // which covers this fully qualified class name
      // Search up the hierarchy for a match.
      String searchName = className;
      settings = classTraceSettings.get(searchName);
      while (settings == null && searchName != null)
      {
        int clipPoint = searchName.lastIndexOf('$');
        if (clipPoint == -1)
        {
          clipPoint = searchName.lastIndexOf('.');
        }
        if (clipPoint != -1)
        {
          searchName = searchName.substring(0, clipPoint);
          settings = classTraceSettings.get(searchName);
        }
        else
        {
          searchName = null;
        }
      }
      // Try global settings
      // only if no specific target is defined
      if (settings == null && classTraceSettings.size()==1) {
        settings = classTraceSettings.get(GLOBAL);
      }
    }
    return settings == null ? TraceSettings.DISABLED : settings;
  }



  /**
   * Adds a trace settings to the current set for a specified scope.
   * If a scope is not specified, the settings will be set for the
   * global scope. The global scope settings are used when no other
   * scope matches.
   *
   * @param  scope     The scope for which to set the trace settings.
   *                   This should be a fully-qualified class name, or
   *                   {@code null} to set the trace settings for the
   *                   global scope.
   * @param  settings  The trace settings for the specified scope.
   */
  public final void addTraceSettings(String scope, TraceSettings settings)
  {
    if (scope == null) {
      setClassSettings(GLOBAL, settings);
    }
    else {
      int methodPt= scope.lastIndexOf('#');
      if (methodPt != -1) {
        String methodName= scope.substring(methodPt+1);
        scope= scope.substring(0, methodPt);
        setMethodSettings(scope, methodName, settings);
      }
      else {
        setClassSettings(scope, settings);
      }
    }
  }

  /**
   * Determine whether a trace setting is already defined for a particular
   * scope.
   *
   * @param scope
   *          The scope for which to make the determination. This should be a
   *          fully-qualified class name.
   * @return {@code true} if a trace settings is defined for the specified
   *         scope, {@code false} otherwise.
   */
  final boolean hasTraceSettings(String scope)
  {
    int methodPt = scope.lastIndexOf('#');
    if (methodPt != -1)
    {
      String methodName = scope.substring(methodPt + 1);
      scope = scope.substring(0, methodPt);
      if (methodTraceSettings != null)
      {
        Map<String, TraceSettings> methodLevels =
            methodTraceSettings.get(scope);
        if (methodLevels != null)
        {
          return methodLevels.containsKey(methodName);
        }
      }
    }
    else if (classTraceSettings != null)
    {
      return classTraceSettings.containsKey(scope);
    }
    return false;
  }



  /**
   * Remove a trace setting by scope.
   *
   * @param  scope  The scope for which to remove the trace setting.
   *                This should be a fully-qualified class name, or
   *                {@code null} to remove the trace setting for the
   *                global scope.
   *
   * @return  The trace settings for the specified scope, or
   *          {@code null} if no trace setting is defined for that
   *          scope.
   */
  final TraceSettings removeTraceSettings(String scope)
  {
    TraceSettings removedSettings = null;
    if (scope == null) {
      if(classTraceSettings != null)
      {
        removedSettings =  classTraceSettings.remove(GLOBAL);
      }
    }
    else {
      int methodPt= scope.lastIndexOf('#');
      if (methodPt != -1) {
        String methodName= scope.substring(methodPt+1);
        scope= scope.substring(0, methodPt);
        if(methodTraceSettings != null)
        {
          Map<String, TraceSettings> methodLevels =
              methodTraceSettings.get(scope);
          if(methodLevels != null)
          {
            removedSettings = methodLevels.remove(methodName);
            if(methodLevels.isEmpty())
            {
              methodTraceSettings.remove(scope);
            }
          }
        }
      }
      else {
        if(classTraceSettings != null)
        {
          removedSettings =  classTraceSettings.remove(scope);
        }
      }
    }

    return removedSettings;
  }

  /**
   * Set the trace settings for a class.
   *
   * @param  className  The class name.
   * @param  settings   The trace settings for the class.
   */
  private final synchronized void setClassSettings(String className, TraceSettings settings)
  {
    if (classTraceSettings == null)
    {
      classTraceSettings = new HashMap<>();
    }
    classTraceSettings.put(className, settings);
  }



  /**
   * Set the method settings for a particular method in a class.
   *
   * @param  className   The class name.
   * @param  methodName  The method name.
   * @param  settings    The trace settings for the method.
   */
  private final synchronized void setMethodSettings(String className,
      String methodName, TraceSettings settings)
  {
    if (methodTraceSettings == null) {
      methodTraceSettings = new HashMap<>();
    }
    Map<String, TraceSettings> methodLevels = methodTraceSettings.get(className);
    if (methodLevels == null)
    {
      methodLevels = new TreeMap<>();
      methodTraceSettings.put(className, methodLevels);
    }
    methodLevels.put(methodName, settings);
  }



  /**
   * Log an arbitrary event in a method.
   * @param  settings        The current trace settings in effect.
   * @param  signature       The method signature.
   * @param  sourceLocation  The location of the method in the source.
   * @param  msg             The message to be logged.
   * @param  stackTrace      The stack trace at the time the message
   *                         is logged or null if its not available.
   */
  public abstract void trace(TraceSettings settings, String signature,
      String sourceLocation, String msg, StackTraceElement[] stackTrace);



  /**
   * Log a caught exception in a method.
   * @param  settings        The current trace settings in effect.
   * @param  signature       The method signature.
   * @param  sourceLocation  The location of the method in the source.
   * @param  msg             The message to be logged.
   * @param  ex              The exception that was caught.
   * @param  stackTrace      The stack trace at the time the exception
   *                         is caught or null if its not available.
   */
  public abstract void traceException(TraceSettings settings, String signature,
      String sourceLocation, String msg, Throwable ex,
      StackTraceElement[] stackTrace);

}
