/*
 * Copyright (c) 2013 Ramon Servadei, Paul Mackinlay 
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *    
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fimtra.infra.util;

import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A simple logger that writes to a file and to <code>System.err</code>. Uses a
 * {@link RollingFileAppender} to log to a file in the logs directory called
 * message_yyyyMMddHHmmss.log.
 * 
 * @see UtilProperties
 * @author Ramon Servadei
 * @author Paul Mackinlay
 */
public abstract class Log
{
    /** Controls logging to std.err, default is <code>false</code> for performance reasons */
    private static final boolean LOG_TO_STDERR = UtilProperties.Values.LOG_TO_STDERR;
    private static final String TAB = "    ";
    private static final FastDateFormat fastDateFormat = new FastDateFormat();
    private static final ExecutorService FILE_APPENDER_EXECUTOR =
        ThreadUtils.newSingleThreadExecutorService("LogAsyncFileAppender");
    private static final Lock lock = new ReentrantLock();
    private static PrintStream consoleStream = System.err;

    static final Queue<String> LOG_MESSAGE_QUEUE = new ConcurrentLinkedQueue<String>();
    static final CharSequence LINE_SEPARATOR = SystemUtils.lineSeparator();
    static final RollingFileAppender FILE_APPENDER;

    static
    {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
        {
            @SuppressWarnings("synthetic-access")
            @Override
            public void run()
            {
                System.err.println("Shutting down logging...");
                FILE_APPENDER_EXECUTOR.shutdown();
                try
                {
                    FILE_APPENDER_EXECUTOR.awaitTermination(5, TimeUnit.SECONDS);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                flushMessages();
                try
                {
                    FILE_APPENDER.flush();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
                try
                {
                    FILE_APPENDER.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }));
        lock.lock();
        try
        {
            FILE_APPENDER =
                RollingFileAppender.createStandardRollingFileAppender("messages", UtilProperties.Values.LOG_DIR);
            System.out.println("Log file " + FILE_APPENDER);
        }
        finally
        {
            lock.unlock();
        }
    }

    private Log()
    {
        super();
    }

    /**
     * Set the PrintStream to use for console messages
     * 
     * @param stream
     *            the console stream to use
     */
    public static void setConsoleStream(PrintStream stream)
    {
        lock.lock();
        try
        {
            consoleStream = stream;
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void log(Object source, String... messages)
    {
        lock.lock();
        try
        {
            println(getLogMessage(source, messages));
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void log(Object source, String message)
    {
        lock.lock();
        try
        {
            println(getLogMessage(source, message));
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void log(Object source, String message, Throwable t)
    {
        lock.lock();
        try
        {
            StringWriter stringWriter = new StringWriter(1024);
            PrintWriter pw = new PrintWriter(stringWriter);
            pw.println(getLogMessage(source, message));
            t.printStackTrace(pw);
            print(stringWriter.toString());
        }
        finally
        {
            lock.unlock();
        }
    }

    private static String getLogMessage(Object source, CharSequence... messages)
    {
        int len = 256;
        for (int i = 0; i < messages.length; i++)
        {
            len += messages[i] == null ? 4 : messages[i].length();
        }
        StringBuilder sb = new StringBuilder(len);
        sb.append(fastDateFormat.yyyyMMddHHmmssSSS(System.currentTimeMillis())).append(TAB).append(
            Thread.currentThread().getName()).append(TAB).append(
            source instanceof Class ? ((Class<?>) source).getName() : source.getClass().getName()).append(":").append(
            System.identityHashCode(source)).append(TAB);
        for (int i = 0; i < messages.length; i++)
        {
            sb.append(messages[i]);
        }
        return sb.toString();
    }

    private static String getLogMessage(Object source, String message)
    {
        StringBuilder sb = new StringBuilder(256);
        sb.append(fastDateFormat.yyyyMMddHHmmssSSS(System.currentTimeMillis())).append(TAB).append(
            Thread.currentThread().getName()).append(TAB).append(
            source instanceof Class ? ((Class<?>) source).getName() : source.getClass().getName()).append(":").append(
            System.identityHashCode(source)).append(TAB).append(message);
        return sb.toString();
    }

    private static void println(final String logMessage)
    {
        final StringBuilder sb = new StringBuilder(logMessage.length() + LINE_SEPARATOR.length());
        sb.append(logMessage).append(LINE_SEPARATOR);
        print(sb.toString());
    }

    private static void print(final String logMessageWithLineSeparator)
    {
        LOG_MESSAGE_QUEUE.add(logMessageWithLineSeparator);
        try
        {
            FILE_APPENDER_EXECUTOR.execute(new Runnable()
            {
                @Override
                public void run()
                {
                    flushMessages();
                }
            });
        }
        catch (RejectedExecutionException e)
        {
            // REALLY don't care about these!
        }
        if (LOG_TO_STDERR)
        {
            consoleStream.print(logMessageWithLineSeparator);
        }
    }

    /**
     * Create a banner message
     */
    public static void banner(Object source, String message)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < message.length(); i++)
        {
            sb.append("=");
        }
        String sbString = sb.toString();
        Log.log(source, SystemUtils.lineSeparator(), sbString, SystemUtils.lineSeparator(), message,
            SystemUtils.lineSeparator(), sbString);
    }

    static void flushMessages()
    {
        final int size = LOG_MESSAGE_QUEUE.size();
        if (size > 0)
        {
            for (int i = 0; i < size; i++)
            {
                try
                {
                    FILE_APPENDER.append(LOG_MESSAGE_QUEUE.poll());
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
            try
            {
                FILE_APPENDER.flush();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
}

@@ -1588,20 +1588,17 @@
  TAB = %22
-    
+%7C
 %22;%0A    p

Replace tab with "|" in log messages