// Package 
package gov.va.med.fw.util;

// Java Classes
import java.text.NumberFormat;

// Library Classes
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class provides generic System helper methods.  Note that all logging is only performed
 * when "info" level logging is enabled.
 *
 * @author Andrew Pach
 * @version 3.0
 */
public class SystemUtils extends org.apache.commons.lang.SystemUtils
{
	/** Logger name that can be configured in log4j.xml files
	 * with <level value="all"/>.  Allows for classes to load that logger by
	 * this constant name (rather than their class instance) */
	public static final String ALWAYS_LOGGER = "ALWAYS_LOGGER";
	
    protected static final Log logger = LogFactory.getLog(SystemUtils.class);
    protected static final Runtime runtime = Runtime.getRuntime();

    /**
     * Logs all statistics (i.e. system and memory statistics).  Garbage collection is not
     * called prior to logging the statistics.
     *
     */
    public static void logAllStats()
    {
        logAllStats(false);
    }

    /**
     * Logs all statistics (i.e. system and memory statistics).
     *
     * @param garbageCollectFirst If true, garbage collection will be called before logging the
     * memory information.
     */
    public static void logAllStats(boolean garbageCollectFirst)
    {
        logSystemStats();
        logMemory(garbageCollectFirst);
    }

    /**
     * Logs the system statistics.
     */
    public static void logSystemStats()
    {
        if (logger.isInfoEnabled())
        {
            logger.info("OS Name: " + OS_NAME + LINE_SEPARATOR +
                "OS Version: " + OS_VERSION + LINE_SEPARATOR +
                "Java Version: " + JAVA_VERSION_FLOAT + LINE_SEPARATOR +
                "Available Processors: " + runtime.availableProcessors());
        }
    }

    /**
     * Logs the current memory information.  Garbage collection is not called prior to logging
     * the memory information.
     */
    public static void logMemory()
    {
        logMemory(false);
    }

    /**
     * Logs the current memory information while optionally performing garbage collection.
     *
     * @param garbageCollectFirst If true, garbage collection will be called before logging the
     * memory information.
     */
    public static void logMemory(boolean garbageCollectFirst)
    {
        if (logger.isInfoEnabled())
        {
            if (garbageCollectFirst)
            {
                runtime.gc();
            }
            logMemory(getMaxMemory(), getAvailableMemory(), getUsedMemory());
        }
    }

    /**
     * Logs the maximum, available, and used memory based on the passed in values.
     *
     * @param maxMemory The maximum memory
     * @param availableMemory The available memory
     * @param usedMemory The used memory
     */
    public static void logMemory(long maxMemory, long availableMemory, long usedMemory)
    {
        if (logger.isInfoEnabled())
        {
            NumberFormat nf = NumberFormat.getInstance();
            NumberFormat nfp = NumberFormat.getPercentInstance();

            logger.info("Used Memory:      " + nf.format(usedMemory) +
                " bytes (" + nfp.format((float)usedMemory / (float)maxMemory ) + ")" + LINE_SEPARATOR +
                "Available Memory: " + nf.format(availableMemory) +
                " bytes (" + nfp.format((float)availableMemory / (float)maxMemory) + ")" + LINE_SEPARATOR +
                "Max Memory:       " + nf.format(maxMemory) + " bytes");
        }
    }

    /**
     * Logs the memory delta between the old used and the current used memory in bytes.
     *
     * @param oldUsedMemory The old used memory in bytes.
     */
    public static void logMemoryDelta(long oldUsedMemory)
    {
        logMemoryDelta(null, oldUsedMemory, getUsedMemory());
    }

    public static void logMemoryDelta(String context, long oldUsedMemory)
    {
        logMemoryDelta(context, oldUsedMemory, getUsedMemory());
    }
    
    /**
     * Logs the memory delta between the old used and new used memory in bytes.
     *
     *@partam context Contextual information to log
     * @param oldUsedMemory The old used memory in bytes.
     * @param newUsedMemory The new used memory in bytes.
     */
    public static void logMemoryDelta(String context, long oldUsedMemory, long newUsedMemory)
    {
        if (logger.isInfoEnabled())
        {
            NumberFormat nf = NumberFormat.getInstance();
            NumberFormat nfp = NumberFormat.getPercentInstance();
            StringBuffer buf = new StringBuffer();
            if(context != null)
            	buf.append("[").append(context).append("] ");
            buf.append("Delta Memory:     ").append(nf.format(newUsedMemory - oldUsedMemory));
            buf.append(" bytes (").append(nfp.format((float)(newUsedMemory - oldUsedMemory) / (float)getMaxMemory() ) + ")");
            
            logger.info(buf.toString());
        }
    }

    /**
     * Gets the maximum memory.
     *
     * @return The maximum memory.
     */
    public static long getMaxMemory()
    {
        return runtime.maxMemory();
    }

    /**
     * Gets the currently available memory.
     *
     * @return The currently available memory.
     */
    public static long getAvailableMemory()
    {
        return getMaxMemory() - runtime.totalMemory() + runtime.freeMemory();
    }

    /**
     * Gets the current used memory.
     *
     * @return The current used memory.
     */
    public static long getUsedMemory()
    {
        return getMaxMemory() - getAvailableMemory();
    }
}
