/**
 *  Copyright (C) 2002-2008  The FreeCol Team
 *
 *  This file is part of FreeCol.
 *
 *  FreeCol 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  FreeCol 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 FreeCol.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.freecol.common.resources;

import java.awt.Dimension;
import java.awt.Image;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import javax.swing.ImageIcon;

import net.sf.freecol.FreeCol;
import net.sf.freecol.client.gui.video.Video;
import net.sf.freecol.common.io.sza.SimpleZippedAnimation;


/**
 * Class for getting resources (images, audio etc).
 */
public class ResourceManager {
    
    /*
     * The following fields are mappings from resource IDs
     * to resources. A mapping is defined within a specific
     * context. See the comment on each field's setter for
     * more information: 
     */
    private static ResourceMapping baseMapping;
    private static ResourceMapping tcMapping;
    private static ResourceMapping campaignMapping;
    private static ResourceMapping scenarioMapping;
    private static List<ResourceMapping> modMappings = new LinkedList<ResourceMapping>();

    /*
     * All the mappings above merged into this single
     * ResourceMapping according to precendence.
     */
    private static ResourceMapping mergedContainer;
    
    private static volatile Thread preloadThread = null;
    
    private static volatile boolean dirty = false;
    
    private static Dimension lastWindowSize;
    
    
    /**
     * Sets the mappings specified in the date/base-directory
     * @param _baseMapping The mapping between IDs and files. 
     */
    public static void setBaseMapping(final ResourceMapping _baseMapping) {
        baseMapping = _baseMapping;
        dirty = true;
    }

    /**
     * Sets the mappings specified for a Total Conversion (TC).
     * @param _tcMapping The mapping between IDs and files. 
     */
    public static void setTcMapping(final ResourceMapping _tcMapping) {
        tcMapping = _tcMapping;
        dirty = true;
    }

    /**
     * Sets the mappings specified by mods.
     * @param _modMappings A list of the mapping between IDs and files. 
     */
    public static void setModMappings(final List<ResourceMapping> _modMappings) {
        modMappings = _modMappings;
        dirty = true;
    }
    
    /**
     * Sets the mappings specified in a campaign.
     * @param _campaignMapping The mapping between IDs and files. 
     */
    public static void setCampaignMapping(final ResourceMapping _campaignMapping) {
        campaignMapping = _campaignMapping;
        dirty = true;
    }

    /**
     * Sets the mappings specified in a scenario.
     * @param _scenarioMapping The mapping between IDs and files. 
     */
    public static void setScenarioMapping(final ResourceMapping _scenarioMapping) {
        scenarioMapping = _scenarioMapping;
        dirty = true;
    }

    /**
     * Preload resources. This method is intended to
     * be called when starting the application, as
     * it blocks until resources needed for the first
     * panels have been loaded.
     * 
     * It also ensures that the
     * {@link #startBackgroundPreloading(Dimension) background preloading thread}
     * is started.
     *  
     * @param windowSize
     */
    public static void preload(final Dimension windowSize) {
        if (lastWindowSize != windowSize) {
            dirty = true;
        }
        lastWindowSize = windowSize;
        updateIfDirty(); // starts: startBackgroundPreloading
        getImage("CanvasBackgroundImage", windowSize);
        getImage("TitleImage");
        getImage("BackgroundImage");
    }
    
    /**
     * Starts background preloading of resources.
     * @param windowSize The window size to use when scaling
     *      full screen size images.
     */
    public static void startBackgroundPreloading(final Dimension windowSize) {
        lastWindowSize = windowSize;
        if (dirty) {
            updateIfDirty();
            return; // startBackgroundPreloading will be called from update
        }
        preloadThread = new Thread(FreeCol.CLIENT_THREAD+"Resource loader") {
            public void run() {
                if (windowSize != null) {
                    getImage("EuropeBackgroundImage", windowSize);
                    getImage("CanvasBackgroundImage", windowSize);
                }
                for (String key : mergedContainer.getResources().keySet()) {
                    if (preloadThread != this) {
                        return;
                    }
                    getImage(key);
                    getSimpleZippedAnimation(key);
                }
            }
        };
        preloadThread.setPriority(2);
        preloadThread.start();
    }
    
    /**
     * Updates the resource mappings after making changes.
     */
    private static void updateIfDirty() {
        if (dirty) {
            dirty = false;
            preloadThread = null;
            createMergedContainer();
            startBackgroundPreloading(lastWindowSize);
        }
    }

    /**
     * Creates a merged container for easy access to resources.
     */
    private static void createMergedContainer() {
        ResourceMapping _mergedContainer = new ResourceMapping();
        _mergedContainer.addAll(baseMapping);
        _mergedContainer.addAll(tcMapping);
        _mergedContainer.addAll(campaignMapping);
        _mergedContainer.addAll(scenarioMapping);
        ListIterator<ResourceMapping> it = modMappings.listIterator(modMappings.size()); 
        while (it.hasPrevious()) {
            _mergedContainer.addAll(it.previous());
        }
        mergedContainer = _mergedContainer;
    }
    
