/
codebase/src/net/sourceforge/pain/admin/console/command/
codebase/src/net/sourceforge/pain/data/role/
codebase/src/net/sourceforge/pain/network/console/telnet/
codebase/src/net/sourceforge/pain/network/guitool/
codebase/src/net/sourceforge/pain/plugin/
codebase/src/net/sourceforge/pain/util/
db/src/net/sourceforge/pain/util/
gui/
gui/lib/
gui/src/net/sourceforge/pain/tools/guitool/dbbrowse/
gui/src/net/sourceforge/pain/tools/guitool/dialog/
gui/src/net/sourceforge/pain/tools/guitool/menu/
gui/src/net/sourceforge/pain/tools/guitool/resources/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/
gui/src/net/sourceforge/pain/tools/guitool/resources/images/explorer/
mudlibs/tinylib/
mudlibs/tinylib/area/
mudlibs/tinylib/etc/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/affect/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/prototype/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/data/trigger/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/affect/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/deploy/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/guitool/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/event/guitool/event/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/fn/util/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/trigger/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/logic/trigger/impl/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/command/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/reset/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/shutdown/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/plugin/social/
mudlibs/tinylib/src/net/sourceforge/pain/tinylib/util/
tests/
tests/src/
tests/src/net/sourceforge/pain/db/data/
package net.sourceforge.pain.util;

import java.io.*;
import java.text.*;
import java.util.*;

/**
 * PAiN Mud Codebase logger
 * Note: this class could be changed to Log4j as soon as any reasons will be found
 * (I just do not want any additional jar files in distribution)
 */
public final class Log {

    private static long lastTime;
    private static final char[] lastTimeString = new char[23];
    private static final GregorianCalendar calendar = new GregorianCalendar();
    private static SimpleDateFormat fileFormat = new SimpleDateFormat("dd_MM_yyyy_HH'h'_mm'm'");

    public static int rollPeriod = 60 * 60 * 1000;
    public static long nextRollTime = -1;
    public static String dirPath = "";
    public static final String activeLogFileName = "pain.log";
    public static Writer writer;

    private static final int flushPeriod = 1 * 1000;
    private static long nextFlushTime = -1;
    private static Flusher flusher;
    private static ShutdownFlusher shutdownFlusher;

    private Log() {
    }

    public static synchronized void debug(final String message) {
        roll();
        log(" [DEBUG] ", message);
    }

    public static synchronized void info(final String message) {
        roll();
        log(" [INFO] ", message);
    }

    public static synchronized void warn(final String message) {
        roll();
        log(" [WARN] ", message);
    }

    public static synchronized void error(final String message) {
        roll();
        log(" [ERROR] ", message);
    }

    public static synchronized void error(final Throwable e) {
        _error("", e);
    }


    public static synchronized void error(final String message, final Throwable e) {
        _error(message, e);
    }

    private static void _error(final String message, final Throwable e) {
        roll();
        log(" [ERROR] ", message + " / " + e.getClass().getName() + " : " + e.getMessage());
        log("\r\n");
        final StackTraceElement[] stack = e.getStackTrace();
        final int catchDeep = stack.length - (new Throwable().getStackTrace().length - 2);
        for (int i = 0; i < stack.length; i++) {
            if (i == catchDeep) {
                log("[log]\t");
            } else {
                log("\t");
            }
            log(stack[i].toString());
            log("\r\n");
        }
        log(".\r\n");
    }

    private static void log(final String message) {
        try {
            out(message);
        } catch (Exception e) {
            handleException(message, e);
        }
    }

    private static void log(final char[] message) {
        try {
            out(message);
        } catch (Exception e) {
            handleException(new String(message), e);
        }
    }

    private static void handleException(final String message, final Exception e) {
        if (writer == null || writer instanceof FileWriter) {
            switchToSystemOut(e, message);
        }
    }


    private static void renameFile(final File file, final String name) {
        File toFile = new File(name);
        for (int i = 1; toFile.exists(); i++) {
            toFile = new File(name + i);
        }
        file.renameTo(toFile);
    }

    private static void log(final String prefix, final String message) {
        log(getTime());
        log(prefix);
        log(message);
        log("\r\n");
    }

    private static void switchToSystemOut(final Exception e, final String message) {
        writer = new OutputStreamWriter(System.out);
        error(e);
        info("Switching output to system out");
        log(message);
    }

    private static void flush() {
        if (nextFlushTime > System.currentTimeMillis()) {
            return;
        }
        synchronized (Log.class) {
            try {
                writer.flush();
            } catch (Exception e) {
                handleException(e.getMessage(), e);
            }
        }
        nextFlushTime = System.currentTimeMillis() + flushPeriod;
    }

    private static void out(String message) throws IOException {
        message = message == null ? "null" : message;
        writer.write(message, 0, message.length());
    }

    private static void out(final char[] message) throws IOException {
        writer.write(message, 0, message.length);
    }


