/******************************************************************************
 * Copyright (c) 2004, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IBM Corporation - initial API and implementation 
 ****************************************************************************/

package org.eclipse.gmf.runtime.draw2d.ui.render.factory;

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.zip.Adler32;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Platform;
import org.eclipse.gmf.runtime.draw2d.ui.render.RenderInfo;
import org.eclipse.gmf.runtime.draw2d.ui.render.RenderedImage;
import org.eclipse.gmf.runtime.draw2d.ui.render.internal.AbstractRenderedImage;
import org.eclipse.gmf.runtime.draw2d.ui.render.internal.factory.RenderedImageKey;
import org.eclipse.gmf.runtime.draw2d.ui.render.internal.image.ImageRenderedImage;
import org.eclipse.swt.graphics.RGB;
//import org.eclipse.gmf.runtime.common.core.util.Trace;

/**
 * @author sshaw
 * 
 * Factory class for generating RenderedImage objects
 */
public class RenderedImageFactory {

    static private Map instanceMap = new WeakHashMap();

    /**
     * createInfo static Utility to create a RenderInfo object.
     * 
     * @param width
     *            the width of the rendered image to set
     * @param height
     *            the height of the rendered image to set
     * @param maintainAspectRatio
     *            <code>boolean</code> <code>true</code> if aspect ratio of
     *            original vector file is maintained, <code>false</code>
     *            otherwise
     * @param antialias
     *            <code>boolean</code> <code>true</code> if the image is to
     *            be rendered using anti-aliasing (removing "jaggies" producing
     *            smoother lines), <code>false</code> otherwise
     * @param fill
     *            the <code>RGB</code> of the fill that could instrumented
     *            into image formats that support dynamic color replacement.
     *            Typically, this would replace colors in the image which are
     *            "white" i.e. RGB(255,255,255)
     * @param outline
     *            the <code>RGB</code> of the outline that could
     *            instrumented into image formats that support dynamic color
     *            replacement. Typically, this would replace colors in the image
     *            which are "black" i.e. RGB(0,0,0)
     * @return <code>RenderInfo</code> object that contains information about
     *         the rendered image.
     */
    static public RenderInfo createInfo(int width, int height, boolean maintainAspectRatio, boolean antialias,
            RGB fill, RGB outline ) {
        RenderedImageKey svgInfo = new RenderedImageKey();
        svgInfo.setValues(width, height, maintainAspectRatio,
            antialias, fill, outline);
        return svgInfo;
    }
    
    /**
     * getInstance static constructor method for retrieving the appropriate
     * instance of the immutable class <code>RenderedImage</code>. This
     * method is used to read svg images from JARs.
     * 
     * @param theURL
     *            URL of the SVG image. Normally in a JAR
     * @return <code>RenderedImage</code> instance with the size dimensions
     *         requested.
     */
    static public RenderedImage getInstance(URL theURL) {
        return getInstance(theURL, new RenderedImageKey());
    }

    /**
     * getInstance static constructor method for retrieving the appropriate
     * instance of the immutable class <code>RenderedImage</code>. This
     * method is used to read svg images from JARs.
     * 
     * @param theURL
     *            URL of the SVG image.
     * @param info
     *            object containing information about the size and general data
     *            regarding how the image will be rendered.
     * 
     * @return <code>RenderedImage</code> instance with the size dimensions
     *         requested.
     */
    static public RenderedImage getInstance(URL theURL, RenderInfo info) {

        try {
            InputStream is = theURL.openStream();

            int size = is.available();
            byte[] buffer = new byte[size];

            is.read(buffer);
            is.close();

            return getInstance(buffer, info, theURL.toString());

        } catch (Exception e) {
//            Trace.throwing(Draw2dRenderPlugin.getInstance(),
//                Draw2dRenderDebugOptions.EXCEPTIONS_THROWING,
//                RenderedImage.class, "RenderedImageFactory.getInstance()", //$NON-NLS-1$
//                e);
        }

        return null;
    }
    
