/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.core.sift;

import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.util.Duration;

/**
 * This appender serves as the base class for actual SiftingAppenders
 * implemented by the logback-classic and logback-access modules. In a nutshell,
 * a SiftingAppender contains other appenders which it can build dynamically
 * depending on discriminating values supplied by the event currently being
 * processed. The appender to build (dynamically) is specified as part of a
 * configuration file.
 *
 * @author Ceki Gulcu
 */
public abstract class SiftingAppenderBase<E> extends
        AppenderBase<E> {

  protected AppenderTracker<E> appenderTracker;
  AppenderFactory<E> appenderFactory;
  Duration timeout = new Duration(AppenderTracker.DEFAULT_TIMEOUT);
  int maxAppenderCount = AppenderTracker.DEFAULT_MAX_COMPONENTS;

  Discriminator<E> discriminator;

  public Duration getTimeout() {
    return timeout;
  }

  public void setTimeout(Duration timeout) {
    this.timeout = timeout;
  }

  public int getMaxAppenderCount() {
    return maxAppenderCount;
  }

  public void setMaxAppenderCount(int maxAppenderCount) {
    this.maxAppenderCount = maxAppenderCount;
  }

  /**
   * This setter is intended to be invoked by SiftAction. Customers have no reason to invoke
   * this method directly.
   */
  public void setAppenderFactory(AppenderFactory<E> appenderFactory) {
    this.appenderFactory = appenderFactory;
  }

  @Override
  public void start() {
    int errors = 0;
    if (discriminator == null) {
      addError("Missing discriminator. Aborting");
      errors++;
    }
    if (!discriminator.isStarted()) {
      addError("Discriminator has not started successfully. Aborting");
      errors++;
    }
    if (appenderFactory == null) {
      addError("AppenderFactory has not been set. Aborting");
      errors++;
    } else {
      appenderTracker = new AppenderTracker<E>(context, appenderFactory);
      appenderTracker.setMaxComponents(maxAppenderCount);
      appenderTracker.setTimeout(timeout.getMilliseconds());
    }
    if (errors == 0) {
      super.start();
    }
  }

  @Override
  public void stop() {
    for (Appender<E> appender : appenderTracker.allComponents()) {
      appender.stop();
    }
  }

  abstract protected long getTimestamp(E event);

  @Override
  protected void append(E event) {
    if (!isStarted()) {
      return;
    }
    String discriminatingValue = discriminator.getDiscriminatingValue(event);
    long timestamp = getTimestamp(event);

    Appender<E> appender = appenderTracker.getOrCreate(discriminatingValue, timestamp);
    // marks the appender for removal as specified by the user
    if (eventMarksEndOfLife(event)) {
      appenderTracker.endOfLife(discriminatingValue);
    }
    appenderTracker.removeStaleComponents(timestamp);
    appender.doAppend(event);
  }

  protected abstract boolean eventMarksEndOfLife(E event);

  public Discriminator<E> getDiscriminator() {
    return discriminator;
  }

  public void setDiscriminator(Discriminator<E> discriminator) {
    this.discriminator = discriminator;
  }


  // sometimes one needs to close a nested appender immediately
  // for example when executing a command which has its own nested appender
  // and the command also cleans up after itself. However, an open file appender
  // will prevent the folder from being deleted
  // see http://www.qos.ch/pipermail/logback-user/2010-March/001487.html

  /**
   * @since 0.9.19
   */
  public AppenderTracker<E> getAppenderTracker() {
    return appenderTracker;
  }

  public String getDiscriminatorKey() {
    if (discriminator != null) {
      return discriminator.getKey();
    } else {
      return null;
    }
  }
}
