/**
 * Debug.java
 * Copyright 2007 - 2008 Zach Scrivena
 * zachscrivena@gmail.com
 * http://zs.freeshell.org/
 *
 * TERMS AND CONDITIONS:
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.freeshell.zs.common;

import java.io.Console;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Locale;


/**
 * Perform debugging-related operations.
 */
public final class Debug
{
    /** time format string (yyyy-MM-dd HH:mm:ss.SSS) */
    private static final String TIMESTAMP_FORMAT_STRING = "\n[%1$tF %1$tT.%1$tL] ";

    /** standard error output stream */
    private static PrintWriter err;


    /**
    * Static initialization block.
    */
    static
    {
        final Console c = System.console();

        if (c == null)
        {
            err = new PrintWriter(System.out);
        }
        else
        {
            err = c.writer();
        }
    }


    /**
    * Private constructor that should never be called.
    */
    private Debug()
    {}


    /**
    * Print to the standard error output stream immediately.
    *
    * @param format
    *     format string
    * @param args
    *     arguments referenced by the format specifiers in the format string
    */
    public static void p(
            final String format,
            final Object... args)
    {
        err.format(format, args);
        err.flush();
    }


    /**
    * Print to the standard error output stream immediately, with a timestamp.
    *
    * @param format
    *     format string
    * @param args
    *     arguments referenced by the format specifiers in the format string
    */
    public static void pt(
            final String format,
            final Object... args)
    {
        err.format(Locale.ENGLISH, TIMESTAMP_FORMAT_STRING, new Date());
        err.format(format, args);
        err.flush();
    }


    /**
    * Get a string representation of the stack trace for the specified exception.
    *
    * @param ex
    *      exception
    * @return
    *      string representation of the stack trace
    */
    public static String getStackTraceString(
            final Exception ex)
    {
        final StringBuilder sb = new StringBuilder("Exception stack trace:");

        for (StackTraceElement e : ex.getStackTrace())
        {
            sb.append("\n  ");
            sb.append(e);
        }

        return sb.toString();
    }


    /**
    * Get a string representation of the system information (OS, JRE, JVM).
    *
    * @return
    *     string representation of the system information
    */
    public static String getSystemInformationString()
    {
        final StringBuilder sb = new StringBuilder();
        sb.append("System Information:\n  ");
        sb.append(getSystemProperty("os.name"));
        sb.append(" (");
        sb.append(getSystemProperty("os.version"));
        sb.append(", ");
        sb.append(getSystemProperty("os.arch"));
        sb.append(")\n  JRE ");
        sb.append(getSystemProperty("java.version"));
        sb.append(" (");
        sb.append(getSystemProperty("java.vendor"));
        sb.append(")\n  ");
        sb.append(getSystemProperty("java.vm.name"));
        sb.append(' ');
        sb.append(getSystemProperty("java.vm.version"));
        sb.append(" (");
        sb.append(getSystemProperty("java.vm.vendor"));
        sb.append(')');
        return sb.toString();
    }


    /**
    * Get the specified system property, without throwing any exceptions.
    *
    * @param key
    *     key of the system property to be obtained
    * @return
    *     String representation of the specified system property
    */
    public static String getSystemProperty(
            final String key)
    {
        try
        {
            return System.getProperty(key);
        }
        catch (Exception e)
        {
            return String.format("Failed to get system property \"%s\" (%s)", key, e.toString());
        }
    }


    /**
    * Set the specified system property, without throwing any exceptions.
    *
    * @param key
    *     key of the system property to be set
    * @param value
    *     value of the system property
    */
    public static void setSystemProperty(
            final String key,
            final String value)
    {
        try
        {
            System.setProperty(key, value);
        }
        catch (Exception e)
        {
            /* ignore */
        }
    }


    /**
    * Sleep for the specified number of milliseconds.
    * This method uses the Thread.sleep() method, but will ignore the InterruptedException
    * exception if thrown.
    *
    * @param millis
    *     number of milliseconds to sleep
    */
    public static void sleep(
            final long millis)
    {
        try
        {
            Thread.sleep(millis);
        }
        catch (InterruptedException e)
        {
            /* ignore */
        }
    }

    /******************
    * NESTED CLASSES *
    ******************/

    /**
    * Represent a "capsule" for passing a single value in a thread-safe manner.
    * The capsule value is set by calling <code>set()</code>, and read by calling <code>get()</code>.
    * The <code>get()</code> method blocks until the value is set.
    * The original state of the capsule can be restored by calling <code>reset()</code>.
    */
    public static class ValueCapsule<T>
    {
        /** refresh interval in milliseconds */
        private static final long REFRESH_INTERVAL_MILLISECONDS = 50L;

        /** capsule value to be passed */
        private T value;

        /** mutex lock for <code>value</code> */
        private final Object valueLock = new Object();

        /** has the capsule value been set by calling the <code>set()</code> method? */
        private boolean setCalled;


        /**
        * Constructor.
        */
        public ValueCapsule()
        {
            reset();
        }


        /**
        * Restore the original state of the capsule.
        */
        public void reset()
        {
            synchronized (valueLock)
            {
                value = null;
                setCalled = false;
            }
        }


        /**
        * Set the capsule value.
        *
        * @param value
        *     new capsule value
        */
        public void set(
                final T value)
        {
            synchronized (valueLock)
            {
                this.value = value;
                setCalled = true;
            }
        }


        /**
        * Get the capsule value.
        * This method blocks until the value is set.
        *
        * @return
        *     capsule value
        */
        public T get()
        {
            while (true)
            {
                synchronized (valueLock)
                {
                    if (setCalled)
                    {
                        return value;
                    }
                }

                Debug.sleep(REFRESH_INTERVAL_MILLISECONDS);
            }
        }
    }
}