/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * 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 net.java.sip.communicator.util;

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

/**
 * Print a brief summary of the LogRecord in a human readable. The summary will
 * typically be on a single line (unless it's too long :) ... what I meant to
 * say is that we don't add any line breaks).
 *
 * @author Emil Ivov
 */

public class ScLogFormatter
    extends java.util.logging.Formatter
{

    /**
     * Program name logging property name
     */
    private static final String PROGRAM_NAME_PROPERTY = ".programname";
    
    /**
     * Disable timestamp logging property name.
     */
    private static final String DISABLE_TIMESTAMP_PROPERTY
        = ".disableTimestamp";

    /**
     * Line separator used by current platform
     */
    private static String lineSeparator = System.getProperty("line.separator");
    
    /**
     * Two digit <tt>DecimalFormat</tt> instance, used to format datetime
     */
    private static DecimalFormat twoDigFmt = new DecimalFormat("00");

    /**
     * Three digit <tt>DecimalFormat</tt> instance, used to format datetime
     */
    private static DecimalFormat threeDigFmt = new DecimalFormat("000");

    /**
     * The application name used to generate this log
     */
    private static String programName;

    /**
     * Whether logger will add date to the logs, enabled by default.
     */
    private static boolean timestampDisabled = false;

    /**
     * The default constructor for <tt>ScLogFormatter</tt> which loads 
     * program name property from logging.properties file, if it exists
     */
    public ScLogFormatter()
    {
        loadConfigProperties();
    }
    
    /**
     * Format the given LogRecord.
     * @param record the log record to be formatted.
     * @return a formatted log record
     */
    @Override
    public synchronized String format(LogRecord record)
    {
        StringBuffer sb = new StringBuffer();
        
        
        if (programName != null)
        {
            // Program name
            sb.append(programName);

            sb.append(' ');
        }

        if(!timestampDisabled)
        {
            //current time
            Calendar cal = Calendar.getInstance();
            int year = cal.get(Calendar.YEAR);
            int month = cal.get(Calendar.MONTH) + 1;
            int day = cal.get(Calendar.DAY_OF_MONTH);
            int hour = cal.get(Calendar.HOUR_OF_DAY);
            int minutes = cal.get(Calendar.MINUTE);
            int seconds = cal.get(Calendar.SECOND);
            int millis = cal.get(Calendar.MILLISECOND);

            sb.append(year).append('-');
            sb.append(twoDigFmt.format(month)).append('-');
            sb.append(twoDigFmt.format(day)).append(' ');
            sb.append(twoDigFmt.format(hour)).append(':');
            sb.append(twoDigFmt.format(minutes)).append(':');
            sb.append(twoDigFmt.format(seconds)).append('.');
            sb.append(threeDigFmt.format(millis)).append(' ');
        }

        //log level
        sb.append(record.getLevel().getLocalizedName());
        sb.append(": ");

        // Thread ID
        sb.append("[" + record.getThreadID() + "] ");

        //caller method
        int lineNumber = inferCaller(record);
        String loggerName = record.getLoggerName();

        if(loggerName == null)
            loggerName = record.getSourceClassName();

        if(loggerName.startsWith("net.java.sip.communicator."))
        {
            sb.append(loggerName.substring("net.java.sip.communicator."
                                           .length()));
        }
        else
            sb.append(record.getLoggerName());

        if (record.getSourceMethodName() != null)
        {
            sb.append(".");
            sb.append(record.getSourceMethodName());

            //include the line number if we have it.
            if(lineNumber != -1)
                sb.append("().").append(Integer.toString(lineNumber));
            else
                sb.append("()");
        }
        sb.append(" ");
        sb.append(record.getMessage());
        sb.append(lineSeparator);
        if (record.getThrown() != null)
        {
            try
            {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                record.getThrown().printStackTrace(pw);
                pw.close();
                sb.append(sw.toString());
            }
            catch (Exception ex)
            {
            }
        }
        return sb.toString();
    }

    /**
     * Try to extract the name of the class and method that called the current
     * log statement.
     *
     * @param record the logrecord where class and method name should be stored.
     *
     * @return the line number that the call was made from in the caller.
     */
    private int inferCaller(LogRecord record)
    {
        // Get the stack trace.
        StackTraceElement stack[] = (new Throwable()).getStackTrace();

        //the line number that the caller made the call from
        int lineNumber = -1;

        // First, search back to a method in the SIP Communicator Logger class.
        int ix = 0;
        while (ix < stack.length)
        {
            StackTraceElement frame = stack[ix];
            String cname = frame.getClassName();
            if (cname.equals("net.java.sip.communicator.util.Logger"))
            {
                break;
            }
            ix++;
        }
        // Now search for the first frame 
        // before the SIP Communicator Logger class.
        while (ix < stack.length)
        {
            StackTraceElement frame = stack[ix];
            lineNumber=stack[ix].getLineNumber();
            String cname = frame.getClassName();
            if (!cname.equals("net.java.sip.communicator.util.Logger"))
            {
                // We've found the relevant frame.
                record.setSourceClassName(cname);
                record.setSourceMethodName(frame.getMethodName());
                break;
            }
            ix++;
        }

        return lineNumber;
    }

    /**
     * Loads all config properties.
     */
    private void loadConfigProperties()
    {
        loadProgramNameProperty();
        loadTimestampDisabledProperty();
    }

    /**
     * Checks and loads timestamp disabled property if any.
     */
    private static void loadTimestampDisabledProperty()
    {
        LogManager manager = LogManager.getLogManager();
        String cname = ScLogFormatter.class.getName();
        timestampDisabled = Boolean.parseBoolean(
            manager.getProperty(cname + DISABLE_TIMESTAMP_PROPERTY));
    }

    /**
     * Load the programname property to be used in logs to identify Jitsi-based
     * application which produced the logs
     */
    private static void loadProgramNameProperty()
    {
        LogManager manager = LogManager.getLogManager();
        String cname = ScLogFormatter.class.getName();
        programName = manager.getProperty(cname + PROGRAM_NAME_PROPERTY);
    }
    
}
