/*
 * @(#)Logger.java
 *
 * Copyright (c) 2002-2003 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */
package com.jscape.util;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Simple logger class. Provides controllable logging services.
 * All logger methods are thread-safe.
 *
 * @author Alex Usun
 */
@SuppressWarnings("unchecked")
public class Logger {

    private final Handler handler;
    private Level level;
    private boolean showLevel = true;
    private boolean showTimestamp = true;

    //--------------------------------------------------------------------------

    /**
     * Creates a new logger instance for specified level and stream.
     *
     * @param level   desired logger level
     * @param printer logger output stream
     */
    public Logger(Level level, PrintStream printer) {
        Assert.notNull(level);
        Assert.notNull(printer);
        this.level = level;
        this.handler = Handler.getInstanceFor(printer);
    }

    //--------------------------------------------------------------------------

    /**
     * @param printer
     */
    public Logger(PrintStream printer) {
        this(Level.OFF, printer);
    }

    //--------------------------------------------------------------------------

    /**
     * Closes the logger.
     */
    public synchronized void close() {
        this.handler.close();
    }

    //--------------------------------------------------------------------------

    /**
     * @return is closed
     */
    public synchronized boolean isClosed() {
        return this.handler.isClosed();
    }

    //--------------------------------------------------------------------------

    /**
     * Obtains the current logger level.
     *
     * @return the current logger level
     */
    public synchronized Level getLevel() {
        return this.level;
    }

    //--------------------------------------------------------------------------

    /**
     * Current logger level setup.
     *
     * @param level desired logger level
     */
    public synchronized void setLevel(Level level) {
        Assert.notNull(level);
        this.level = level;
    }

    //--------------------------------------------------------------------------

    /**
     * Writes specified message to the logger stream. The result of
     * this operation depends on current logger level - if it is in
     * <code>Level.OFF</code> message won't be written.
     *
     * @param message message to log
     */
    public synchronized void log(Level level, String message) {
        Assert.notNull(level);
        if (isClosed()) {
            return;
        }

        if (level.equals(Level.OFF)) {
            return;
        }
        if (this.level.compareTo(level) > 0) {
            return;
        }

        String msg = "";

        // append timestamp
        if (showTimestamp) {
            Date date = new Date(System.currentTimeMillis());
            DateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
            msg += format.format(date) + " ";
        }

        // append level
        if (showLevel) {
            msg = msg + level + " ";
        }


        // append message
        msg = msg + message;

        this.handler.print(msg);
    }

    //--------------------------------------------------------------------------

    /**
     * @param message
     */
    public synchronized void fine(String message) {
        log(Level.FINE, message);
    }

    //--------------------------------------------------------------------------

    /**
     * @param message
     */
    public synchronized void info(String message) {
        log(Level.INFO, message);
    }

    //--------------------------------------------------------------------------

    /**
     * @param message
     */
    public synchronized void error(String message) {
        log(Level.ERROR, message);
    }

    public synchronized void error(String message, Throwable error) {
        StringWriter writer = new StringWriter();
        error.printStackTrace(new PrintWriter(writer));
        String logMessage = message != null ? message : error.getMessage() != null ? error.getMessage() : "";
        error(logMessage + " " + writer.toString());
    }

    //--------------------------------------------------------------------------

    /**
     * @param e
     */
    public synchronized void error(Throwable e) {
        error(e.getMessage(), e);
    }

    //--------------------------------------------------------------------------

    /**
     * @return print stream
     */
    public PrintStream getStream() {
        return this.handler.getStream();
    }

    //--------------------------------------------------------------------------

    /**
     * Obtains current logger string representation.
     *
     * @return current logger string representation
     */
    public synchronized String toString() {
        return "Logger is at " + this.level + " level";
    }