    /**
     * getInstance static constructor method for retrieving the appropriate
     * instance of the immutable class <code>RenderedImage</code>.
     * 
     * @param buffer
     *            byte[] array containing an cached SVG image file.
     * @param info
     *            object containing information about the size and general data
     *            regarding how the image will be rendered.
     * @param url the url of the image (filename url - should be there for SVG, since it may have external references)
     * @return <code>RenderedImage</code> instance with the size dimensions
     *         requested.
     * @since 2.1
     */
    public static RenderedImage getInstance(byte [] buffer, RenderInfo info, String url) {
        Adler32 checksum = new Adler32();
        checksum.update(buffer);
        final RenderedImageKey key = new RenderedImageKey(info, checksum.getValue(), null, url);
        WeakReference ref = (WeakReference) instanceMap.get(key);
        RenderedImage image = null;
        if (ref != null)
            image = (RenderedImage) (((WeakReference) instanceMap.get(key))
                .get());
        else
            image = autodetectImage(buffer, key);

		// Bugzilla 208374
		if (image == null) {
			// Remove entry holding null reference.
			instanceMap.remove(key);
			// Recreate entry using buffer.
			image = getInstance(buffer);
		}

        return image;
    	
    }

    /**
     * getInstance static constructor method for retrieving the appropriate
     * instance of the immutable class <code>RenderedImage</code>.
     * 
     * @param szFilePath
     *            <code>String</code> file path of svg file
     * @return <code>RenderedImage</code> instance with the size dimensions
     *         requested.
     */
    static public RenderedImage getInstance(String szFilePath) {
        return getInstance(szFilePath, new RenderedImageKey());
    }

    /**
     * getInstance static constructor method for retrieving the appropriate
     * instance of the immutable class <code>RenderedImage</code>.
     * 
     * @param szFilePath
     *            <code>String</code> file path of svg file
     * @param info
     *            <code>RenderInfo</code> object containing information about
     *            the size and general data regarding how the image will be
     *            rendered.
     * @return <code>RenderedImage</code> instance with the size dimensions
     *         requested.
     */
    static public RenderedImage getInstance(String szFilePath, RenderInfo info) {
        try {
            FileInputStream fis = new FileInputStream(szFilePath);
            int size = fis.available();
            byte[] buffer = new byte[size];

            fis.read(buffer);
            fis.close();

            return getInstance(buffer, info, szFilePath);

        } catch (Exception e) {
//            Trace.throwing(Draw2dRenderPlugin.getInstance(),
//                Draw2dRenderDebugOptions.EXCEPTIONS_THROWING,
//                RenderedImageFactory.class,
//                "RenderedImageFactory.getInstance()", //$NON-NLS-1$
//                e);
        }

        return null;
    }

    /**
     * getInstance static constructor method for retrieving the appropriate
     * instance of the immutable class <code>RenderedImage</code>.
     * 
     * @param buffer
     *            <code>byte[]</code> array containing an cached SVG image
     *            file.
     * @return the <code>RenderedImage</code> that encapsulates the contents
     *         of the given byte buffer.
     */
    static public RenderedImage getInstance(byte[] buffer) {
        return getInstance(buffer, new RenderedImageKey());
    }