    /** i just had nothing to do :) */
    private static char[] getTime() {
        // code from log4j package (ISO8601DateFormat with a minimal changes)
        final long now = System.currentTimeMillis();
        final int millis = (int) (now % 1000);
        if ((now - millis) != lastTime) {
            // We reach this point at most once per second
            // across all threads instead of each time format()
            // is called. This saves considerable CPU time.
            calendar.setTimeInMillis(now);
            final int year = calendar.get(Calendar.YEAR);
            lastTimeString[0] = (char) ('1' + (year / 1000) - 1);
            lastTimeString[1] = (char) ('1' + (year % 1000) / 100 - 1);
            lastTimeString[2] = (char) ('1' + (year % 100) / 10 - 1);
            lastTimeString[3] = (char) ('1' + (year % 10) - 1);
            lastTimeString[4] = '-';
            final char m1;
            final char m2;
            switch (calendar.get(Calendar.MONTH)) {
                case Calendar.JANUARY:
                    m1 = '0';
                    m2 = '1';
                    break;
                case Calendar.FEBRUARY:
                    m1 = '0';
                    m2 = '2';
                    break;
                case Calendar.MARCH:
                    m1 = '0';
                    m2 = '3';
                    break;
                case Calendar.APRIL:
                    m1 = '0';
                    m2 = '4';
                    break;
                case Calendar.MAY:
                    m1 = '0';
                    m2 = '5';
                    break;
                case Calendar.JUNE:
                    m1 = '0';
                    m2 = '6';
                    break;
                case Calendar.JULY:
                    m1 = '0';
                    m2 = '7';
                    break;
                case Calendar.AUGUST:
                    m1 = '0';
                    m2 = '8';
                    break;
                case Calendar.SEPTEMBER:
                    m1 = '0';
                    m2 = '9';
                    break;
                case Calendar.OCTOBER:
                    m1 = '1';
                    m2 = '0';
                    break;
                case Calendar.NOVEMBER:
                    m1 = '1';
                    m2 = '1';
                    break;
                case Calendar.DECEMBER:
                    m1 = '1';
                    m2 = '2';
                    break;
                default:
                    m1 = 'N';
                    m2 = 'A';
                    break;
            }
            lastTimeString[5] = m1;
            lastTimeString[6] = m2;
            lastTimeString[7] = '-';

            final int day = calendar.get(Calendar.DAY_OF_MONTH);
            lastTimeString[8] = (char) ('1' + day / 10 - 1);
            lastTimeString[9] = (char) ('1' + day % 10 - 1);
            lastTimeString[10] = ' ';

            final int hour = calendar.get(Calendar.HOUR_OF_DAY);
            lastTimeString[11] = (char) ('1' + hour / 10 - 1);
            lastTimeString[12] = (char) ('1' + hour % 10 - 1);
            lastTimeString[13] = ':';

            final int mins = calendar.get(Calendar.MINUTE);
            lastTimeString[14] = (char) ('1' + mins / 10 - 1);
            lastTimeString[15] = (char) ('1' + mins % 10 - 1);
            lastTimeString[16] = ':';

            final int secs = calendar.get(Calendar.SECOND);
            lastTimeString[17] = (char) ('1' + secs / 10 - 1);
            lastTimeString[18] = (char) ('1' + secs % 10 - 1);
            lastTimeString[19] = ',';
            lastTime = now - millis;
        }
        lastTimeString[20] = (char) ('1' + (millis / 100) - 1);
        lastTimeString[21] = (char) ('1' + (millis % 100) / 10 - 1);
        lastTimeString[22] = (char) ('1' + (millis % 10) - 1);
        return lastTimeString;
    }

    private static void roll() {
        if (nextRollTime > System.currentTimeMillis()) {
            return;
        }
        synchronized (Log.class) {
            if (flusher == null) {
                flusher = new Flusher();
                Thread worker = new Thread(flusher);
                worker.setDaemon(true);
                worker.start();
            }
            if (shutdownFlusher == null) {
                shutdownFlusher = new ShutdownFlusher();
            }

            if (nextRollTime > System.currentTimeMillis()) {
                return;//DCL
            }
            try {
                nextRollTime = System.currentTimeMillis() + rollPeriod;
                if (writer != null && !(writer instanceof FileWriter)) {
                    return;
                }
                if (writer != null) {
                    writer.close();
                }
                createNewLogFile();
            } catch (Exception e) {
                handleException("roll error", e);
            }
        }

    }

    private static void createNewLogFile() throws Exception {
        final File file = new File(dirPath + activeLogFileName);
        if (file.exists()) {
            // check first line for name:
            String line = null;
            final BufferedReader r = new BufferedReader(new FileReader(file));
            try {
                line = r.readLine();
            } finally {
                r.close();
            }
            if (line == null) {
                line = fileFormat.format(new Date(file.lastModified())) + "_before.log";
            }
            renameFile(file, dirPath + line);
        }
        writer = new FileWriter(file);
        writer.write(fileFormat.format(new Date())); //first line in file is its creation date, and future name during roll
        writer.write(".log");
        writer.write("\r\n");
    }


    private static final class ShutdownFlusher extends Thread {
        public ShutdownFlusher() {
            this.setDaemon(true);
            Runtime.getRuntime().addShutdownHook(this);
        }

        public void run() {
            try {
                if (writer != null) {
                    writer.flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }


        }
    }

    private static final class Flusher implements Runnable {
        public void run() {
            while (true) {
                synchronized (this) {
                    try {
                        this.wait(flushPeriod);
                        flush();
                    } catch (InterruptedException e) {
                        Log.error(e);
                    }
                }
            }
        }
    }

    public static boolean isDebugEnabled() {
        return true;
    }

    public static boolean isInfoEnabled() {
        return true;
    }

    public static boolean isWarnEnabled() {
        return true;
    }
}