/*
 * 12/21/2008
 *
 * Util.java - Utility methods for the autocompletion package.
 * 
 * This library is distributed under a modified BSD license.  See the included
 * RSyntaxTextArea.License.txt file for details.
 */
package com.fr.design.gui.autocomplete;

import javax.swing.*;
import java.awt.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.AccessControlException;
import java.util.regex.Pattern;


/**
 * Utility methods for the auto-complete framework.
 *
 * @author Robert Futrell
 * @version 1.0
 */
public class Util {

    /**
     * If a system property is defined with this name and set, ignoring case,
     * to <code>true</code>, this library will not attempt to use Substance
     * renderers.  Otherwise, if a Substance Look and Feel is installed, we
     * will attempt to use Substance cell renderers in all of our dropdowns.<p>
     * <p/>
     * Note that we do not have a build dependency on Substance, so all access
     * to Substance stuff is done via reflection.  We will fall back onto
     * default renderers if something goes horribly wrong.
     */
    public static final String PROPERTY_DONT_USE_SUBSTANCE_RENDERERS =
            "com.fr.design.gui.autocomplete.DontUseSubstanceRenderers";

    /**
     * If this system property is <code>true</code>, then even the "main" two
     * auto-complete windows will allow window decorations via
     * {@link com.fr.design.gui.syntax.ui.rsyntaxtextarea.PopupWindowDecorator}.  If this property is undefined or
     * <code>false</code>, they won't honor such decorations.  This is due to
     * certain performance issues with translucent windows (used for drop
     * shadows), even as of Java 7u2.
     */
    public static final String PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS =
            "com.fr.design.gui.autocomplete.allowDecoratedAutoCompleteWindows";

    /**
     * Used for the color of hyperlinks when a LookAndFeel uses light text
     * against a dark background.
     */
    private static final Color LIGHT_HYPERLINK_FG = new Color(0xd8ffff);

    private static final Pattern TAG_PATTERN = Pattern.compile("<[^>]*>");

    private static final boolean useSubstanceRenderers;
    private static boolean desktopCreationAttempted;
    private static Object desktop;
    private static final Object LOCK_DESKTOP_CREATION = new Object();


    /**
     * Attempts to open a web browser to the specified URI.
     *
     * @param uri The URI to open.  If this is <code>null</code>, nothing
     *            happens and this method returns <code>false</code>.
     * @return Whether the operation was successful.  This will be
     *         <code>false</code> on JRE's older than 1.6.
     */
    public static boolean browse(URI uri) {

        boolean success = false;

        if (uri != null) {
            Object desktop = getDesktop();
            if (desktop != null) {
                try {
                    Method m = desktop.getClass().getDeclaredMethod(
                            "browse", new Class[]{URI.class});
                    m.invoke(desktop, new Object[]{uri});
                    success = true;
                } catch (RuntimeException re) {
                    throw re; // Keep FindBugs happy
                } catch (Exception e) {
                    // Ignore, just return "false" below.
                }
            }
        }

        return success;

    }


    /**
     * Returns the singleton <code>java.awt.Desktop</code> instance, or
     * <code>null</code> if it is unsupported on this platform (or the JRE
     * is older than 1.6).
     *
     * @return The desktop, as an {@link Object}.
     */
    private static Object getDesktop() {

        synchronized (LOCK_DESKTOP_CREATION) {

            if (!desktopCreationAttempted) {

                desktopCreationAttempted = true;

                try {
                    Class<?> desktopClazz = Class.forName("java.awt.Desktop");
                    Method m = desktopClazz.
                            getDeclaredMethod("isDesktopSupported");

                    boolean supported = ((Boolean) m.invoke(null)).booleanValue();
                    if (supported) {
                        m = desktopClazz.getDeclaredMethod("getDesktop");
                        desktop = m.invoke(null);
                    }

                } catch (RuntimeException re) {
                    throw re; // Keep FindBugs happy
                } catch (Exception e) {
                    // Ignore; keeps desktop as null.
                }

            }

        }

        return desktop;

    }


    /**
     * Returns a hex string for the specified color, suitable for HTML.
     *
     * @param c The color.
     * @return The string representation, in the form "<code>#rrggbb</code>",
     *         or <code>null</code> if <code>c</code> is <code>null</code>.
     */
    public static String getHexString(Color c) {

        if (c == null) {
            return null;
        }

        // Don't assume 0xff alpha
        //return "#" + Integer.toHexString(c.getRGB()&0xffffff).substring(2);

        StringBuilder sb = new StringBuilder("#");
        int r = c.getRed();
        if (r < 16) {
            sb.append('0');
        }
        sb.append(Integer.toHexString(r));
        int g = c.getGreen();
        if (g < 16) {
            sb.append('0');
        }
        sb.append(Integer.toHexString(g));
        int b = c.getBlue();
        if (b < 16) {
            sb.append('0');
        }
        sb.append(Integer.toHexString(b));

        return sb.toString();

    }


