package com.srbenoit.log;

import java.util.ResourceBundle;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A class with a static method to create a named <code>Logger</code> that has a special handler
 * installed to allow retention of and access to log records.
 */
public final class LogMgr {

    /** a log to which to write diagnostic messages */
    private static final Logger LOG;

    static {

        LogHandler handler;
        ResourceBundle res;

        LOG = Logger.getLogger("com.srbenoit");

        // This named logger may have already been created (and the handler
        // added), so check for an existing BekenHandler.
        if (findHandler(LOG) == null) {
            res = LogRes.getInstance(LOG);
            LOG.setLevel(Level.ALL);
            LOG.setUseParentHandlers(false);
            fixConsole(LOG);
            handler = new LogHandler(res);
            handler.setLevel(Level.ALL);
            LOG.addHandler(handler);
        }
    }

    /**
     * Private constructor to prevent instantiation.
     */
    private LogMgr() {

        // no action
    }

    /**
     * Get a <code>Logger</code> that has a handler that retains log entries installed.
     *
     * @return  the logger
     */
    public static Logger getLogger() {

        return LOG;
    }

    /**
     * Configures a logger based on logging properties. This may start file logging if so configured
     * and not already configured.
     *
     * @param  logger  the logger to be configured
     * @param  props   the properties that hold logging configuration
     */
    public static void configure(final Logger logger, final LogProperties props) {

        LogHandler handler;

        logger.setLevel(props.getLogLevel());

        handler = findHandler(logger);

        if (handler != null) {
            handler.configure(props, logger);
        }
    }

    /**
     * Given a <code>Logger</code>, scan its list of installed loggers for a <code>
     * LogHandler</code>, and return that handler if found.
     *
     * @param   logger  the <code>Logger</code> to search
     * @return  the <code>LogHandler</code> if found, <code>null</code> if not
     */
    public static LogHandler findHandler(final Logger logger) {

        Handler[] list;
        LogHandler handler = null;

        list = logger.getHandlers();

        for (int i = 0; i < list.length; i++) {

            if (list[i] instanceof LogHandler) {
                handler = (LogHandler) list[i];

                break;
            }
        }

        return handler;
    }

    /**
     * Given a <code>Logger</code>, scans its list of installed loggers for a <code>
     * ConsoleHandler</code>, and changes its output stream from <code>System.err</code> to <code>
     * System.out</code>.
     *
     * @param  logger  the <code>Logger</code> to search
     */
    private static void fixConsole(final Logger logger) {

        SysOutConsoleHandler handler;
        Handler[] list;
        boolean hit = false;

        list = logger.getHandlers();

        for (int i = 0; i < list.length; i++) {

            if (list[i] instanceof ConsoleHandler) {
                logger.removeHandler(list[i]);
            } else if (list[i] instanceof SysOutConsoleHandler) {
                hit = true;

                break;
            }
        }

        if (!hit) {
            handler = new SysOutConsoleHandler();
            handler.setLevel(Level.ALL);
            handler.setFormatter(new LogFormatter());
            logger.addHandler(handler);
        }
    }

    /**
     * Given a <code>Logger</code>, scans its list of installed handlers for <code>
     * ConsoleHandler</code> or <code>SysOutConsoleHandler</code> and removes any such handlers it
     * finds.
     *
     * <p>This is intended for use in headless environments where attempting to log to a console can
     * cause output buffers for consoles to fill, locking up the program.
     *
     * @param  logger  the <code>Logger</code> to check
     */
    public static void haltConsoleOutput(final Logger logger) {

        Handler[] list;

        list = logger.getHandlers();

        for (int i = 0; i < list.length; i++) {

            if (list[i] instanceof ConsoleHandler) {
                logger.removeHandler(list[i]);
            } else if (list[i] instanceof SysOutConsoleHandler) {
                logger.removeHandler(list[i]);
            }
        }
    }

    /**
     * Main method that creates a log and logs several messages to test and demonstrate logging.
     *
     * @param  args  command-line arguments
     */
    public static void main(final String... args) {

        String classname;
        String methodname;
        String[] list;
        Exception cause;
        Exception exc;

        classname = "LogMgr";
        methodname = "main";
        list = new String[] { "item1", "item2", "item3" };
        cause = new IllegalArgumentException("Cause exception");
        exc = new SecurityException("Some exception", cause);

        LOG.config("Config message");
        LOG.entering(classname, methodname);
        LOG.entering(classname, methodname, list[0]);
        LOG.entering(classname, methodname, list);
        LOG.exiting(classname, methodname);
        LOG.exiting(classname, methodname, list[0]);
        LOG.fine("Fine message");
        LOG.finer("Finer message");
        LOG.finest("Finest message");
        LOG.info("Info message");
        LOG.severe("Severe message");
        LOG.throwing(classname, methodname, exc);
        LOG.warning("Warning message");
        LOG.log(Level.CONFIG, "Log message 1");
        LOG.log(Level.INFO, "Log message 2", list[0]);
        LOG.log(Level.WARNING, "Log message 3", list);
        LOG.log(Level.SEVERE, "Log message 4", exc);
    }
}