    /**
     * Returns the resource of the given type.
     * @param <T> The type of the resource to get.
     * @param resourceId The resource to get.
     * @param type The type of the resource to get.
     * @return The resource if there is one with the given
     *      resourceId and type, or else <code>null</code>.
     */
    private static <T> T getResource(final String resourceId, final Class<T> type) {
        final Resource r = mergedContainer.get(resourceId);
        if (type.isInstance(r)) {
            return type.cast(r);
        } else {
            return null;
        }
    }

    /**
     * Returns the animation specified by the given name.
     * 
     * @param resource The name of the resource to return.
     * @return The animation identified by <code>resource</code>
     *      or <code>null</code> if there is no animation
     *      identified by that name.
     */
    public static SimpleZippedAnimation getSimpleZippedAnimation(final String resource) {
        updateIfDirty();
        final SZAResource r = getResource(resource, SZAResource.class);
        return (r != null) ? r.getSimpleZippedAnimation() : null;
    }
    
    /**
     * Gets the <code>Video</code> represented by the given resource.
     * @return The <code>Video</code> in it's original size.
     */
    public static Video getVideo(final String resource) {
        updateIfDirty();
        final VideoResource r = getResource(resource, VideoResource.class);
        return (r != null) ? r.getVideo() : null;
    }
    
    /**
     * Returns the animation specified by the given name.
     * 
     * @param resource The name of the resource to return.
     * @param scale The size of the requested animation (with 1
     *      being normal size, 2 twice the size, 0.5 half the
     *      size etc). Rescaling will be performed unless using 1.
     * @return The animation identified by <code>resource</code>
     *      or <code>null</code> if there is no animation
     *      identified by that name.
     */
    public static SimpleZippedAnimation getSimpleZippedAnimation(final String resource, final double scale) {
        updateIfDirty();
        final SZAResource r = getResource(resource, SZAResource.class);
        return (r != null) ? r.getSimpleZippedAnimation(scale) : null;
    }

    /**
     * Returns the image specified by the given name.
     * 
     * @param resource The name of the resource to return.
     * @return The image identified by <code>resource</code>
     *      or <code>null</code> if there is no image
     *      identified by that name.
     */
    public static Image getImage(final String resource) {
        updateIfDirty();
        final ImageResource r = getResource(resource, ImageResource.class);
        return (r != null) ? r.getImage() : null;
    }
    
    /**
     * Returns the image specified by the given name.
     * 
     * @param resource The name of the resource to return.
     * @param scale The size of the requested image (with 1 being normal size,
     *      2 twice the size, 0.5 half the size etc). Rescaling
     *      will be performed unless using 1.
     * @return The image identified by <code>resource</code>
     *      or <code>null</code> if there is no image
     *      identified by that name.
     */
    public static Image getImage(final String resource, final double scale) {
        updateIfDirty();
        final ImageResource r = getResource(resource, ImageResource.class);
        return (r != null) ? r.getImage(scale) : null;
    }
    
    /**
     * Returns the image specified by the given name.
     * 
     * @param resource The name of the resource to return.
     * @param size The size of the requested image. Rescaling
     *      will be performed if necessary.
     * @return The image identified by <code>resource</code>
     *      or <code>null</code> if there is no image
     *      identified by that name.
     */
    public static Image getImage(final String resource, final Dimension size) {
        updateIfDirty();
        final ImageResource r = getResource(resource, ImageResource.class);
        return (r != null) ? r.getImage(size) : null;
    }
    
    /**
     * Returns the a grayscale version of the image specified by
     * the given name.
     * 
     * @param resource The name of the resource to return.
     * @param size The size of the requested image. Rescaling
     *      will be performed if necessary.
     * @return The image identified by <code>resource</code>
     *      or <code>null</code> if there is no image
     *      identified by that name.
     */
    public static Image getGrayscaleImage(final String resource, final Dimension size) {
        updateIfDirty();
        final ImageResource r = getResource(resource, ImageResource.class);
        return (r != null) ? r.getGrayscaleImage(size) : null;
    }
    
    /**
     * Returns the grayscale version of the image specified by the given name.
     * 
     * @param resource The name of the resource to return.
     * @param scale The size of the requested image (with 1 being normal size,
     *      2 twice the size, 0.5 half the size etc). Rescaling
     *      will be performed unless using 1.
     * @return The image identified by <code>resource</code>
     *      or <code>null</code> if there is no image
     *      identified by that name.
     */
    public static Image getGrayscaleImage(final String resource, final double scale) {
        updateIfDirty();
        final ImageResource r = getResource(resource, ImageResource.class);
        return (r != null) ? r.getGrayscaleImage(scale) : null;
    }

    /**
     * Creates an <code>ImageIcon</code> for the image of
     * the given name.
     * 
     * @param resource The name of the resource to return.
     * @return An <code>ImageIcon</code> created with the image
     *      identified by <code>resource</code> or
     *      <code>null</code> if there is no image identified
     *      by that name.
     * @see #getImage(String)
     */
    public static ImageIcon getImageIcon(final String resource) {
        updateIfDirty();
        final Image im = getImage(resource);
        return (im != null) ? new ImageIcon(im) : null;
    }
}