/*
 * @(#)UIManagerLookup.java 4/5/2007
 *
 * Copyright 2002 - 2007 JIDE Software Inc. All rights reserved.
 */

package com.jidesoft.plaf;

import com.jidesoft.converter.ObjectConverterManager;

import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class simply uses UIManager's get method to lookup the UIDefaults. We used this everywhere in our code so that
 * we have one central place to find out which UIDefaults we are using. Another good thing is you can use {@link
 * #setTrace(boolean)} and {@link #setDebug(boolean)} to turn on the trace so that it will print out which UIDefaults we
 * are trying to get.
 */
public class UIDefaultsLookup {
    private static Logger LOGGER = Logger.getLogger(UIDefaultsLookup.class.getName());

    private static boolean _debug = false;
    private static boolean _trace = false;

    /**
     * Sets the debug mode. If debug mode is on, we will print out any UIDefaults that the value is null.
     *
     * @param debug true or false.
     */
    public static void setDebug(boolean debug) {
        _debug = debug;
    }

    /**
     * Sets the trace mode. If trace mode is on, we will print out any UIDefaults we are trying to get and its current
     * value.
     *
     * @param trace true or false.
     */
    public static void setTrace(boolean trace) {
        _trace = trace;
    }

    public static void put(UIDefaults table, String key, Object value) {
        Object v = table.get(key);
        if (v == null || !(v instanceof Map)) {
            v = new HashMap<ClassLoader, Object>();
            table.put(key, v);
        }
        Object cl = UIManager.get("ClassLoader");
        if (!(cl instanceof ClassLoader)) {
            cl = value.getClass().getClassLoader();
        }
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("Put " + key + " " + value + " using ClassLoader: " + cl);
        }
        ((Map) v).put(cl, value);
    }

/*  This is the old method used before JDk8 b87. The getCallerClass(int) is removed in b87 so we can't use it any longer.
    // Returns the invoker's class loader, or null if none.
    // NOTE: This must always be invoked when there is exactly one intervening
    // frame from the core libraries on the stack between this method's
    // invocation and the desired invoker.
    static ClassLoader getCallerClassLoader() {
        Object cl = UIManager.get("ClassLoader");
        if (cl instanceof ClassLoader) {
            return (ClassLoader) cl;
        }

        // NOTE use of more generic Reflection.getCallerClass()
        Class caller = Reflection.getCallerClass(3);
        // This can be null if the VM is requesting it
        if (caller == null) {
            return null;
        }
        // Circumvent security check since this is package-private
        return caller.getClassLoader();
    }
*/


    static ClassLoader getCallerClassLoader() {
        Object cl = UIManager.get("ClassLoader");
        if (cl instanceof ClassLoader) {
            return (ClassLoader) cl;
        }

        String className = Thread.currentThread().getStackTrace()[3].getClassName();
        try {
            return Class.forName(className).getClassLoader();
        }
        catch (ClassNotFoundException e) {
            return null;
        }
    }

    public static Object get(Object key) {
        Object value = UIManager.get(key);
        log(value, key, null);
        if (value instanceof Map && "Theme.painter".equals(key)) {
            Map map = (Map) value;
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("Getting " + key + " from a map");
                for (Object o : map.keySet()) {
                    LOGGER.fine("\t" + o + " => " + map.get(o));
                }
            }
            try {
                ClassLoader classLoader = getCallerClassLoader();
                Object o = map.get(classLoader);
                if (o != null) {
                    if (LOGGER.isLoggable(Level.FINE)) {
                        LOGGER.fine("\tGetting " + o + " using CallerClassLoader" + classLoader);
                    }
                }
                while (o == null && classLoader.getParent() != null) {
                    classLoader = classLoader.getParent();
                    o = map.get(classLoader);
                    if (o != null) {
                        if (LOGGER.isLoggable(Level.FINE)) {
                            LOGGER.fine("\tGetting " + o + " using one of the parent ClassLoader " + classLoader);
                        }
                        break;
                    }
                }
                if (o != null) return o;
            }
            catch (Exception e) {
                // ignore
            }
            if (map.size() == 1) {
                Object o = map.values().iterator().next();
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.fine("Failed...getting the only one " + o);
                }
                return o;
            }
            else {
                Object o = map.get(LookAndFeelFactory.getUIManagerClassLoader());
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.fine("Failed...getting " + o + " using UIManagerClassLoader " + LookAndFeelFactory.getUIManagerClassLoader());
                }
                return o;
            }
        }
        return value;
    }

    public static Object get(Object key, Locale l) {
        Object value = UIManager.get(key, l);
        log(value, key, l);
        return value;
    }

    private static void log(Object value, Object key, Locale l) {
        if (_debug && value == null) {
            System.out.println("\"" + key + (l == null ? "" : l.toString()) + " \" ==> null ------------------------");
        }
        else if (_trace) {
            if (value == null) {
                System.out.println("\"" + key + (l == null ? "" : l.toString()) + " \" ==> null ------------------------");
            }
            else {
                System.out.println("\"" + key + (l == null ? "" : l.toString()) + " \" ==> " + value.getClass().getName() + "(" + ObjectConverterManager.toString(value) + ")");
            }
        }
    }

    /**
     * If the value of <code>key</code> is a <code>Font</code> return it, otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is a <code>Font</code>, return the <code>Font</code> object; otherwise
     *         return <code>null</code>
     */
    public static Font getFont(Object key) {
        Object value = get(key);
        return (value instanceof Font) ? (Font) value : null;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is a <code>Font</code> return it, otherwise
     * return <code>null</code>.
     *
     * @param key the desired key
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is a <code>Font</code>, return the
     *         <code>Font</code> object; otherwise return <code>null</code>
     * @since 1.9.5.04
     */
    public static Font getFont(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Font) ? (Font) value : null;
    }

    /**
     * If the value of <code>key</code> is a <code>Color</code> return it, otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is a <code>Color</code>, return the <code>Color</code> object;
     *         otherwise return <code>null</code>
     */
    public static Color getColor(Object key) {
        Object value = get(key);
        return (value instanceof Color) ? (Color) value : null;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is a <code>Color</code> return it, otherwise
     * return <code>null</code>.
     *
     * @param key the desired key
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is a <code>Color</code>, return the
     *         <code>Color</code> object; otherwise return <code>null</code>
     * @since 1.9.5.04
     */
    public static Color getColor(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Color) ? (Color) value : null;
    }


    /**
     * If the value of <code>key</code> is an <code>Icon</code> return it, otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is an <code>Icon</code>, return the <code>Icon</code> object; otherwise
     *         return <code>null</code>
     */
    public static Icon getIcon(Object key) {
        Object value = get(key);
        return (value instanceof Icon) ? (Icon) value : null;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is an <code>Icon</code> return it, otherwise
     * return <code>null</code>.
     *
     * @param key the desired key
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is an <code>Icon</code>, return the
     *         <code>Icon</code> object; otherwise return <code>null</code>
     * @since 1.9.5.04
     */
    public static Icon getIcon(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Icon) ? (Icon) value : null;
    }


    /**
     * If the value of <code>key</code> is a <code>Border</code> return it, otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is a <code>Border</code>, return the <code>Border</code> object;
     *         otherwise return <code>null</code>
     */
    public static Border getBorder(Object key) {
        Object value = get(key);
        return (value instanceof Border) ? (Border) value : null;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is a <code>Border</code> return it, otherwise
     * return <code>null</code>.
     *
     * @param key the desired key
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is a <code>Border</code>, return the
     *         <code>Border</code> object; otherwise return <code>null</code>
     * @since 1.9.5.04
     */
    public static Border getBorder(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Border) ? (Border) value : null;
    }


    /**
     * If the value of <code>key</code> is a <code>String</code> return it, otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is a <code>String</code>, return the <code>String</code> object;
     *         otherwise return <code>null</code>
     */
    public static String getString(Object key) {
        Object value = get(key);
        return (value instanceof String) ? (String) value : null;
    }

    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is a <code>String</code> return it, otherwise
     * return <code>null</code>.
     *
     * @param key the desired key
     * @param l   the desired <code>Locale</code>
     * @return if the value for <code>key</code> for the given <code>Locale</code> is a <code>String</code>, return the
     *         <code>String</code> object; otherwise return <code>null</code>
     * @since 1.9.5.04
     */
    public static String getString(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof String) ? (String) value : null;
    }

    /**
     * If the value of <code>key</code> is an <code>Integer</code> return its integer value, otherwise return 0.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is an <code>Integer</code>, return its value, otherwise return 0
     */
    public static int getInt(Object key) {
        Object value = get(key);
        return (value instanceof Integer) ? (Integer) value : 0;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is an <code>Integer</code> return its integer
     * value, otherwise return 0.
     *
     * @param key the desired key
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is an <code>Integer</code>, return its value,
     *         otherwise return 0
     * @since 1.9.5.04
     */
    public static int getInt(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Integer) ? (Integer) value : 0;
    }


    /**
     * If the value of <code>key</code> is boolean, return the boolean value, otherwise return false.
     *
     * @param key an <code>Object</code> specifying the key for the desired boolean value
     * @return if the value of <code>key</code> is boolean, return the boolean value, otherwise return false.
     * @since 1.9.5.04
     */
    public static boolean getBoolean(Object key) {
        Object value = get(key);
        return (value instanceof Boolean) ? (Boolean) value : false;
    }

    /**
     * If the value of <code>key</code> is boolean, return the boolean value, otherwise return false.
     *
     * @param key          an <code>Object</code> specifying the key for the desired boolean value
     * @param defaultValue the default value if the key is missing
     * @return if the value of <code>key</code> is boolean, return the boolean value, otherwise return false.
     */
    public static boolean getBoolean(Object key, boolean defaultValue) {
        Object value = get(key);
        return (value instanceof Boolean) ? (Boolean) value : defaultValue;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is boolean, return the boolean value,
     * otherwise return false.
     *
     * @param key an <code>Object</code> specifying the key for the desired boolean value
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is boolean, return the boolean value, otherwise
     *         return false.
     * @since 1.9.5.04
     */
    public static boolean getBoolean(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Boolean) ? (Boolean) value : false;
    }


    /**
     * If the value of <code>key</code> is an <code>Insets</code> return it, otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is an <code>Insets</code>, return the <code>Insets</code> object;
     *         otherwise return <code>null</code>
     */
    public static Insets getInsets(Object key) {
        Object value = get(key);
        return (value instanceof Insets) ? (Insets) value : null;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is an <code>Insets</code> return it, otherwise
     * return <code>null</code>.
     *
     * @param key the desired key
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is an <code>Insets</code>, return the
     *         <code>Insets</code> object; otherwise return <code>null</code>
     * @since 1.9.5.04
     */
    public static Insets getInsets(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Insets) ? (Insets) value : null;
    }


    /**
     * If the value of <code>key</code> is a <code>Dimension</code> return it, otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @return if the value for <code>key</code> is a <code>Dimension</code>, return the <code>Dimension</code> object;
     *         otherwise return <code>null</code>
     */
    public static Dimension getDimension(Object key) {
        Object value = get(key);
        return (value instanceof Dimension) ? (Dimension) value : null;
    }


    /**
     * If the value of <code>key</code> for the given <code>Locale</code> is a <code>Dimension</code> return it,
     * otherwise return <code>null</code>.
     *
     * @param key the desired key
     * @param l   the desired locale
     * @return if the value for <code>key</code> and <code>Locale</code> is a <code>Dimension</code>, return the
     *         <code>Dimension</code> object; otherwise return <code>null</code>
     * @since 1.9.5.04
     */
    public static Dimension getDimension(Object key, Locale l) {
        Object value = get(key, l);
        return (value instanceof Dimension) ? (Dimension) value : null;
    }
}