    /**
     * Returns the color to use for hyperlink-style components.  This method
     * will return <code>Color.blue</code> unless it appears that the current
     * LookAndFeel uses light text on a dark background, in which case a
     * brighter alternative is returned.
     *
     * @return The color to use for hyperlinks.
     */
    static final Color getHyperlinkForeground() {

        // This property is defined by all standard LaFs, even Nimbus (!),
        // but you never know what crazy LaFs there are...
        Color fg = UIManager.getColor("Label.foreground");
        if (fg == null) {
            fg = new JLabel().getForeground();
        }

        return isLightForeground(fg) ? LIGHT_HYPERLINK_FG : Color.blue;

    }


    /**
     * Returns the screen coordinates for the monitor that contains the
     * specified point.  This is useful for setups with multiple monitors,
     * to ensure that popup windows are positioned properly.
     *
     * @param x The x-coordinate, in screen coordinates.
     * @param y The y-coordinate, in screen coordinates.
     * @return The bounds of the monitor that contains the specified point.
     */
    public static Rectangle getScreenBoundsForPoint(int x, int y) {
        GraphicsEnvironment env = GraphicsEnvironment.
                getLocalGraphicsEnvironment();
        GraphicsDevice[] devices = env.getScreenDevices();
        for (int i = 0; i < devices.length; i++) {
            GraphicsConfiguration config = devices[i].getDefaultConfiguration();
            Rectangle gcBounds = config.getBounds();
            if (gcBounds.contains(x, y)) {
                return gcBounds;
            }
        }
        // If point is outside all monitors, default to default monitor (?)
        return env.getMaximumWindowBounds();
    }


    /**
     * Give apps a chance to decorate us with drop shadows, etc. Since very
     * scrolly things such as lists (of e.g. completions) are *very* slow when
     * in per-pixel translucent windows, even as of Java 7u2, we force the user
     * to specify an extra option for the two "main" auto-complete windows.
     *
     * @return Whether to allow decorating the main auto-complete windows.
     * @see #PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS
     */
    public static boolean getShouldAllowDecoratingMainAutoCompleteWindows() {
        try {
            return Boolean.getBoolean(
                    PROPERTY_ALLOW_DECORATED_AUTOCOMPLETE_WINDOWS);
        } catch (AccessControlException ace) { // We're in an applet.
            return false;
        }
    }


    /**
     * Returns whether we should attempt to use Substance cell renderers and
     * styles for things such as completion choices, if a Substance Look and
     * Feel is installed.  If this is <code>false</code>, we'll use our
     * standard rendering for completions, even when Substance is being used.
     *
     * @return Whether to use Substance renderers if Substance is installed.
     */
    public static boolean getUseSubstanceRenderers() {
        return useSubstanceRenderers;
    }


    /**
     * Returns whether the specified color is "light" to use as a foreground.
     * Colors that return <code>true</code> indicate that the current Look and
     * Feel probably uses light text colors on a dark background.
     *
     * @param fg The foreground color.
     * @return Whether it is a "light" foreground color.
     */
    public static final boolean isLightForeground(Color fg) {
        return fg.getRed() > 0xa0 && fg.getGreen() > 0xa0 && fg.getBlue() > 0xa0;
    }


    /**
     * Returns whether <code>str</code> starts with <code>start</code>,
     * ignoring case.
     *
     * @param str   The string to check.
     * @param start The prefix to check for.
     * @return Whether <code>str</code> starts with <code>start</code>,
     *         ignoring case.
     */
    public static boolean startsWithIgnoreCase(String str, String start) {
        int startLen = start.length();
        if (str.length() >= startLen) {
            for (int i = 0; i < startLen; i++) {
                char c1 = str.charAt(i);
                char c2 = start.charAt(i);
                if (Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }


    /**
     * Strips any HTML from a string.  The string must start with
     * "<code>&lt;html&gt;</code>" for markup tags to be stripped.
     *
     * @param text The string.
     * @return The string, with any HTML stripped.
     */
    public static String stripHtml(String text) {
        if (text == null || !text.startsWith("<html>")) {
            return text;
        }
        // TODO: Micro-optimize me, might be called in renderers and loops
        return TAG_PATTERN.matcher(text).replaceAll("");
    }


    static {

        boolean use = true;
        try {
            use = !Boolean.getBoolean(PROPERTY_DONT_USE_SUBSTANCE_RENDERERS);
        } catch (AccessControlException ace) { // We're in an applet.
            use = true;
        }
        useSubstanceRenderers = use;

    }


}