    /**
     * Returns a related instance of the given <code>RenderedImage</code> that
     * is based on the same byte stream or file info but instrumented for the
     * passed in <code>RenderInfo</code> object
     * 
     * @param image
     *            <code>RenderedImage</code> that is used as a base to
     *            retrieve the related instance.
     * @param info
     *            <code>RenderInfo</code> object containing information about
     *            the size and general data regarding how the image will be
     *            rendered.
     * @return <code>RenderedImage</code> instance with the size dimensions
     *         requested. May return <code>null</code> if no related instance
     *         can be found or if the original buffer cannot be retrieved.
     */
    static public RenderedImage getRelatedInstance(RenderedImage image,
            RenderInfo info) {
        if (image instanceof AbstractRenderedImage) {
            RenderedImageKey oldKey = ((AbstractRenderedImage) image).getKey();
            
            Object extraData = oldKey.getExtraData();
            if (info.getBackgroundColor() != null
                && !info.getBackgroundColor().equals(
                    oldKey.getBackgroundColor())) {
                extraData = null;
            } else if (info.getForegroundColor() != null
                && !info.getForegroundColor().equals(
                    oldKey.getForegroundColor())) {
                extraData = null;
            }
            
            RenderedImageKey key = new RenderedImageKey(info, oldKey.getChecksum(), extraData);
            WeakReference ref = (WeakReference) instanceMap.get(key);
            if (ref != null) {
                return (RenderedImage) ref.get();
            } else {
                return autodetectImage(((AbstractRenderedImage) image)
                    .getBuffer(), key);
            }
        }

        return null;
    }

    /**
     * getInstance static constructor method for retrieving the appropriate
     * instance of the immutable class <code>RenderedImage</code>.
     * 
     * @param buffer
     *            byte[] array containing an cached SVG image file.
     * @param info
     *            object containing information about the size and general data
     *            regarding how the image will be rendered.
     * @return <code>RenderedImage</code> instance with the size dimensions
     *         requested.
     */
    static public RenderedImage getInstance(byte[] buffer, RenderInfo info) {
        if (buffer == null)
            throw new InvalidParameterException();

        Adler32 checksum = new Adler32();
        checksum.update(buffer);
        final RenderedImageKey key = new RenderedImageKey(info, checksum.getValue(), null);
        WeakReference ref = (WeakReference) instanceMap.get(key);
        RenderedImage image = null;
        if (ref != null)
            image = (RenderedImage) (((WeakReference) instanceMap.get(key))
                .get());
        else
            image = autodetectImage(buffer, key);

		// Bugzilla 208374
		if (image == null) {
			// Remove entry holding null reference.
			instanceMap.remove(key);
			// Recreate entry using buffer.
			image = getInstance(buffer);
		}

        return image;
    }

    private static final String E_MODIFIER_FACTORY = "factory"; //$NON-NLS-1$
    private static final String A_CLASS = "class"; //$NON-NLS-1$

    static private List imageTypes = null;
    
    static private RenderedImage autodetectImage(byte[] buffer,
            final RenderedImageKey key) {
        
        if (imageTypes == null) {
            imageTypes = new ArrayList();
            
            IExtensionPoint riExtensionPt = Platform.getExtensionRegistry().getExtensionPoint("org.eclipse.gmf.runtime.draw2d.ui.render", //$NON-NLS-1$
                                                            "renderedImageFactory");  //$NON-NLS-1$
            IConfigurationElement[] configEls = riExtensionPt.getConfigurationElements();
            for (int i = 0; i < configEls.length; i++) {
                IConfigurationElement element = configEls[i];
    
                if (element.getName().equals(E_MODIFIER_FACTORY)) {
                    RenderedImageType imageType = null;
                    try {
                        imageType = (RenderedImageType)element.createExecutableExtension(A_CLASS);
                        if (imageType != null)
                            imageTypes.add(imageType);
                    } catch (CoreException e) {
                        continue;
                    }
                }
            }
        }
        
        RenderedImage image = null;
        ListIterator li = imageTypes.listIterator();
        while (li.hasNext()) {
            RenderedImageType imageType = (RenderedImageType)li.next();
            image = imageType.autoDetect(buffer, key);
            if (image != null)
                break;
        }

        if (image == null) {
            // can't create a RenderedImageType for image files until bugzilla 116227 is resolved.  Until then,
            // assume, the fall through type is ImageRenderedImage.
            image = new ImageRenderedImage(buffer, key);
        }
        
        if (image != null) {
            instanceMap.put(key, new WeakReference(image));
        }
        
        return image;
    }
}
