
package uk.co.wingpath.util;

import java.util.*;

/**
* This class implements the {@link Reporter} interface by wrapping a
* {@code Reporter} and applying re-write rules to reported messages.
* <p>A re-write rule can change the text of a message, change the level of
* a message, add a help ID, or discard a message altogether.
*/
public class SubstituteReporter
    implements Reporter
{
    private class Rule
    {
        final Reporter.Level level;
        final Substituter substituter;
        final Reporter.Level newLevel;
        final String helpId;

        Rule (Reporter.Level level, Substituter substituter,
            Reporter.Level newLevel, String helpId)
        {
            this.level = level;
            this.substituter = substituter;
            this.newLevel = newLevel;
            this.helpId = helpId;
        }

        boolean apply (Reporter.Level level, String helpId, String msg,
            Throwable t)
        {
            if (level != this.level)
                return false;
            String newMsg = substituter.substitute (msg);
            if (newMsg == null)
                return false;
            if (this.helpId != null)
                helpId = this.helpId;
            switch (newLevel)
            {
            case FATAL:
                if (t == null)
                    return reporter.fatal (newMsg);
                else
                    return reporter.fatal (t, newMsg);
                // break;
            case ERROR:
                reporter.error (helpId, newMsg);
                break;
            case WARN:
                reporter.warn (helpId, newMsg);
                break;
            case INFO:
                reporter.info (helpId, newMsg);
                break;
            case TRACE:
                reporter.trace (helpId, newMsg);
                break;
            case DEBUG:
                reporter.debug (newMsg);
                break;
            }
            return true;
        }
    }

    private final Reporter reporter;
    private final List<Rule> rules;

    /**
    * Constructs a {@code SubstituteReporter} wrapping the supplied
    * reporter.
    * @param reporter the reporter to be wrapped.
    */
    public SubstituteReporter (Reporter reporter)
    {
        if (reporter == null)
            throw new NullPointerException ("reporter must not be null");
        this.reporter = reporter;
        rules = new ArrayList<Rule> ();
    }

    /**
    * Adds a re-write rule.
    * @param level the level to which the rule applies.
    * @param substituter the substitution to be applied.
    * @param newLevel the level at which messages are to be forwarded.
    * The special level {@code NONE} may be used to discard metching messages.
    * @param helpId the help ID to be added. May be {@code null} if no
    * help ID is to be added.
    */
    public void addRule (Reporter.Level level, Substituter substituter,
        Reporter.Level newLevel, String helpId)
    {
        rules.add (new Rule (level, substituter, newLevel, helpId));
    }

    /**
    * Adds a re-write rule.
    * @param level the level to which the rule applies.
    * @param pattern the pattern to match the message against.
    * @param format the format for constructing the new message.
    * @param newLevel the level at which messages are to be forwarded.
    * The special level {@code NONE} may be used to discard metching messages.
    * @param helpId the help ID to be added. May be {@code null} if no
    * help ID is to be added.
    */
    public void addRule (Reporter.Level level, String pattern, String format,
        Reporter.Level newLevel, String helpId)
    {
        rules.add (new Rule (level, new Substituter (pattern, format),
            newLevel, helpId));
    }

    private boolean applyRules (Reporter.Level level, String helpId,
        Throwable t, String fmt, Object... args)
    {
        String msg = String.format (fmt, args);

        for (Rule rule : rules)
        {
            if (rule.apply (level, helpId, msg, t))
                return true;
        }

        return false;
    }

    @Override
    public boolean fatal (String fmt, Object... args)
    {
        if (applyRules (Reporter.Level.FATAL, null, null, fmt, args))
            return true;
        return reporter.fatal (fmt, args);
    }

    @Override
    public boolean fatal (Throwable t, String fmt, Object... args)
    {
        if (applyRules (Reporter.Level.FATAL, null, t, fmt, args))
            return true;
        return reporter.fatal (t, fmt, args);
    }

    @Override
    public void error (String helpId, String fmt, Object... args)
    {
        if (!applyRules (Reporter.Level.ERROR, helpId, null, fmt, args))
            reporter.error (helpId, fmt, args);
    }

    @Override
    public void warn (String helpId, String fmt, Object... args)
    {
        if (!applyRules (Reporter.Level.WARN, helpId, null, fmt, args))
            reporter.warn (helpId, fmt, args);
    }

    @Override
    public void info (String helpId, String fmt, Object... args)
    {
        if (!applyRules (Reporter.Level.INFO, helpId, null, fmt, args))
            reporter.info (helpId, fmt, args);
    }

    @Override
    public void trace (String helpId, String fmt, Object... args)
    {
        if (!applyRules (Reporter.Level.TRACE, null, null, fmt, args))
            reporter.trace (helpId, fmt, args);
    }

    @Override
    public boolean debug (String fmt, Object... args)
    {
        if (!applyRules (Reporter.Level.DEBUG, null, null, fmt, args))
            reporter.debug (fmt, args);
        return true;
    }

    @Override
    public void clear ()
    {
        reporter.clear ();
    }
}

