package org.apache.logging.log4j.core.filter;

import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.message.Message;

@Plugin(
   name = "BurstFilter",
   category = "Core",
   elementType = "filter",
   printObject = true
)
public final class BurstFilter extends AbstractFilter {
   private static final long NANOS_IN_SECONDS = 1000000000L;
   private static final int DEFAULT_RATE = 10;
   private static final int DEFAULT_RATE_MULTIPLE = 100;
   private static final int HASH_SHIFT = 32;
   private final Level level;
   private final long burstInterval;
   private final DelayQueue<BurstFilter.LogDelay> history = new DelayQueue();
   private final Queue<BurstFilter.LogDelay> available = new ConcurrentLinkedQueue();

   private BurstFilter(Level level, float rate, long maxBurst, Filter.Result onMatch, Filter.Result onMismatch) {
      super(onMatch, onMismatch);
      this.level = level;
      this.burstInterval = (long)(1.0E9F * ((float)maxBurst / rate));

      for(int i = 0; (long)i < maxBurst; ++i) {
         this.available.add(new BurstFilter.LogDelay());
      }

   }

   public Filter.Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) {
      return this.filter(level);
   }

   public Filter.Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
      return this.filter(level);
   }

   public Filter.Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
      return this.filter(level);
   }

   public Filter.Result filter(LogEvent event) {
      return this.filter(event.getLevel());
   }

   private Filter.Result filter(Level level) {
      if (!this.level.isMoreSpecificThan(level)) {
         return this.onMatch;
      } else {
         BurstFilter.LogDelay delay;
         for(delay = (BurstFilter.LogDelay)this.history.poll(); delay != null; delay = (BurstFilter.LogDelay)this.history.poll()) {
            this.available.add(delay);
         }

         delay = (BurstFilter.LogDelay)this.available.poll();
         if (delay != null) {
            delay.setDelay(this.burstInterval);
            this.history.add(delay);
            return this.onMatch;
         } else {
            return this.onMismatch;
         }
      }
   }

   public int getAvailable() {
      return this.available.size();
   }

   public void clear() {
      Iterator iter = this.history.iterator();

      while(iter.hasNext()) {
         BurstFilter.LogDelay delay = (BurstFilter.LogDelay)iter.next();
         this.history.remove(delay);
         this.available.add(delay);
      }

   }

   public String toString() {
      return "level=" + this.level.toString() + ", interval=" + this.burstInterval + ", max=" + this.history.size();
   }

   @PluginFactory
   public static BurstFilter createFilter(@PluginAttribute("level") Level level, @PluginAttribute("rate") Float rate, @PluginAttribute("maxBurst") Long maxBurst, @PluginAttribute("onMatch") Filter.Result match, @PluginAttribute("onMismatch") Filter.Result mismatch) {
      Filter.Result onMatch = match == null ? Filter.Result.NEUTRAL : match;
      Filter.Result onMismatch = mismatch == null ? Filter.Result.DENY : mismatch;
      Level actualLevel = level == null ? Level.WARN : level;
      float eventRate = rate == null ? 10.0F : rate;
      if (eventRate <= 0.0F) {
         eventRate = 10.0F;
      }

      long max = maxBurst == null ? (long)(eventRate * 100.0F) : maxBurst;
      return new BurstFilter(actualLevel, eventRate, max, onMatch, onMismatch);
   }

   private class LogDelay implements Delayed {
      private long expireTime;

      public LogDelay() {
      }

      public void setDelay(long delay) {
         this.expireTime = delay + System.nanoTime();
      }

      public long getDelay(TimeUnit timeUnit) {
         return timeUnit.convert(this.expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
      }

      public int compareTo(Delayed delayed) {
         if (this.expireTime < ((BurstFilter.LogDelay)delayed).expireTime) {
            return -1;
         } else {
            return this.expireTime > ((BurstFilter.LogDelay)delayed).expireTime ? 1 : 0;
         }
      }

      public boolean equals(Object o) {
         if (this == o) {
            return true;
         } else if (o != null && this.getClass() == o.getClass()) {
            BurstFilter.LogDelay logDelay = (BurstFilter.LogDelay)o;
            return this.expireTime == logDelay.expireTime;
         } else {
            return false;
         }
      }

      public int hashCode() {
         return (int)(this.expireTime ^ this.expireTime >>> 32);
      }
   }
}
