/*
 * Copyright (c) 2009, 2020 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2009, 2018 Jason Mehrens. All Rights Reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

import com.sun.mail.util.logging.CollectorFormatter;
import com.sun.mail.util.logging.DurationFilter;
import com.sun.mail.util.logging.MailHandler;
import com.sun.mail.util.logging.SeverityComparator;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.logging.*;
import jakarta.activation.DataHandler;
import jakarta.mail.MessagingException;
import jakarta.mail.Session;
import jakarta.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 user's temp directory.
 *
 * @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);

    /**
     * Runs the demo.
     *
     * @param args the command line arguments
     * @throws IOException if there is a problem.
     */
    public static void main(String[] args) throws IOException {
        List<String> l = Arrays.asList(args);
        if (l.contains("/?") || l.contains("-?") || l.contains("-help")) {
            LOGGER.info("Usage: java MailHandlerDemo "
                    + "[[-all] | [-body] | [-custom] | [-debug] | [-low] "
                    + "| [-simple] | [-pushlevel] | [-pushfilter] "
                    + "| [-pushnormal] | [-pushonly]] "
                    + "\n\n"
                    + "-all\t\t: Execute all demos.\n"
                    + "-body\t\t: An email with all records and only a body.\n"
                    + "-custom\t\t: An email with attachments and dynamic names.\n"
                    + "-debug\t\t: Output basic debug information about the JVM "
                    + "and log configuration.\n"
                    + "-low\t\t: Generates multiple emails due to low capacity."
                    + "\n"
                    + "-simple\t\t: An email with all records with body and "
                    + "an attachment.\n"
                    + "-pushlevel\t: Generates high priority emails when the"
                    + " push level is triggered and normal priority when "
                    + "flushed.\n"
                    + "-pushFilter\t: Generates high priority emails when the "
                    + "push level and the push filter is triggered and normal "
                    + "priority emails when flushed.\n"
                    + "-pushnormal\t: Generates multiple emails when the "
                    + "MemoryHandler push level is triggered.  All generated "
                    + "email are sent as normal priority.\n"
                    + "-pushonly\t: Generates multiple emails when the "
                    + "MemoryHandler push level is triggered.  Generates high "
                    + "priority emails when the push level is triggered and "
                    + "normal priority when flushed.\n");
        } else {
            final boolean debug = init(l); //may create log messages.
            try {
                LOGGER.log(Level.FINEST, "This is the finest part of the demo.",
                        new MessagingException("Fake Jakarta Mail issue."));
                LOGGER.log(Level.FINER, "This is the finer part of the demo.",
                        new NullPointerException("Fake bug."));
                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 file chooser issue."));
                LOGGER.log(Level.SEVERE, "The end of the demo.",
                        new IOException("Fake access denied issue."));
            } finally {
                closeHandlers();
            }

            //Force parse errors.  This does have side effects.
            if (debug && getConfigLocation() != null) {
                LogManager.getLogManager().readConfiguration();
            }
        }
    }

    /**
     * Used debug problems with the logging.properties. The system property
     * java.security.debug=access,stack can be used to trace access to the
     * LogManager reset.
     *
     * @param prefix a string to prefix the output.
     * @param err any PrintStream or null for System.out.
     */
    @SuppressWarnings("UseOfSystemOutOrSystemErr")
    private static void checkConfig(String prefix, PrintStream err) {
        if (prefix == null || prefix.trim().length() == 0) {
            prefix = "DEBUG";
        }

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

        try {
            err.println(prefix + ": java.version="
                    + System.getProperty("java.version"));
            err.println(prefix + ": LOGGER=" + LOGGER.getLevel());
            err.println(prefix + ": JVM id "
                    + ManagementFactory.getRuntimeMXBean().getName());
            err.println(prefix + ": java.security.debug="
                    + System.getProperty("java.security.debug"));
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                err.println(prefix + ": SecurityManager.class="
                        + sm.getClass().getName());
                err.println(prefix + ": SecurityManager classLoader="
                        + toString(sm.getClass().getClassLoader()));
                err.println(prefix + ": SecurityManager.toString=" + sm);
            } else {
                err.println(prefix + ": SecurityManager.class=null");
                err.println(prefix + ": SecurityManager.toString=null");
                err.println(prefix + ": SecurityManager classLoader=null");
            }

            String policy = System.getProperty("java.security.policy");
            if (policy != null) {
                File f = new File(policy);
                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()));
            }

            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()));
            } else {
                err.println(prefix + ": " + key
                        + " is not set as a system property.");
            }
            err.println(prefix + ": LogManager.class="
                    + manager.getClass().getName());
            err.println(prefix + ": LogManager classLoader="
                    + toString(manager.getClass().getClassLoader()));
            err.println(prefix + ": LogManager.toString=" + manager);
            err.println(prefix + ": MailHandler classLoader="
                    + toString(MailHandler.class.getClassLoader()));
            err.println(prefix + ": Context ClassLoader="
                    + toString(Thread.currentThread().getContextClassLoader()));
            err.println(prefix + ": Session ClassLoader="
                    + toString(Session.class.getClassLoader()));
            err.println(prefix + ": DataHandler ClassLoader="
                    + toString(DataHandler.class.getClassLoader()));

            final String p = MailHandler.class.getName();
            key = p.concat(".mail.to");
            String to = manager.getProperty(key);
            err.println(prefix + ": TO=" + to);
            if (to != null) {
                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)));
            }

            synchronized (manager) {
                final Enumeration<String> e = manager.getLoggerNames();
                while (e.hasMoreElements()) {
                    final Logger l = manager.getLogger(e.nextElement());
                    if (l != null) {
                        final Handler[] handlers = l.getHandlers();
                        if (handlers.length > 0) {
                            err.println(prefix + ": " + l.getClass().getName()
                                    + ", " + l.getName());
                            for (Handler h : handlers) {
                                err.println(prefix + ":\t" + toString(prefix, err, h));
                            }
                        }
                    }
                }
            }
        } catch (Throwable error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }
        err.flush();
    }

    /**
     * Gets the class loader list.
     *
     * @param cl the class loader or null.
     * @return the class loader list.
     */
    private static String toString(ClassLoader cl) {
        StringBuilder buf = new StringBuilder();
        buf.append(cl);
        while (cl != null) {
            cl = cl.getParent();
            buf.append("<-").append(cl);
        }
        return buf.toString();
    }

    /**
     * Gets a formatting string describing the given handler.
     *
     * @param prefix the output prefix.
     * @param err the error stream.
     * @param h the handler.
     * @return the formatted string.
     */
    private static String toString(String prefix, PrintStream err, Handler h) {
        StringBuilder buf = new StringBuilder();
        buf.append(h.getClass().getName());
        try {
            if (h instanceof MailHandler) {
                MailHandler mh = (MailHandler) h;
                buf.append(", ").append(mh.getSubject());
            }
        } catch (SecurityException error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }

        try {
            buf.append(", ").append(h.getFormatter());
        } catch (SecurityException error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }

        try {
            if (h instanceof MailHandler) {
                MailHandler mh = (MailHandler) h;
                buf.append(", ").append(Arrays.toString(
                        mh.getAttachmentFormatters()));
            }
        } catch (SecurityException error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }

        try {
            buf.append(", ").append(h.getLevel());
        } catch (SecurityException error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }

        try {
            buf.append(", ").append(h.getFilter());
        } catch (SecurityException error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }

        try {
            buf.append(", ").append(h.getErrorManager());
        } catch (SecurityException error) {
            err.print(prefix + ": ");
            error.printStackTrace(err);
        }

        buf.append(", ").append(toString(h.getClass().getClassLoader()));
        return buf.toString();
    }

    /**
     * Example for body only messages. On close the remaining messages are sent.      <code>
     * ##logging.properties
     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
     * com.sun.mail.util.logging.MailHandler.subject=Body only demo
     * ##
     * </code>
     */
    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.      <code>
     * ##logging.properties
     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
     * com.sun.mail.util.logging.MailHandler.subject=Low capacity demo
     * com.sun.mail.util.logging.MailHandler.capacity=5
     * ##
     * </code>
     */
    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.      <code>
     * ##logging.properties
     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
     * com.sun.mail.util.logging.MailHandler.subject=Body and attachment demo
     * com.sun.mail.util.logging.MailHandler.attachment.formatters=java.util.logging.XMLFormatter
     * com.sun.mail.util.logging.MailHandler.attachment.names=data.xml
     * ##
     * </code>
     */
    private static void initSimpleAttachment() {
        MailHandler h = new MailHandler();
        h.setSubject("Body and attachment demo");
        h.setAttachmentFormatters(new XMLFormatter());
        h.setAttachmentNames("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.      <code>
     * ##logging.properties
     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
     * com.sun.mail.util.logging.MailHandler.subject=Push level demo
     * com.sun.mail.util.logging.MailHandler.pushLevel=WARNING
     * ##
     * </code>
     */
    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 generation rate. If the push filter is
     * triggered the message is high priority. Otherwise, on close any remaining
     * messages are sent. If the capacity is set to the      <code>
     * ##logging.properties
     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
     * com.sun.mail.util.logging.MailHandler.subject=Push filter demo
     * com.sun.mail.util.logging.MailHandler.pushLevel=ALL
     * com.sun.mail.util.logging.MailHandler.pushFilter=com.sun.mail.util.logging.DurationFilter
     * com.sun.mail.util.logging.DurationFilter.records=2
     * com.sun.mail.util.logging.DurationFilter.duration=1 * 60 * 1000
     * ##
     * </code>
     */
    private static void initWithPushFilter() {
        MailHandler h = new MailHandler();
        h.setSubject("Push filter demo");
        h.setPushLevel(Level.ALL);
        h.setPushFilter(new DurationFilter(2, 1L * 60L * 1000L));
        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.      <code>
     * ##logging.properties
     * MailHandlerDemo.handlers=java.util.logging.MemoryHandler
     * java.util.logging.MemoryHandler.target=com.sun.mail.util.logging.MailHandler
     * com.sun.mail.util.logging.MailHandler.level=ALL
     * java.util.logging.MemoryHandler.level=ALL
     * java.util.logging.MemoryHandler.push=WARNING
     * com.sun.mail.util.logging.MailHandler.subject=Push only demo
     * com.sun.mail.util.logging.MailHandler.pushLevel=WARNING
     * ##
     * </code>
     */
    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. Use the LogManager config option or extend the MemoryHandler to
     * emulate this behavior via the logging.properties.
     */
    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. The subject will contain the most
     * severe record and a count of remaining records. The log records are
     * ordered from most severe to least severe. The body uses a custom
     * formatter that includes a summary by date and time. The attachment use
     * XML and plain text formats. Each attachment has a different set of
     * filtering. The attachment names are generated from either a fixed name or
     * are built using the number and type of the records formatted. On close
     * any remaining messages are sent. Use the LogManager config option or
     * extend the MemoryHandler to emulate this behavior via the
     * logging.properties.
     */
    private static void initCustomAttachments() {
        MailHandler h = new MailHandler();

        //Sort records by severity keeping the severe messages at the top.
        h.setComparator(Collections.reverseOrder(new SeverityComparator()));

        //Use subject to provide a hint as to what is in the email.
        h.setSubject(new CollectorFormatter());

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

        //Create 3 attachments.
        h.setAttachmentFormatters(new XMLFormatter(),
                new XMLFormatter(), new SimpleFormatter());

        //Filter each attachment differently.
        h.setAttachmentFilters(null,
                new DurationFilter(3L, 1000L),
                new DurationFilter(1L, 15L * 60L * 1000L));

        //Creating the attachment name formatters.
        h.setAttachmentNames(new CollectorFormatter("all.xml"),
                new CollectorFormatter("{3} records and {5} errors.xml"),
                new CollectorFormatter("{5,choice,0#no errors|1#1 error|1<"
                        + "{5,number,integer} errors}.txt"));

        LOGGER.addHandler(h);
    }

    /**
     * Sets up the demos that will run.
     *
     * @param l the list of arguments.
     * @return true if debug is on.
     */
    private static boolean init(List<String> l) {
        l = new ArrayList<String>(l);
        Session session = Session.getInstance(System.getProperties());
        boolean all = l.remove("-all") || l.isEmpty();
        if (l.remove("-body") || all) {
            initBodyOnly();
        }

        if (l.remove("-custom") || all) {
            initCustomAttachments();
        }

        if (l.remove("-low") || all) {
            initLowCapacity();
        }

        if (l.remove("-pushfilter") || all) {
            initWithPushFilter();
        }

        if (l.remove("-pushlevel") || all) {
            initWithPushLevel();
        }

        if (l.remove("-pushnormal") || all) {
            initPushNormal();
        }

        if (l.remove("-pushonly") || all) {
            initPushOnly();
        }

        if (l.remove("-simple") || all) {
            initSimpleAttachment();
        }

        boolean fallback = applyFallbackSettings();
        boolean debug = l.remove("-debug") || session.getDebug();
        if (debug) {
            checkConfig(CLASS_NAME, session.getDebugOut());
        }

        if (!l.isEmpty()) {
            LOGGER.log(Level.SEVERE, "Unknown commands: {0}", l);
        }

        if (fallback) {
            LOGGER.info("Check your user temp dir for output.");
        }
        return debug;
    }

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

    /**
     * Apply some fallback settings if no configuration file was specified.
     *
     * @return true if fallback settings were applied.
     */
    private static boolean applyFallbackSettings() {
        if (getConfigLocation() == null) {
            LOGGER.setLevel(Level.ALL);
            Handler[] handlers = LOGGER.getHandlers();
            for (Handler h : handlers) {
                fallbackSettings(h);
            }
            fallbackSettings(pushOnlyHandler);
            fallbackSettings(pushNormalHandler);
            return true;
        }
        return false;
    }

    /**
     * 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;
    }

    /**
     * No objects are allowed.
     * @throws IllegalAccessException always.
     */
    private MailHandlerDemo() throws IllegalAccessException {
        throw new IllegalAccessException();
    }
}
