/*

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */
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.
 *
 * @version $Id$
 */
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 static final 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 {
            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 purl URL of 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;
                    try {
                        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));
                            }
                        }
                    } catch (ThreadDeath td) {
                        filt = ImageTagRegistry.getBrokenLinkImage
                            (JDKRegistryEntry.this, errCode, errParam);
                        dr.setSource(filt);
                        throw td;
                    } catch (Throwable t) { }
                    if (filt == null)
                        filt = ImageTagRegistry.getBrokenLinkImage
                            (JDKRegistryEntry.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 static 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...
                }
            }
        }
    }

}
