package de.renew.logging.gui;

import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.Layout;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggingEvent;

import de.renew.engine.common.SimulatorEvent;


/**
 * A Log4j {@link Appender} implementation that collects Renew simulator log
 * events and dispatches them to {@link LoggerRepository} instances. The
 * repository instance is determined based on the Log4j logger name and the
 * simulation run id of the logged event.
 * <p>
 * The {@code GuiAppender} assumes that it receives only log4j events whose
 * message is of the type {@link SimulatorEvent}. Other events will throw a
 * {@link ClassCastException}.
 * </p>
 * <p>
 * Because the {@code GuiAppender} and some related classes generate log4j
 * events in the category {@code "de.renew.gui.logging"}, it is not allowed
 * to configure a {@code GuiAppender} as destination for events of that log
 * category. Otherwise an endless loop might occur. Fortunately, the log events
 * generated here are not {@code SimulatorEvent} instances so that the loop
 * will be broken by a {@code ClassCastException}.
 * </p>
 *
 * @author Sven Offermann (code)
 * @author Michael Duvigneau (documentation)
 */
public class GuiAppender implements Appender {
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(GuiAppender.class);

    /**
     * The filters of this appender as configured by Log4j.
     **/
    private List<Filter> _filters = new Vector<Filter>();

    /**
     * The name of this appender as configured by Log4j.
     **/
    private String _name = null;

    /**
     * The capacity currently configured for {@link LoggerRepository} instances
     * related to this appender. The default value is {@value} , but it can be
     * reconfigured anytime via {@link #setPufferSize(int)}.
     * By default a GuiAppender stores only the last {@value} net simulator log messages
     **/
    private int _stepPufferSize = 20;

    @Override
    public void addFilter(Filter filter) {
        _filters.add(filter);
    }

    @Override
    public Filter getFilter() {
        if (_filters.isEmpty()) {
            return null;
        }
        return _filters.get(0);
    }

    @Override
    public void clearFilters() {
        _filters.clear();
    }

    @Override
    public String getName() {
        return _name;
    }

    /**
     * {@inheritDoc}
     * <p>
     * The {@code GuiAppender} has no {@code ErrorHandler} and ignores
     * the given parameter.
     * </p>
     **/
    @Override
    public void setErrorHandler(ErrorHandler eHandler) {
        // do nothing
    }

    /**
     * {@inheritDoc}
     * <p>
     * The {@code GuiAppender} has no {@code ErrorHandler}.
     * </p>
     * @return always {@code null}
     **/
    @Override
    public ErrorHandler getErrorHandler() {
        return null;
    }

    /**
     * {@inheritDoc}
     * <p>
     * The {@code GuiAppender} uses no {@code Layout} and ignores
     * the given parameter.
     * </p>
     **/
    @Override
    public void setLayout(Layout layout) {
        // No layout used
    }

    /**
     * {@inheritDoc}
     * <p>
     * The {@code GuiAppender} has no {@code Layout}.
     * </p>
     * @return always {@code null}
     **/
    @Override
    public Layout getLayout() {
        // no layout used
        return null;
    }

    @Override
    public void setName(String name) {
        this._name = name;
    }

    /**
     * {@inheritDoc}
     * <p>
     * The {@code GuiAppender} has no {@code Layout}.
     * </p>
     * @return always {@code false}
     **/
    @Override
    public boolean requiresLayout() {
        return false;
    }

    @Override
    public void close() {

    }

    /**
     * Gets the capacity of the {@code LoggerRepository} instances
     * associated with this appender.
     *
     * @return the limit on the number of step traces kept in memory.
     */
    public int getPufferSize() {
        return _stepPufferSize;
    }

    /**
     * Sets the capacity of the {@code LoggerRepository} instances
     * associated with this appender.
     *
     * @param bufferSize the limit on the number of step traces kept in memory.
     */
    public void setPufferSize(int bufferSize) {
        this._stepPufferSize = bufferSize;
    }

    /**
     * Receive a {@link SimulatorEvent} wrapped in a {@link LoggingEvent} and
     * dispatch it to the appropriate {@link LoggerRepository} instances.
     * <p>
     * The {@code GuiAppender} is peckish and accepts only log4j events
     * whose message is of the type {@link SimulatorEvent}.
     * </p>
     *
     * @param event the log event with the {@link SimulatorEvent} message to
     *            collect
     * @throws ClassCastException if the message coming with the event is not of
     *             the type {@link SimulatorEvent}.
     **/
    @Override
    public void doAppend(LoggingEvent event) {
        SimulatorEvent simEvent = (SimulatorEvent) event.getMessage();
        MainRepository mRepository = MainRepositoryManager.getInstance().getRepository(simEvent);
        if (mRepository == null) {
            // cannot log the message, cause i cannot find the repository for this simulation step
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(
                    this + ": Discarding event because no matching repository exists: " + simEvent);
            }
            return;
        }

        String[] definedLoggerNames = getDefinedLoggers(event.getLoggerName());
        for (int x = 0; x < definedLoggerNames.length; x++) {
            LoggerRepository repository =
                mRepository.getLoggerRepository(definedLoggerNames[x], _stepPufferSize);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(this + ": Dispatching event to " + repository + ": " + simEvent);
            }
            repository.addEvent(simEvent);
        }
    }

    /**
     * Compute names of all Log4j loggers that are currently configured with a
     * {@code GuiAppender} and are covered by the given
     * {@code eventLoggerName}.
     *
     * @param eventLoggerName a qualified logger name for which the configured
     *            gui loggers should be computed.
     * @return qualified logger names which are associated with a gui logger.
     */
    private static String[] getDefinedLoggers(String eventLoggerName) {
        Vector<String> loggers = new Vector<String>();
        Category logger = Logger.getLogger(eventLoggerName);
        while (logger != null) {
            Enumeration<?> enumeration = logger.getAllAppenders();
            boolean found = false;
            while (enumeration.hasMoreElements() && !found) {
                Appender appender = (Appender) enumeration.nextElement();
                if (appender instanceof GuiAppender) {
                    found = true;
                    loggers.add(logger.getName());
                }
            }
            logger = logger.getParent();
        }

        return loggers.toArray(new String[] { });
    }
}