// Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.core.logging;

import java.util.List;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;

/**
 * Filter that can be used to filter based on the logger name (usually the class name).
 *
 * <p>Does the comparison using a startsWith() test, so that the filter can be used to filter by
 * package as well. To perform an exact match, then provide a NULL terminated string (e.g. \0)
 */
public class LoggerFilter extends Filter<ILoggingEvent> {
  private final List<String> loggerName;
  private final Boolean exclude;
  /**
   * Contruct new LoggerFilter
   *
   * @param loggerName The name of the logger to match against
   * @param exclude Should matched loggers be included (false) or excluded (true)
   */
  public LoggerFilter(String loggerName, Boolean exclude) {
    this.loggerName = Lists.newArrayList(loggerName);
    this.exclude = exclude;
  }

  /**
   * Construct new LoggerFilter
   *
   * @param loggerName A list of loggers to match against
   * @param exclude Should matched loggers be included (false) or excluded (true)
   */
  public LoggerFilter(List<String> loggerName, Boolean exclude) {
    this.loggerName = loggerName;
    this.exclude = exclude;
  }

  /**
   * Decide whether or not to deny the event based on the settings of this filter
   *
   * @param event The logging event to test
   */
  @Override
  public FilterReply decide(ILoggingEvent event) {
    if (Strings.isNullOrEmpty(event.getLoggerName())) {
      return FilterReply.DENY;
    }

    for (String name : loggerName) {
      FilterReply ret;
      if (name.endsWith("/0")) {
        String nullTerminatedLN = event.getLoggerName() + "/0";
        ret = nameMatches(nullTerminatedLN.equals(name));
      } else {
        ret = nameMatches(event.getLoggerName().startsWith(name));
      }

      ret = shouldReturn(ret);
      if (ret != null) {
        return ret;
      }
    }

    if (exclude) {
      return FilterReply.NEUTRAL;
    } else {
      return FilterReply.DENY;
    }
  }

  private FilterReply shouldReturn(FilterReply ret) {
    if (exclude && ret == FilterReply.DENY) {
      return FilterReply.DENY;
    } else if (!exclude && ret == FilterReply.NEUTRAL) {
      return FilterReply.NEUTRAL;
    } else {
      return null;
    }
  }

  /**
   * Determine whether, on matching the logger name, we should reply DENY or NEUTRAL based on the
   * current settings
   *
   * @param matches Does it match or not
   * @return FilterReply.DENY or FilterReply.NEUTRAL
   */
  private FilterReply nameMatches(boolean matches) {
    if (matches) {
      return exclude ? FilterReply.DENY : FilterReply.NEUTRAL;
    } else {
      return !exclude ? FilterReply.DENY : FilterReply.NEUTRAL;
    }
  }

  /** @return the loggerName */
  public List<String> getLoggerName() {
    return loggerName;
  }

  /** @return the exclude */
  public Boolean getExclude() {
    return exclude;
  }
}
