/*
 * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
 * Distributed under the terms of either:
 * - the common development and distribution license (CDDL), v1.0; or
 * - the GNU Lesser General Public License, v2.1 or later
 */
package winstone;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

/**
 * A utility class for logging event and status messages. It maintains a
 * collection of streams for different types of messages, but any messages with
 * unknown or unspecified stream go to the default stream.
 * 
 * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
 * @version $Id: Logger.java,v 1.8 2006/11/09 06:01:43 rickknowles Exp $
 */
public class Logger {
    
    public final static String DEFAULT_STREAM = "Winstone";
    public static Level MIN = Level.OFF;
    public static Level ERROR = Level.SEVERE;
    public static Level WARNING = Level.WARNING;
    public static Level INFO = Level.INFO;
    public static Level SPEED = Level.FINE;
    public static Level DEBUG = Level.FINER;
    public static Level FULL_DEBUG = Level.FINEST;
    public static Level MAX = Level.ALL;

    protected static Boolean semaphore = true;
    protected static boolean initialised = false;
    protected static Writer defaultStream;
    protected static Map namedStreams;
//    protected static Collection nullStreams;
    protected final static DateFormat sdfLog = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    protected static boolean showThrowingThread;

    /**
     * Initialises default streams
     */
    public static void init(Level level) {
        init(level, System.out, false);
    }

    public static void init(int level) {
        init(Level.parse(String.valueOf(level)));
    }
    
    /**
     * Initialises default streams
     */
    public static void init(Level level, OutputStream defaultStream, 
            boolean showThrowingThreadArg) {
        synchronized (semaphore) {
            if (!initialised) { // recheck in case we were blocking on another init
                initialised = false;
                LOGGER.setLevel(level);
                namedStreams = new HashMap();
//                nullStreams = new ArrayList();
                initialised = true;
                setStream(DEFAULT_STREAM, defaultStream);
                showThrowingThread = showThrowingThreadArg;
            }
        }
    }

    /**
     * Allocates a stream for redirection to a file etc
     */
    public static void setStream(String name, OutputStream stream) {
        setStream(name, stream != null ? new OutputStreamWriter(stream) : null);
    }

    /**
     * Allocates a stream for redirection to a file etc
     */
    public static void setStream(String name, Writer stream) {
        if (name == null) {
            name = DEFAULT_STREAM;
        }
        if (!initialised) {
            init(INFO);
        }
        synchronized (semaphore) {
            if (name.equals(DEFAULT_STREAM)) {
                defaultStream = stream;
            } else if (stream == null) {
                namedStreams.remove(name);
            } else {
                namedStreams.put(name, stream);
            }
        }
    }

    /**
     * Forces a flush of the contents to file, display, etc
     */
    public static void flush(String name) {
        if (!initialised) {
            init(INFO);
        }

        Writer stream = getStreamByName(name);
        if (stream != null) {
            try {stream.flush();} catch (IOException err) {}
        }
    }
    
    private static Writer getStreamByName(String streamName) {
        if ((streamName != null) && streamName.equals(DEFAULT_STREAM)) {
            // As long as the stream has not been nulled, assign the default if not found
            synchronized (semaphore) {
                Writer stream = (Writer) namedStreams.get(streamName);
                if ((stream == null) && !namedStreams.containsKey(streamName)) {
                    stream = defaultStream;
                }
                return stream;
            }
        } else {
            return defaultStream;
        }
        
    }

    public static void setCurrentDebugLevel(int level) {
        if (!initialised) {
            init(level);
        } else synchronized (semaphore) {
            LOGGER.setLevel(Level.parse(String.valueOf(level)));
        }
    }

    /**
     * Writes a log message to the requested stream, and immediately flushes
     * the contents of the stream.
     */
    private static void logInternal(Level level, String message, Throwable error) {
        if (!initialised) {
            init(INFO);
        }
        
        String msg = "";
        if (showThrowingThread) {
            msg = "["+Thread.currentThread().getName()+"] - ";
        }
        msg += message;
        
        LOGGER.log(level,msg,error);
    }

    public static void log(Level level, WinstoneResourceBundle resources,
            String messageKey) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, resources.getString(messageKey), null);
        }
    }

    public static void log(Level level, WinstoneResourceBundle resources,
            String messageKey, Throwable error) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, resources.getString(messageKey), error);
        }
    }

    public static void log(Level level, WinstoneResourceBundle resources,
            String messageKey, Object param) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, resources.getString(messageKey, param), null);
        }
    }

    public static void log(Level level, WinstoneResourceBundle resources,
            String messageKey, Object... params) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, resources.getString(messageKey, params), null);
        }
    }

    public static void log(Level level, WinstoneResourceBundle resources,
            String messageKey, Object param, Throwable error) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, resources.getString(messageKey, param), error);
        }
    }

    public static void log(Level level, WinstoneResourceBundle resources,
            String messageKey, Object params[], Throwable error) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, resources.getString(messageKey, params), error);
        }
    }

    public static void log(Level level, WinstoneResourceBundle resources,
            String streamName, String messageKey, Object params[], Throwable error) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, resources.getString(messageKey, params), error);
        }
    }

    public static void logDirectMessage(Level level, String streamName, String message, 
            Throwable error) {
        if (!LOGGER.isLoggable(level)) {
            return;
        } else {
            logInternal(level, message, error);
        }
    }

    private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger("winstone");
}
