/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander 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.
 *
 * muCommander 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 com.mucommander.ui.icon;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.Hashtable;
import java.util.Map;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.commons.file.util.ResourceLoader;

/**
 * IconManager takes care of loading, caching, rescaling the icons contained inside the application's JAR file.
 *
 * @author Maxence Bernard
 */
public class IconManager {
	private static final Logger LOGGER = LoggerFactory.getLogger(IconManager.class);
	
    /** Caches for the different icon sets */
    private final static Map<String, ImageIcon> caches[];

    /** Designates the file icon set */
    public final static int FILE_ICON_SET        = 0;
    /** Designates the action icon set */
    public final static int ACTION_ICON_SET      = 1;
    /** Designates the toolbar icon set */
    public final static int STATUS_BAR_ICON_SET  = 2;
    /** Designates the table icon set */
    public final static int COMMON_ICON_SET      = 3;
    /** Designates the preferences icon set */
    public final static int PREFERENCES_ICON_SET = 4;
    /** Designates the progress icon set */
    public final static int PROGRESS_ICON_SET    = 5;
    /** Designates the language icon set */
    public final static int LANGUAGE_ICON_SET    = 6;
    /** Designates the mucommander icon set */
    public final static int MUCOMMANDER_ICON_SET = 7;

    /** Base folder of all images */
    private final static String BASE_IMAGE_FOLDER = "/images";
    /** Icon sets folders within the application's JAR file */
    private final static String ICON_SET_FOLDERS[] = {
        BASE_IMAGE_FOLDER +"/file/",
        BASE_IMAGE_FOLDER +"/action/",
        BASE_IMAGE_FOLDER +"/status_bar/",
        BASE_IMAGE_FOLDER +"/common/",
        BASE_IMAGE_FOLDER +"/preferences/",
        BASE_IMAGE_FOLDER +"/progress/",
        BASE_IMAGE_FOLDER +"/language/",
        BASE_IMAGE_FOLDER +"/mucommander/"
    };


    static {
        // Initialize caches for icon sets that need it.
        // Icons which are displayed once in a while like preferences icons don't need to be cached
        caches = new Hashtable[ICON_SET_FOLDERS.length];
        caches[FILE_ICON_SET]       = new Hashtable<String, ImageIcon>();
        caches[ACTION_ICON_SET]     = new Hashtable<String, ImageIcon>();
        caches[STATUS_BAR_ICON_SET] = new Hashtable<String, ImageIcon>();
        caches[COMMON_ICON_SET]     = new Hashtable<String, ImageIcon>();
        caches[PROGRESS_ICON_SET]   = new Hashtable<String, ImageIcon>();
    }


    /**
     * Creates a new instance of IconManager.
     */
    private IconManager() {}


    /**
     * Creates and returns an ImageIcon instance using the specified icon path and scale factor. No caching.
     *
     * @param iconPath path of the icon resource inside the application's JAR file
     * @param scaleFactor the icon scale factor, <code>1.0f</code> to have the icon in its original size (no rescaling)
     */
    public static ImageIcon getIcon(String iconPath, float scaleFactor) {
        URL resourceURL = ResourceLoader.getResourceAsURL(iconPath);
        if(resourceURL==null) {
            LOGGER.trace("Warning: attempt to load non-existing icon: {}, icon missing?", iconPath);
            return null;
        }

        ImageIcon icon = new ImageIcon(resourceURL);
        return scaleFactor==1.0f?icon:getScaledIcon(icon, scaleFactor);
    }

    /**
     * Convenience method, calls and returns the result of {@link #getIcon(String, float) getIcon(iconPath, scaleFactor)}
     * with a scale factor of 1.0f (no rescaling).
     */
    public static ImageIcon getIcon(String iconPath) {
        return getIcon(iconPath, 1.0f);
    }


    /**
     * Returns a scaled version of the given ImageIcon instance, using the specified scale factor.
     *
     * @param icon the icon to scale.
     * @param scaleFactor the icon scale factor, <code>1.0f</code> to have the icon in its original size (no rescaling)
     */
    public static ImageIcon getScaledIcon(ImageIcon icon, float scaleFactor) {
        if(scaleFactor==1.0f || icon==null)
            return icon;

        Image image = icon.getImage();
        return new ImageIcon(image.getScaledInstance((int)(scaleFactor*image.getWidth(null)), (int)(scaleFactor*image.getHeight(null)), Image.SCALE_AREA_AVERAGING));
    }

