/*
 * @(#)Iconkit.java 5.1
 *
 */

package CH.ifa.draw.util;

import java.awt.Component;
import java.awt.Frame;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.image.ImageProducer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import de.renew.draw.ui.api.ImageApi;
import de.renew.plugin.PluginManager;


/**
 * The Iconkit class supports the sharing of images. It maintains
 * a map of image names and their corresponding images.
 * <br>
 * Iconkit also supports to load a collection of images in
 * synchronized way.
 * The resolution of a path name to an image is delegated to the DrawingEditor.
 * <hr>
 * <b>Design Patterns</b><P>
 * <img src="images/red-ball-small.gif" width=6 height=6 alt=" o ">
 * <b><a href=../pattlets/sld031.htm>Singleton</a></b><br>
 * The Iconkit is a singleton.
 * <hr>
 * @deprecated This class is not to be used externally and will later be hidden.
 */
@Deprecated(since = "5.0", forRemoval = true)
public class Iconkit {
    /**
     * Logger for the class Iconkit.
     * Only a single logger instance exists at any given time. If a logger for Iconkit
     * already exists it is returned or else a new instance is created.
     * @deprecated This field is not to be used externally and will later be hidden.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(Iconkit.class);
    private final static int ID = 123;
    private static Iconkit _instance = null;
    private final Hashtable<String, Image> _images;
    private final Vector<String> _registeredImages;
    private final MediaTracker _tracker;

    /**
     * Constructs an Iconkit that uses the given editor to
     * resolve image path names.
     *
     * @param component the component the images will be displayed in
     */
    public Iconkit(Component component) {
        _images = new Hashtable<>(53);
        _registeredImages = new Vector<>(10);
        _tracker = new MediaTracker(component);
        _instance = this;
    }

    /**
     * Gets the singleton instance.
     *
     * @return the current Iconkit instance, if an instance already exists it is returned
     * or else a new instance is created
     * @deprecated This method is not to be used externally and will later be hidden.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public static synchronized Iconkit instance() {
        // Workaround: if the Draw Plugin is not initialized yet,
        // we do not have an instance of Iconkit. This fixes this issue.
        if (_instance == null) {
            return new Iconkit(new Frame());
        }

        return _instance;
    }

    /**
     * Registers an image that is then loaded together with
     * the other registered images by loadRegisteredImages.
     *
     * @param fileName the name of the image file to register
     * {@link #loadRegisteredImages}
     * @deprecated This method is not to be used externally. Please use the method {@link ImageApi#loadImage(String)} instead.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public void registerImage(String fileName) {
        _registeredImages.addElement(fileName);
    }

    /**
     * Registers and loads an image.
     *
     * @param fileName the filename where the image will be loaded from
     * @return the now loaded and registered image
     * @deprecated This method is not to be used externally. Please use the method {@link ImageApi#registerAndLoadImage(String)} instead.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public Image registerAndLoadImage(Component ignored, String fileName) {
        registerImage(fileName);
        loadRegisteredImages();
        return getImage(fileName);
    }

    /**
     * Loads an image with the given name.
     *
     * @param filename the filename where the image will be loaded from
     * @return the now loaded image
     * @deprecated This method is not to be used externally. Please use the method {@link ImageApi#loadImage(String)} instead.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public Image loadImage(String filename) {
        if (_images.containsKey(filename)) {
            return _images.get(filename);
        }

        Image image = loadImageResource(filename);
        if (image != null) {
            _images.put(filename, image);
        }
        return image;
    }

    /**
     * Gets the image with the given name.
     * If the image can't be found it tries again after loading all the registered images.
     *
     * @param filename the filename, which will be used as a key to access the image
     * @return the requested Image object
     * @deprecated This method is not to be used externally. Please use the method {@link ImageApi#getImage(String)} instead.
     */
    @Deprecated(since = "5.0", forRemoval = true)
    public Image getImage(String filename) {
        if (_images.containsKey(filename)) {
            return _images.get(filename);
        }

        // Load registered images and try again
        loadRegisteredImages();

        // try again
        return _images.get(filename);
    }

    /**
     * Loads an image by its resource name.
     *
     * @param resourceName The name of the image resource.
     * @return An image instance, or <code>null</code>.
     */
    private Image loadImageResource(String resourceName) {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        try {
            java.net.URL url =
                PluginManager.getInstance().getBottomClassLoader().getResource(resourceName);
            LOGGER.debug(resourceName + "(" + url + ")");

            // If url is null: use resource name!
            if (url == null) {
                return toolkit.getImage(resourceName);
            }

            // Create an image from an image producer.
            Object content = url.getContent();
            if (content instanceof ImageProducer) {
                return toolkit.createImage((ImageProducer) content);
            }

            // Load the image from an input stream.
            if (content instanceof InputStream stream) {
                return loadImageInputStream(toolkit, stream);
            }

            // Since we have no useful information available,
            // we try to go the more resource intensive route.
            return toolkit.getImage(url);
        } catch (Exception ex) {
            LOGGER.error("While loading " + resourceName + ":");
            LOGGER.error(ex.getMessage(), ex);
            return null;
        }
    }

    private Image loadImageInputStream(Toolkit toolkit, InputStream stream) throws IOException {
        int max = 1024;
        if (stream.available() > max) {
            max = stream.available();
        }
        byte[] data = new byte[max];
        int pos = 0;
        boolean canReadMore;
        do {
            if (pos > max / 2) {
                max = max * 2;
                byte[] newData = new byte[max];
                System.arraycopy(data, 0, newData, 0, pos);
                data = newData;
            }
            int num = stream.read(data, pos, max - pos);
            canReadMore = num > 0;
            if (canReadMore) {
                pos = pos + num;
            }
        } while (canReadMore);
        stream.close();

        // Create the image.
        return toolkit.createImage(data, 0, pos);
    }

    /**
     * Loads all registered images.
     * {@link #registerImage}
     */
    private void loadRegisteredImages() {
        if (_registeredImages.isEmpty()) {
            return;
        }


        // register images with MediaTracker
        Enumeration<String> k = _registeredImages.elements();
        while (k.hasMoreElements()) {
            String fileName = k.nextElement();
            if (_images.get(fileName) == null) {
                _tracker.addImage(loadImage(fileName), ID);
            }
        }
        _registeredImages.removeAllElements();

        // block until all images are loaded
        try {
            _tracker.waitForAll();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}