/*
 * Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
 *
 * This file is part of the SeaGlass Pluggable Look and Feel.
 *
 * 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.
 *
 * $Id$
 */
package com.seaglasslookandfeel.util;

import java.awt.Component;
import java.awt.Shape;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.event.WindowListener;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

/**
 * Various utitilies used to manage window drawing.
 *
 * @author Ken Orr
 * @author Kathryn Huxtable
 */
public class WindowUtils {

    /**
     * Tries to make the given {@link Window} non-opqaue (transparent) across
     * platforms and JREs. This method is not guaranteed to succeed, and will
     * fail silently if the given {@code Window} cannot be made non-opaque.
     *
     * <p>This method is useful, for example, when creating a HUD style window
     * that is semi-transparent, and thus doesn't want the window background to
     * be drawn.</p>
     *
     * @param window the {@code Window} to make non-opaque.
     */
    public static void makeWindowNonOpaque(Window window) {
        if (PlatformUtils.isJava6()) {
            // on non-mac platforms, try to use the facilities of Java 6 update 10.
            if (!PlatformUtils.isMac()) {
                quietlyTryToMakeWindowNonOqaque(window);
            } else {
                window.setBackground(UIManager.getColor("seaGlassTransparent"));
            }
        }
    }
    
    /**
     * Sets the shape of a window.
     * This will be done via a com.sun API and may be not available on all platforms.
     * @param window to change the shape for 
     * @param s the new shape for the window.
     */

    public static void setWindowShape(Window window, Shape s) {
        if (PlatformUtils.isJava6()) {
            setWindowShapeJava6(window, s);
        } else {
            setWindowShapeJava7(window, s);
        }
    }

    /**
     * @param window
     * @param s
     */
    private static void setWindowShapeJava7(Window window, Shape s) {
        try {
            Class  clazz  = window.getClass();
            Method method = clazz.getMethod("setShape", Shape.class);
            method.invoke(window, s);
        } catch (Exception e) {
            // silently ignore this exception.
        }
    }
    
    /**
     * @param window
     * @param s
     */
    private static void setWindowShapeJava6(Window window, Shape s) {
        try {
            Class  clazz  = Class.forName("com.sun.awt.AWTUtilities");
            Method method = clazz.getMethod("setWindowShape", java.awt.Window.class, Shape.class);
            method.invoke(clazz, window, s);
        } catch (Exception e) {
            // silently ignore this exception.
        }
    }


    /**
     * Trys to invoke
     * {@code com.sun.awt.AWTUtilities.setWindowOpaque(window,false)} on the
     * given {@link Window}. This will only work when running with JRE 6 update
     * 10 or higher. This method will silently fail if the method cannot be
     * invoked.
     *
     * @param window the {@code Window} to try and make non-opaque.
     */
    @SuppressWarnings("unchecked")
    private static void quietlyTryToMakeWindowNonOqaque(Window window) {
        try {
            Class  clazz  = Class.forName("com.sun.awt.AWTUtilities");
            Method method = clazz.getMethod("setWindowOpaque", java.awt.Window.class, Boolean.TYPE);

            method.invoke(clazz, window, false);
        } catch (Exception e) {
            // silently ignore this exception.
        }
    }

    /**
     * Create and install the repaint window focus listener.
     *
     * @param  window the window.
     *
     * @return the listener.
     */
    public static WindowFocusListener createAndInstallRepaintWindowFocusListener(Window window) {
        // create a WindowFocusListener that repaints the window on focus
        // changes.
        WindowFocusListener windowFocusListener = new WindowFocusListener() {
            public void windowGainedFocus(WindowEvent e) {
                e.getWindow().repaint();
            }

            public void windowLostFocus(WindowEvent e) {
                e.getWindow().repaint();
            }
        };

        window.addWindowFocusListener(windowFocusListener);

        return windowFocusListener;
    }