    /**
     * Returns a 'composite' icon made by composing the two given icons: the <code>backgroundIcon</code> is painted
     * first, and the <code>foregroundIcon</code> is superposed, letting its non-transparent pixels reveal the
     * background icon.
     * For this method to provide a meaningful result, the two icons should have the same dimensions and the
     * <code>foreground</code> should have some transparent pixels.
     *
     * @param backgroundIcon the icon that is painted first
     * @param foregroundIcon the icon that is superposed above backgroundIcon, should use transparency
     * @return a 'composite' icon made by composing the two given icons
     */
    public static ImageIcon getCompositeIcon(Icon backgroundIcon, Icon foregroundIcon) {
        BufferedImage bi = new BufferedImage(backgroundIcon.getIconWidth(), backgroundIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);

        Graphics g = bi.getGraphics();
        backgroundIcon.paintIcon(null, g, 0, 0);
        foregroundIcon.paintIcon(null, g, 0, 0);

        return new ImageIcon(bi);
    }


    /**
     * Returns an icon in the specified icon set and with the given name. If a scale factor other than 1.0f is passed,
     * the return icon will be scaled accordingly.
     *
     * <p>If the icon set has a cache, first looks for an existing instance in the cache, and if it couldn't be found, 
     * create an instance and store it in the cache for future access. Note that the cached icon is unscaled, i.e.
     * the scaled icon is not cached.</p>
     *
     * @param iconSet an icon set (see public constants for possible values)
     * @param iconName filename of the icon to retrieve
     * @param scaleFactor the icon scale factor, <code>1.0f</code> to have the icon in its original size (no rescaling)
     * @return an ImageIcon instance corresponding to the specified icon set, name and scale factor,
     * <code>null</code> if the image wasn't found or couldn't be loaded
     */
    public static ImageIcon getIcon(int iconSet, String iconName, float scaleFactor) {
        Map<String, ImageIcon> cache = caches[iconSet];
        ImageIcon icon;

        if(cache==null) {
            // No caching, simply create the icon
            icon = getIcon(ICON_SET_FOLDERS[iconSet]+iconName);
        }
        else {
            // Look for the icon in the cache
            icon = cache.get(iconName);
            if(icon==null) {
                // Icon is not in the cache, let's create it
                icon = getIcon(ICON_SET_FOLDERS[iconSet]+iconName);
                // and add it to the cache if icon exists
                if(icon!=null)
                    cache.put(iconName, icon);
            }
        }

        if(icon==null)
            return null;

        return scaleFactor==1.0f?icon:getScaledIcon(icon, scaleFactor);
    }


    /**
     * Convenience method, calls and returns the result of {@link #getIcon(int, String, float) getIcon(iconSet, iconName, scaleFactor)}
     * with a scale factor of 1.0f (no rescaling).
     */
    public static ImageIcon getIcon(int iconSet, String iconName) {
        return getIcon(iconSet, iconName, 1.0f);
    }


    /**
     * Returns an icon made of the specified icon and some transparent space around it.
     *
     * @param icon the original icon, will be painted at the center of the new icon
     * @param insets specifies the dimensions of the transparent space around the returned icon
     * @return an icon made of the specified icon and some transparent space around it
     */
    public static ImageIcon getPaddedIcon(ImageIcon icon, Insets insets) {
        BufferedImage bi = new BufferedImage(
                icon.getIconWidth()+insets.left+insets.right,
                icon.getIconHeight()+insets.top+insets.bottom,
                BufferedImage.TYPE_INT_ARGB);

        Graphics g = bi.getGraphics();
        g.drawImage(icon.getImage(), insets.left, insets.top, null);

        return new ImageIcon(bi);
    }



    /**
     * Creates and returns an ImageIcon with the same content and dimensions. This method is useful when an ImageIcon
     * is needed and only an Icon is available.
     *
     * <p>If the given Icon is already an ImageIcon, the same instance is returned. If it is not, a new ImageIcon is 
     * created and returned.
     */
    public static ImageIcon getImageIcon(Icon icon) {
        if(icon instanceof ImageIcon)
            return (ImageIcon)icon;

        BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
        icon.paintIcon(null, bi.getGraphics(), 0, 0);

        return new ImageIcon(bi);
    }


    /**
     * Returns the path to the folder that contains the image resource files of the given icon set.
     * The returned path is relative to the application JAR file's root and contains a trailing slash.
     *
     * @param iconSet an icon set (see public constants for possible values)
     */
    public static String getIconSetFolder(int iconSet) {
        return ICON_SET_FOLDERS[iconSet];
    }
}
