package com.onrelay.log.ext;

import java.util.*;

import javax.annotation.*;
import javax.annotation.concurrent.*;

import org.apache.log4j.*;
import org.apache.log4j.helpers.*;
import org.apache.log4j.spi.*;

/**
 * @author <a href="mailto:ttm@onrelay.com">Thomas Muller</a>
 * @version $Revision: 1.1 $ - $Date: 2008/10/08 18:40:48 $
 */

@ThreadSafe

public class ErrorAppender
		extends AppenderSkeleton
		implements AppenderAttachable {

	public static final int DEFAULT_BUFFER_SIZE = 10;

	public static final int MIN_BUFFER_SIZE = 1;

	public static final int MAX_BUFFER_SIZE = 0xFFFF;


	protected volatile int bufferSize = DEFAULT_BUFFER_SIZE;

	protected volatile Level triggerThreshold = Level.WARN;

	@GuardedBy( "self" )
	private final AppenderAttachable aa = new AppenderAttachableImpl();

	@GuardedBy( "bufferMutex" )
	private final LinkedList< LoggingEvent > eventBuffer =
			new LinkedList< LoggingEvent >();

	private final Object bufferMutex = new Object();


	protected void append( @Nullable LoggingEvent event ) {
		if( event == null ) {
			return;
		}

		synchronized( bufferMutex ) {
			if( eventBuffer.size() >= bufferSize ) {
				eventBuffer.removeFirst();
			}
			eventBuffer.add( event );

			if( event.getLevel().isGreaterOrEqual( triggerThreshold ) ) {
				appendToAll();
			}
		}
	}

	public void addAppender( @Nullable Appender appender ) {
		if( appender != null ) {
			synchronized( aa ) {
				aa.addAppender( appender );
			}
		}
	}

	@Nonnull
	public Enumeration getAllAppenders() {
		synchronized( aa ) {

			// Prevent ConcurrentModificationException
			ArrayList list = new ArrayList( 5 );
			for( Enumeration e = aa.getAllAppenders(); e.hasMoreElements(); ) {
				list.add( e.nextElement() );
			}

			return Collections.enumeration( list );
		}
	}

	@CheckForNull
	public Appender getAppender( @Nullable String name ) {
		Appender result = null;
		if( name != null ) {
			synchronized( aa ) {
				result =  aa.getAppender( name );
			}
		}
		return result;
	}

	public void removeAllAppenders() {
		synchronized( aa ) {
			aa.removeAllAppenders();
		}
	}

	public void removeAppender( @Nullable Appender appender ) {
		if( appender != null ) {
			synchronized( aa ) {
				aa.removeAppender( appender );
			}
		}
	}

	public void removeAppender( @Nullable String name ) {
		if( name != null ) {
			synchronized( aa ) {
				aa.removeAppender( name );
			}
		}
	}

	public boolean isAttached( @Nullable Appender appender ) {
		boolean result = false;

		if( appender != null ) {
			synchronized( aa ) {
				result = aa.isAttached( appender );
			}
		}

		return false;
	}

	public void close() {
		// Noop
	}

	public boolean requiresLayout() {
		return false;
	}

	public void activateOptions() {
		// Noop
	}

	public void setBufferSize( @Signed int bufferSize ) {
		this.bufferSize = Math.min( Math.max( MIN_BUFFER_SIZE, bufferSize ),
				MAX_BUFFER_SIZE );
	}

	@Nonnegative
	public int getBufferSize() {
		return bufferSize;
	}


	public void setTriggerThreshold( @Nullable Level level ) {
		if( level != null ) {
			triggerThreshold = level;
		}
	}

	@Nonnull
	public Level getTriggerThreshold() {
		return triggerThreshold;
	}


	private void appendToAll() {
		synchronized( eventBuffer ) {

			// Iterate buffer
			for( Iterator< LoggingEvent > i = eventBuffer.iterator();
					i.hasNext(); ) {
				LoggingEvent event = i.next();

				// Distribute to all appenders
				synchronized( aa ) {
					for( Enumeration e = aa.getAllAppenders();
							e.hasMoreElements(); ) {
						( ( Appender ) e.nextElement() ).doAppend( event );
					}
				}

				// Make sure we drain the buffer
				i.remove();
			}
		}

		// INVARIANT: eventBuffer.size() == 0
	}

}