    /**
     * Logger level.
     */
    public static final class Level
            implements Comparable {

        /**
         *
         */
        public static final Level FINE = new Level("FINE", 1);

        /**
         *
         */
        public static final Level INFO = new Level("INFO", 2);

        /**
         *
         */
        public static final Level ERROR = new Level("ERROR", 3);

        /**
         * Logger level "off". On this level all messages
         * won't be written to the stream.
         */
        public static final Level OFF = new Level("OFF", 4);

        private String name;
        private int value;

        //----------------------------------------------------------------------

        /**
         * Creates a new level instance.
         *
         * @param name string level name.
         */
        protected Level(String name, int value) {
            Assert.notNull(name);

            this.name = name;
            this.value = value;
        }

        //----------------------------------------------------------------------

        /**
         * @return name
         */
        public String getName() {
            return name;
        }

        //----------------------------------------------------------------------

        /**
         * @return integer value
         */
        public int intValue() {
            return this.value;
        }

        //----------------------------------------------------------------------

        /**
         * @return hash code
         */
        public int hashCode() {
            return this.value;
        }

        //----------------------------------------------------------------------

        /**
         * Obtains string level representation.
         *
         * @return string level representation
         */
        public String toString() {
            return this.name;
        }

        //----------------------------------------------------------------------

        /**
         * @param o
         *
         * @return compareTo(Object o)
         */
        public int compareTo(Object o) {
            Level that = (Level) o;

            if (this.value > that.value) {
                return 1;
            }
            if (this.value < that.value) {
                return -1;
            }

            return 0;
        }

    }   // Level

    /**
     *
     */
    private static class Handler {

        /**
         * Special case: System.out print stream.
         */
        private static final Handler SYSTEM_OUT = new Handler(System.out);

        private static final Map HANDLERS = new HashMap();

        private final PrintStream stream;
        private int refCount;

        //----------------------------------------------------------------------

        /**
         * @param printer
         *
         * @return Handler
         */
        public static synchronized Handler getInstanceFor(PrintStream printer) {
            Assert.notNull(printer);

            if (printer == System.out) {
                return SYSTEM_OUT;
            }

            if (HANDLERS.containsKey(printer)) {
                Handler handler = (Handler) HANDLERS.get(printer);
                handler.addRef();

                return handler;
            }

            Handler handler = new Handler(printer);
            HANDLERS.put(printer, handler);

            return handler;
        }

        //----------------------------------------------------------------------

        /**
         * @param printer
         */
        private Handler(PrintStream printer) {
            this.stream = printer;
            this.refCount = 1;
        }

        //----------------------------------------------------------------------

        /**
         *
         */
        private synchronized void addRef() {
            ++this.refCount;
        }

        //----------------------------------------------------------------------

        /**
         *
         */
        private synchronized void releaseRef() {
            --this.refCount;
        }

        //----------------------------------------------------------------------

        /**
         * @param message
         */
        public synchronized void print(String message) {
            if (isClosed()) {
                return;
            }

            this.stream.println(message);
        }

        //----------------------------------------------------------------------

        /**
         *
         */
        public synchronized void close() {
            if (this == SYSTEM_OUT) {
                return;
            } // special case: System.out cannot be closed

            releaseRef();

            if (this.refCount == 0) {
                this.stream.close();
            }
        }

        //----------------------------------------------------------------------

        /**
         * @return is closed
         */
        public synchronized boolean isClosed() {
            return (this.refCount == 0);
        }

        //----------------------------------------------------------------------

        /**
         * @return print stream
         */
        public PrintStream getStream() {
            return this.stream;
        }

    }   // Handler

    /**
     * @return show level
     */
    public boolean isShowLevel() {
        return showLevel;
    }

    /**
     * @return show times-stamp
     */
    public boolean isShowTimestamp() {
        return showTimestamp;
    }

    /**
     * @param b
     */
    public void setShowLevel(boolean b) {
        showLevel = b;
    }

    /**
     * @param b
     */
    public void setShowTimestamp(boolean b) {
        showTimestamp = b;
    }

}
