package com.emiles.util.log.logback;

import com.emiles.util.log.TokenBucket;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.boolex.EvaluationException;
import ch.qos.logback.core.boolex.EventEvaluator;
import ch.qos.logback.core.spi.ContextAwareBase;

/**
 * Logback Evaluator that utilizes the TokenBucket object to prevent the system from 
 * overloading the email server when a burst of logging events with lots of logging
 * event levels greater or equal to predefined logging level.
 * 
 * @see ch.qos.logback.classic.Level
 * 
 * Supported levels:
 *   ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF
 * 
 * Implications:
 *   - It's possible that some logging events will be lost / dropped out of the cyclic 
 *   buffer because SMTPAppender uses a cyclic buffer to buffer the logging events 
 *   before it sends out the email.
 *   - logging events that are greater or equal to the predefined logging level
 *   may not trigger the 'send email' action immediately until next event.
 *   - please note these implications are specific to the logback SMTPAppender 
 *   with TokenBucketEvaluator, more details logging info can always be found 
 *   in the application's log file through proper configuration.
 *   
 * @author dwang
 *
 */
public class TokenBucketEvaluator extends ContextAwareBase implements
    EventEvaluator
{
    String myName = null;
    boolean myStarted = false;
    
    /**
     * Number of tokens to add to the token bucket. For example, a value of 10
     * means 10 tokens will be added to the bucket every
     * <code>tokenFillInterval</code> seconds.
     */
    private long tokenFillAmount;

    /**
     * Interval, in seconds, at which to add tokens to the logging evaluator token
     * bucket. For example, a value of 6 means <code>tokenFillAmount</code>
     * tokens will be added to the bucket every 6 seconds.
     */
    private long tokenFillInterval;

    /**
     * The maximum number of tokens the logging evaluator will allow in its token
     * bucket. This value dictates the maximum traffic burst that can be logged to
     * any appender that uses the <code>TokenBucketEvaluator</code>.
     */
    private long maxTokens;

    private TokenBucket tokenBucket;
    
    static final String DEFAULT_EVALUATOR_LOG_LEVEL = "ERROR";

    private Level myLevel;

    public boolean evaluate (Object event) throws NullPointerException,
        EvaluationException
    {
        if (!myStarted) {
            throw new IllegalStateException("Evaluator [" + myName + "] was called in stopped state");
        }
        
        LoggingEvent loggingEvent = (LoggingEvent) event;
        
        /*
         * logic to trigger the send email action
         *   - one or more token are available in bucket
         *   - logging level is greater or equal to the specified level 
         */
        if ( loggingEvent.getLevel().isGreaterOrEqual(myLevel) 
             && tokenBucket.getToken() )
            return true;
        else
            return false;
    }

    public String getName ()
    {
        return myName;
    }

    public void setName (String name)
    {
        myName = name;
    }

    public boolean isStarted ()
    {
        return myStarted;
    }

    public void setLogLevel (String myLogLevel)
    {
        this.myLevel = Level.toLevel(myLogLevel);
    }

    public void start ()
    {
        if(myLevel == null)
        {
            this.myLevel = Level.toLevel(DEFAULT_EVALUATOR_LOG_LEVEL);
        }
        
        // initialize tokenBucket here because the tokenFillAmount,
        // tokenFillInterval & maxTokens attributes get set
        // via logback configuration when it instantiates the evaluator
        if (tokenBucket == null) 
        {
          tokenBucket = new TokenBucket(tokenFillAmount, tokenFillInterval,
              maxTokens);
        }
        
        myStarted = true;
    }

    public void stop ()
    {
        myStarted = true;
    }
    
    public long getMaxTokens() {
        return maxTokens;
    }
    
    public void setMaxTokens(long maxTokens) {
        this.maxTokens = maxTokens;
    }
    
    public long getTokenFillInterval() {
        return tokenFillInterval;
    }
    
    public void setTokenFillInterval(long tokenFillInterval) {
        this.tokenFillInterval = tokenFillInterval;
    }
    
    public long getTokenFillAmount() {
        return tokenFillAmount;
    }
    
    public void setTokenFillAmount(long tokenFillRate) {
        this.tokenFillAmount = tokenFillRate;
    }
}
