/*
 * Copyright (c) 2009-2013 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2009-2013 Jason Mehrens. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import com.sun.mail.util.logging.MailHandler;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Properties;
import java.util.logging.*;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;

/**
 * Demo for the different configurations for the MailHandler.
 * If the logging properties file or class is not specified then this
 * demo will apply some default settings to store emails in the users temp dir.
 * @author Jason Mehrens
 */
public class MailHandlerDemo {

    /**
     * This class name.
     */
    private static final String CLASS_NAME = MailHandlerDemo.class.getName();
    /**
     * The logger for this class name.
     */
    private static final Logger LOGGER = Logger.getLogger(CLASS_NAME);

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        init(); //may create log messages.
        try {
            LOGGER.log(Level.FINEST, "This is the finest part of the demo.",
                    new MessagingException("Fake"));
            LOGGER.log(Level.FINER, "This is the finer part of the demo.",
                    new NullPointerException("Fake"));
            LOGGER.log(Level.FINE, "This is the fine part of the demo.");
            LOGGER.log(Level.CONFIG, "Logging config file is {0}.", getConfigLocation());
            LOGGER.log(Level.INFO, "Your temp directory is {0}, please wait...", getTempDir());

            try { //waste some time for the custom formatter.
                Thread.sleep(3L * 1000L);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }

            LOGGER.log(Level.WARNING, "This is a warning.", new FileNotFoundException("Fake"));
            LOGGER.log(Level.SEVERE, "The end of the demo.", new IOException("Fake"));
        } finally {
            closeHandlers();
        }
    }

    /**
     * Used debug problems with the logging.properties.
     * @param prefix a string to prefix the output.
     * @param err any PrintStream or null for System.out.
     */
    private static void checkConfig(String prefix, PrintStream err) {
        if (prefix == null || prefix.trim().length() == 0) {
            prefix = "DEBUG";
        }

        if (err == null) {
            err = System.out;
        }

        try {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                err.println(prefix + ": SecurityManager.class=" + sm.getClass().getName());
                err.println(prefix + ": SecurityManager.toString=" + sm);
            } else {
                err.println(prefix + ": SecurityManager.class=" + null);
                err.println(prefix + ": SecurityManager.toString=" + null);
            }

            LogManager manager = LogManager.getLogManager();
            String key = "java.util.logging.config.file";
            String cfg = System.getProperty(key);
            if (cfg != null) {
                err.println(prefix + ": " + cfg);
                File f = new File(cfg);
                err.println(prefix + ": AbsolutePath=" + f.getAbsolutePath());
                err.println(prefix + ": CanonicalPath=" + f.getCanonicalPath());
                err.println(prefix + ": length=" + f.length());
                err.println(prefix + ": canRead=" + f.canRead());
                err.println(prefix + ": lastModified="
                        + new java.util.Date(f.lastModified()));
                //force any errors, only safe is key is present.
                manager.readConfiguration();
            } else {
                err.println(prefix + ": " + key + " is not set as a system property.");
            }
            err.println(prefix + ": LogManager.class=" + manager.getClass().getName());
            err.println(prefix + ": LogManager.toString=" + manager);

            final String p = MailHandler.class.getName();
            key = p.concat(".mail.to");
            String to = manager.getProperty(key);
            err.println(prefix + ": TO=" + to);
            err.println(prefix + ": TO="
                    + Arrays.toString(InternetAddress.parse(to, false)));
            err.println(prefix + ": TO="
                    + Arrays.toString(InternetAddress.parse(to, true)));

            key = p.concat(".mail.from");
            String from = manager.getProperty(key);
            if (from == null || from.length() == 0) {
                Session session = Session.getInstance(new Properties());
                InternetAddress local = InternetAddress.getLocalAddress(session);
                err.println(prefix + ": FROM=" + local);
            } else {
                err.println(prefix + ": FROM="
                        + Arrays.asList(InternetAddress.parse(from, false)));
                err.println(prefix + ": FROM="
                        + Arrays.asList(InternetAddress.parse(from, true)));
            }
        } catch (Throwable error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }
    }

    /**
     * Example for body only messages.
     * On close the remaining messages are sent.
     */
    private static void initBodyOnly() {
        MailHandler h = new MailHandler();
        h.setSubject("Body only demo");
        LOGGER.addHandler(h);
    }

    /**
     * Example showing that when the mail handler reaches capacity it
     * will format and send the current records.  Capacity is used to roughly
     * limit the size of an outgoing message.
     * On close any remaining messages are sent.
     */
    private static void initLowCapacity() {
        MailHandler h = new MailHandler(5);
        h.setSubject("Low capacity demo");
        LOGGER.addHandler(h);
    }

    /**
     * Example for body only messages.
     * On close any remaining messages are sent.
     */
    private static void initSimpleAttachment() {
        MailHandler h = new MailHandler();
        h.setSubject("Body and attachment demo");
        h.setAttachmentFormatters(new Formatter[]{new XMLFormatter()});
        h.setAttachmentNames(new String[]{"data.xml"});
        LOGGER.addHandler(h);
    }

    /**
     * Example setup for priority messages by level.
     * If the push level is triggered the message is high priority.
     * Otherwise, on close any remaining messages are sent.
     */
    private static void initWithPushLevel() {
        MailHandler h = new MailHandler();
        h.setSubject("Push level demo");
        h.setPushLevel(Level.WARNING);
        LOGGER.addHandler(h);
    }

    /**
     * Example for priority messages by custom trigger.
     * If the push filter is triggered the message is high priority.
     * Otherwise, on close any remaining messages are sent.
     */
    private static void initWithPushFilter() {
        MailHandler h = new MailHandler();
        h.setSubject("Push on MessagingException demo");
        h.setPushLevel(Level.ALL);
        h.setPushFilter(new MessageErrorsFilter(true));
        LOGGER.addHandler(h);
    }

    /**
     * Example for circular buffer behavior.  The level, push level, and
     * capacity are set the same so that the memory handler push results
     * in a mail handler push.  All messages are high priority.
     * On close any remaining records are discarded because they never reach
     * the mail handler.
     */
    private static void initPushOnly() {
        final int capacity = 3;
        final Level pushLevel = Level.WARNING;
        final MailHandler h = new MailHandler(capacity);
        h.setPushLevel(pushLevel);
        h.setSubject("Push only demo");
        MemoryHandler m = new MemoryHandler(h, capacity, pushLevel);
        h.setLevel(m.getLevel());
        LOGGER.addHandler(m);
        pushOnlyHandler = h;
    }

    /**
     * Holds on to the push only handler.
     * Only declared here to apply fallback settings.
     */
    private static Handler pushOnlyHandler;

    /**
     * Example for circular buffer behavior as normal priority.  The push level,
     * and capacity are set the same so that the memory handler push results
     * in a mail handler push.  All messages are normal priority.
     * On close any remaining records are discarded because they never reach
     * the mail handler.
     */
    private static void initPushNormal() {
        final int capacity = 3;
        final MailHandler h = new MailHandler(capacity);
        h.setSubject("Push normal demo");
        MemoryHandler m = new MemoryHandler(h, capacity, Level.WARNING) {

            @Override
            public void push() {
                super.push();  //push to target.
                super.flush(); //make the target send the email.
            }
        };
        LOGGER.addHandler(m);
        pushNormalHandler = h;
    }

    /**
     * Holds on to the push normal handler.
     * Only declared here to apply fallback settings.
     */
    private static Handler pushNormalHandler;

    /**
     *  Example for various kinds of custom sorting, formatting, and filtering
     *  for multiple attachment messages.
     *  On close any remaining messages are sent.
     */
    private static void initCustomAttachments() {
        MailHandler h = new MailHandler();

        //Sort records by level keeping the severe messages at the top.
        h.setComparator(new LevelAndSeqComparator(true));

        //Use subject to provide a hint as to what is in the email.
        h.setSubject(new SummaryNameFormatter("Log containing {0} records with {1} errors"));

        //Make the body give a simple summary of what happened.
        h.setFormatter(new SummaryFormatter());

        //Create 3 attachments.
        h.setAttachmentFormatters(new Formatter[]{new XMLFormatter(), new XMLFormatter(), new SimpleFormatter()});

        //filter each attachment differently.
        h.setAttachmentFilters(new Filter[]{null, new MessageErrorsFilter(false),
                    new MessageErrorsFilter(true)});


        //create simple names.
        h.setAttachmentNames(new String[]{"all.xml", "errors.xml", "errors.txt"});

        //extract simple name, replace the rest with formatters.
        h.setAttachmentNames(new Formatter[]{h.getAttachmentNames()[0],
                    new SummaryNameFormatter("{0} records and {1} errors"),
                    new SummaryNameFormatter("{0,choice,0#no records|1#1 record|"
                    + "1<{0,number,integer} records} and "
                    + "{1,choice,0#no errors|1#1 error|1<"
                    + "{1,number,integer} errors}")});

        LOGGER.addHandler(h);
    }

    /**
     * Sets up the demos that will run.
     */
    private static void init() {
        Session session = Session.getInstance(System.getProperties());
        if (session.getDebug()) {
            checkConfig(CLASS_NAME, session.getDebugOut());
        }

        initBodyOnly();
        initLowCapacity();
        initSimpleAttachment();
        initWithPushLevel();
        initWithPushFilter();
        initCustomAttachments();
        initPushOnly();
        initPushNormal();
        applyFallbackSettings();
    }

    /**
     * Close and remove all handlers added to the class logger.
     */
    private static void closeHandlers() {
        Handler[] handlers = LOGGER.getHandlers();
        for (int i = 0; i < handlers.length; i++) {
            Handler h = handlers[i];
            h.close();
            LOGGER.removeHandler(h);
        }
    }

    /**
     * Apply some fallback settings if no configuration file was specified.
     */
    private static void applyFallbackSettings() {
        if (getConfigLocation() == null) {
            LOGGER.setLevel(Level.ALL);
            LOGGER.info("Check your user temp dir for output.");
            Handler[] handlers = LOGGER.getHandlers();
            for (int i = 0; i < handlers.length; i++) {
                Handler h = handlers[i];
                fallbackSettings(h);
            }
            fallbackSettings(pushOnlyHandler);
            fallbackSettings(pushNormalHandler);
        }
    }

    /**
     * Common fallback settings for a single handler.
     * @param h the handler.
     */
    private static void fallbackSettings(Handler h) {
        if (h != null) {
            h.setErrorManager(new FileErrorManager());
            h.setLevel(Level.ALL);
        }
    }

    /**
     * Gets the system temp directory.
     * @return the system temp directory.
     */
    private static String getTempDir() {
        return System.getProperty("java.io.tmpdir");
    }

    /**
     * Gets the configuration file or class name.
     * @return the file name or class name.
     */
    private static String getConfigLocation() {
        String file = System.getProperty("java.util.logging.config.file");
        if (file == null) {
            return System.getProperty("java.util.logging.config.class");
        }
        return file;
    }

    /**
     * A simple message filter example.
     */
    private static final class MessageErrorsFilter implements Filter {

        /**
         * Used to negate this filter.
         */
        private final boolean complement;

        /**
         * Default constructor.
         */
        MessageErrorsFilter() {
            this(true);
        }

        /**
         * Creates the message filter.
         * @param complement used to allow or deny message exceptions.
         */
        MessageErrorsFilter(final boolean complement) {
            this.complement = complement;
        }

        public boolean isLoggable(LogRecord r) {
            return r.getThrown() instanceof MessagingException == complement;
        }
    }

    /**
     * Orders log records by level then sequence number.
     */
    private static final class LevelAndSeqComparator
            implements Comparator<LogRecord>, java.io.Serializable {

        /**
         * Generated serial id.
         */
        private static final long serialVersionUID = 6269562326337300267L;

        /**
         * Used to reverse the ordering.
         */
        private final boolean reverse;

        /**
         * The default constructor.
         */
        LevelAndSeqComparator() {
            this(false);
        }

        /**
         * Creates the comparator.
         * @param reverse to specify reverse ordering.
         */
        LevelAndSeqComparator(final boolean reverse) {
            this.reverse = reverse;
        }

        /**
         * Compare by level.
         * @param r1 the first log record.
         * @param r2 the second log record.
         * @return -1 for less than, 0 for equal, or 1 for greater than.
         */
        public int compare(LogRecord r1, LogRecord r2) {
            final int first = r1.getLevel().intValue();
            final int second = r2.getLevel().intValue();
            if (first < second) {
                return reverse ? 1 : -1;
            } else if (first > second) {
                return reverse ? -1 : 1;
            } else {
                return compareSeq(r1, r2);
            }
        }

        /**
         * Compare by sequence.
         * @param r1 the first log record.
         * @param r2 the second log record.
         * @return -1 for less than, 0 for equal, or 1 for greater than.
         */
        private int compareSeq(LogRecord r1, LogRecord r2) {
            final long first = r1.getSequenceNumber();
            final long second = r2.getSequenceNumber();
            if (first < second) {
                return reverse ? 1 : -1;
            } else if (first > second) {
                return reverse ? -1 : 1;
            } else {
                return 0;
            }
        }
    }
}
