/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.ext.awt.image.spi;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.ext.awt.image.renderable.DeferRable;
import org.apache.batik.ext.awt.image.renderable.Filter;
import org.apache.batik.ext.awt.image.renderable.RedRable;
import org.apache.batik.util.ParsedURL;

/**
 * This Image tag registy entry is setup to wrap the core JDK
 * Image stream tools.  
 */
public class JDKRegistryEntry extends AbstractRegistryEntry 
    implements URLRegistryEntry {

    /**
     * The priority of this entry.
     * This entry should in most cases be the last entry.
     * but if one wishes one could set a priority higher and be called
     * afterwords
     */
    public final static float PRIORITY = 
        1000*MagicNumberRegistryEntry.PRIORITY;

    public JDKRegistryEntry() {
        super ("JDK", PRIORITY, new String[0], new String [] {"image/gif"});
    }

    /**
     * Check if the Stream references an image that can be handled by
     * this format handler.  The input stream passed in should be
     * assumed to support mark and reset.
     *
     * If this method throws a StreamCorruptedException then the
     * InputStream will be closed and a new one opened (if possible).
     *
     * This method should only throw a StreamCorruptedException if it
     * is unable to restore the state of the InputStream
     * (i.e. mark/reset fails basically).  
     */
    public boolean isCompatibleURL(ParsedURL purl) {
        try {
            URL url = new URL(purl.toString());
        } catch (MalformedURLException mue) {
            // No sense in trying it if we can't build a URL out of it.
            return false;
        }
        return true;
    }

    /**
     * Decode the URL into a RenderableImage
     *
     * @param is The input stream that contains the image.
     * @param needRawData If true the image returned should not have
     *                    any default color correction the file may 
     *                    specify applied.  
     */
    public Filter handleURL(ParsedURL purl, boolean needRawData) {
        
        final URL url;
        try {
            url = new URL(purl.toString());
        } catch (MalformedURLException mue) {
            return null;
        }

        final DeferRable  dr  = new DeferRable();
        final String      errCode;
        final Object []   errParam;
        if (purl != null) {
            errCode  = ERR_URL_FORMAT_UNREADABLE;
            errParam = new Object[] {"JDK", url};
        } else {
            errCode  = ERR_STREAM_FORMAT_UNREADABLE;
            errParam = new Object[] {"JDK"};
        }

        Thread t = new Thread() {
                public void run() {
                    Filter filt = null;

                    Toolkit tk = Toolkit.getDefaultToolkit();
                    Image img = tk.createImage(url);

                    if (img != null) {
                        RenderedImage ri = loadImage(img, dr);
                        if (ri != null) {
                            filt = new RedRable(GraphicsUtil.wrap(ri));
                        }
                    }

                    if (filt == null)
                        filt = ImageTagRegistry.getBrokenLinkImage
                            (this, errCode, errParam);
                    
                    dr.setSource(filt);
                }
            };
        t.start();
        return dr;
    }

    // Stuff for Image Loading.
    public RenderedImage loadImage(Image img, final DeferRable  dr) {
        // In some cases the image will be a
        // BufferedImage (subclass of RenderedImage).
        if (img instanceof RenderedImage)
            return (RenderedImage)img;

        MyImgObs observer = new MyImgObs();
        Toolkit.getDefaultToolkit().prepareImage(img, -1, -1, observer);
        observer.waitTilWidthHeightDone();
        if (observer.imageError)
            return null;
        int width  = observer.width;
        int height = observer.height;
        dr.setBounds(new Rectangle2D.Double(0, 0, width, height));

        // Build the image to draw into.
        BufferedImage bi = new BufferedImage
            (width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = bi.createGraphics();
        
        // Wait till the image is fully loaded.
        observer.waitTilImageDone();
        if (observer.imageError)
            return null;
        dr.setProperties(new HashMap());

        g2d.drawImage(img, 0, 0, null);
        g2d.dispose();

        return bi;
    }


    public class MyImgObs implements ImageObserver {
        boolean widthDone = false;
        boolean heightDone = false;
        boolean imageDone = false;
        int width = -1;
        int height = -1;
        boolean imageError = false;

        int IMG_BITS = ALLBITS|ERROR|ABORT;

        public void clear() {
            width=-1;
            height=-1;
            widthDone = false;
            heightDone = false;
            imageDone       = false;
        }

        public boolean imageUpdate(Image img, int infoflags, 
                                   int x, int y, int width, int height) {
            synchronized (this) {
                boolean notify = false;

                if ((infoflags & WIDTH)   != 0) this.width  = width;
                if ((infoflags & HEIGHT)  != 0) this.height = height;

                if ((infoflags & ALLBITS) != 0) {
                    this.width  = width;
                    this.height = height;
                }

                if ((infoflags & IMG_BITS) != 0) {
                    if ((!widthDone) || (!heightDone) || (!imageDone)) {
                        widthDone  = true;
                        heightDone = true;
                        imageDone  = true;
                        notify     = true;
                    }
                    if ((infoflags & ERROR) != 0) {
                        imageError = true;
                    }
                }


                if ((!widthDone) && (this.width != -1)) {
                    notify = true;
                    widthDone = true;
                }
                if ((!heightDone) && (this.height != -1)) {
                    notify = true;
                    heightDone = true;
                }

                if (notify)
                    notifyAll();
            }
            return true;
        }

        public synchronized void waitTilWidthHeightDone() {
            while ((!widthDone) || (!heightDone)) {
                try {
                    // Wait for someone to set xxxDone
                    wait();
                }
                catch(InterruptedException ie) { 
                    // Loop around again see if src is set now...
                }
            }
        }
        public synchronized void waitTilWidthDone() {
            while (!widthDone) {
                try {
                    // Wait for someone to set xxxDone
                    wait();
                }
                catch(InterruptedException ie) { 
                    // Loop around again see if src is set now...
                }
            }
        }
        public synchronized void waitTilHeightDone() {
            while (!heightDone) {
                try {
                    // Wait for someone to set xxxDone
                    wait();
                }
                catch(InterruptedException ie) { 
                    // Loop around again see if src is set now...
                }
            }
        }

        public synchronized void waitTilImageDone() {
            while (!imageDone) {
                try {
                    // Wait for someone to set xxxDone
                    wait();
                }
                catch(InterruptedException ie) { 
                    // Loop around again see if src is set now...
                }
            }
        }
    }

}
