/*
 * @(#)ExtDesktopProperty.java
 *
 * Copyright 2002 JIDE Software Inc. All rights reserved.

 * Copyright (c) 1995, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.jidesoft.plaf;

import com.jidesoft.plaf.vsnet.ConvertListener;

import javax.swing.*;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;

/**
 * Wrapper for multiple values from the desktop. The value is lazily looked up, and can be accessed using the
 * <code>UIManager.ActiveValue</code> method <code>createValue</code>. If the underlying desktop property changes this
 * will force the UIs to update all known Frames. You can invoke <code>invalidate</code> to force the value to be
 * fetched again.
 */

public class ExtWindowsDesktopProperty implements UIDefaults.ActiveValue {
    /**
     * Indicates if an updateUI call is pending.
     */
    private static boolean updatePending;

    /**
     * PropertyChangeListener attached to the Toolkit.
     */
    private WeakPCL pcl;
    /**
     * Key used to lookup value from desktop.
     */
    private String[] keys;
    /**
     * Value to return.
     */
    private Object[] value;
    /**
     * Fallback value in case we get null from desktop.
     */
    private Object[] fallback;

    /**
     * Convert color to another color
     */
    private ConvertListener listener;
    /**
     * Toolkit.
     */
    private Toolkit toolkit;


    /**
     * Sets whether or not an updateUI call is pending.
     */
    private static synchronized void setUpdatePending(boolean update) {
        updatePending = update;
    }

    /**
     * Returns true if a UI update is pending.
     */
    private static synchronized boolean isUpdatePending() {
        return updatePending;
    }

    /**
     * Updates the UIs of all the known Frames.
     */
    private static void updateAllUIs() {
        Frame appFrames[] = Frame.getFrames();
        for (Frame frame : appFrames) {
            updateWindowUI(frame);
        }
    }

    /**
     * Updates the UI of the passed in window and all its children.
     */
    private static void updateWindowUI(Window window) {
        SwingUtilities.updateComponentTreeUI(window);
        Window ownedWins[] = window.getOwnedWindows();
        for (Window win : ownedWins) {
            updateWindowUI(win);
        }
    }


    /**
     * Creates a DesktopProperty.
     *
     * @param keys     Key used in looking up desktop value.
     * @param fallback Value used if desktop property is null.
     * @param toolkit  Toolkit used to fetch property from, can be null in which default will be used.
     */
    public ExtWindowsDesktopProperty(String[] keys, Object[] fallback, Toolkit toolkit, ConvertListener listener) {
        this.keys = keys;
        this.fallback = fallback;
        this.toolkit = toolkit;
        this.listener = listener;
    }

    /**
     * UIManager.LazyValue method, returns the value from the desktop or the fallback value if the desktop value is
     * null.
     */
    public Object createValue(UIDefaults table) {
        if (value == null) {
            value = configureValue(getValueFromDesktop());
            if (value[0] == null) {
                value = configureValue(getDefaultValue());
            }
        }
        return listener.convert(value);
    }

    /**
     * Returns the value from the desktop.
     */
    protected Object[] getValueFromDesktop() {
        if (this.toolkit == null) {
            this.toolkit = Toolkit.getDefaultToolkit();
        }
        pcl = new WeakPCL(this, toolkit);
        Object[] values = new Object[getKeys().length];
        for (int i = 0; i < getKeys().length; i++) {
            values[i] = toolkit.getDesktopProperty(getKeys()[i]);
            toolkit.addPropertyChangeListener(getKeys()[i], pcl);
        }
        return values;
    }

    /**
     * Returns the value to use if the desktop property is null.
     */
    protected Object[] getDefaultValue() {
        return fallback;
    }

    /**
     * Invalides the current value so that the next invocation of <code>createValue</code> will ask for the property
     * again.
     */
    public void invalidate() {
        if (pcl != null) {
            for (int i = 0; i < getKeys().length; i++) {
                toolkit.removePropertyChangeListener(getKeys()[i], pcl);
            }
            toolkit = null;
            pcl = null;
            value = null;
        }
    }

    /**
     * Requests that all components in the GUI hierarchy be updated to reflect dynamic changes in this look&feel.  This
     * update occurs by uninstalling and re-installing the UI objects. Requests are batched and collapsed into a single
     * update pass because often many desktop properties will change at once.
     */
    protected void updateUI() {
        if (!isUpdatePending()) {
            setUpdatePending(true);
            Runnable uiUpdater = new Runnable() {
                public void run() {
                    updateAllUIs();
                    setUpdatePending(false);
                }
            };
            SwingUtilities.invokeLater(uiUpdater);
        }
    }

    /**
     * Configures the value as appropriate for a defaults property in the UIDefaults table.
     */
    protected Object[] configureValue(Object[] value) {
        if (value != null) {
            for (int i = 0; i < value.length; i++) {
                value[i] = configureValue(value[i]);
            }
        }
        return value;
    }

    /**
     * Configures the value as appropriate for a defaults property in the UIDefaults table.
     */
    protected Object configureValue(Object value) {
        if (value != null) {
            if (value instanceof Color) {
                return new ColorUIResource((Color) value);
            }
            else if (value instanceof Font) {
                return new FontUIResource((Font) value);
            }
            else if (value instanceof UIDefaults.ProxyLazyValue) {
                value = ((UIDefaults.ProxyLazyValue) value).createValue(null);
            }
            else if (value instanceof UIDefaults.ActiveValue) {
                value = ((UIDefaults.ActiveValue) value).createValue(null);
            }
        }
        return value;
    }

    /**
     * Returns the key used to lookup the desktop properties value.
     */
    protected String[] getKeys() {
        return keys;
    }


    /**
     * As there is typically only one Toolkit, the PropertyChangeListener is handled via a WeakReference so as not to
     * pin down the DesktopProperty.
     */
    private static class WeakPCL extends WeakReference
            implements PropertyChangeListener {
        private Toolkit kit;

        WeakPCL(Object target, Toolkit kit) {
            super(target);
            this.kit = kit;
        }

        public void propertyChange(PropertyChangeEvent pce) {
            ExtWindowsDesktopProperty property = (ExtWindowsDesktopProperty) get();

            if (property == null) {
                // The property was GC'ed, we're no longer interested in
                // PropertyChanges, remove the listener.
                kit.removePropertyChangeListener(pce.getPropertyName(), this);
            }
            else {
                property.invalidate();
                property.updateUI();
            }
        }
    }
}