    /**
     * {@code true} if the given {@link Component}'s parent {@link Window} is
     * currently active (focused).
     *
     * @param  component the {@code Component} to check the parent
     *                   {@code Window}'s focus for.
     *
     * @return {@code true} if the given {@code Component}'s parent
     *         {@code Window} is currently active.
     */
    public static boolean isParentWindowFocused(Component component) {
        Window window = SwingUtilities.getWindowAncestor(component);

        return window != null && window.isFocused();
    }

    /**
     * Installs a {@link WindowFocusListener} on the given {@link JComponent}'s
     * parent {@link Window}. If the {@code JComponent} doesn't yet have a
     * parent, then the listener will be installed when the component is added
     * to a container.
     *
     * @param component     the component who's parent frame to listen to focus
     *                      changes on.
     * @param focusListener the {@code WindowFocusListener} to notify when focus
     *                      changes.
     */
    public static void installWeakWindowFocusListener(JComponent component, WindowFocusListener focusListener) {
        WindowListener   weakFocusListener = createWeakWindowFocusListener(focusListener);
        AncestorListener ancestorListener  = createAncestorListener(component, weakFocusListener);

        component.addAncestorListener(ancestorListener);
    }

    /**
     * Create the weak window focus listener.
     *
     * @param  windowFocusListener the focus listener
     *
     * @return the weak referenced listener.
     */
    private static WindowListener createWeakWindowFocusListener(WindowFocusListener windowFocusListener) {
        final WeakReference<WindowFocusListener> weakReference = new WeakReference<WindowFocusListener>(windowFocusListener);

        return new WindowAdapter() {
            public void windowActivated(WindowEvent e) {
                // TODO if the WeakReference's object is null, remove the
                // WeakReference as a FocusListener.
                if (weakReference.get() != null) {
                    weakReference.get().windowGainedFocus(e);
                }
            }

            public void windowDeactivated(WindowEvent e) {
                if (weakReference.get() != null) {
                    weakReference.get().windowLostFocus(e);
                }
            }
        };
    }

    /**
     * Installs a listener on the given {@link JComponent}'s parent
     * {@link Window} that repaints the given component when the parent window's
     * focused state changes. If the given component does not have a parent at
     * the time this method is called, then an ancestor listener will be
     * installed that installs a window listener when the components parent
     * changes.
     *
     * @param component the {@code JComponent} to add the repaint focus listener
     *                  to.
     */
    public static void installJComponentRepainterOnWindowFocusChanged(JComponent component) {
        // TODO check to see if the component already has an ancestor.
        WindowListener   windowListener   = createWeakWindowFocusListener(createRepaintWindowListener(component));
        AncestorListener ancestorListener = createAncestorListener(component, windowListener);

        component.addAncestorListener(ancestorListener);
    }

    /**
     * Create the ancestor listener.
     *
     * @param  component      the component.
     * @param  windowListener the listener.
     *
     * @return the weak referenced listener.
     */
    private static AncestorListener createAncestorListener(JComponent component, final WindowListener windowListener) {
        final WeakReference<JComponent> weakReference = new WeakReference<JComponent>(component);

        return new AncestorListener() {
            public void ancestorAdded(AncestorEvent event) {
                // TODO if the WeakReference's object is null, remove the
                // WeakReference as an AncestorListener.
                Window window = weakReference.get() == null ? null : SwingUtilities.getWindowAncestor(weakReference.get());

                if (window != null) {
                    window.removeWindowListener(windowListener);
                    window.addWindowListener(windowListener);
                }
            }

            public void ancestorRemoved(AncestorEvent event) {
                Window window = weakReference.get() == null ? null : SwingUtilities.getWindowAncestor(weakReference.get());

                if (window != null) {
                    window.removeWindowListener(windowListener);
                }
            }

            public void ancestorMoved(AncestorEvent event) {
                // no implementation.
            }
        };
    }

    /**
     * Create the repaint window listener.
     *
     * @param  component the component.
     *
     * @return the listener.
     */
    private static WindowFocusListener createRepaintWindowListener(final JComponent component) {
        return new WindowAdapter() {
            public void windowActivated(WindowEvent e) {
                component.repaint();
            }

            public void windowDeactivated(WindowEvent e) {
                component.repaint();
            }
        };
    }

}