/*****************************************************************************
 * 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.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.apache.batik.ext.awt.color.ICCColorSpaceExt;
import org.apache.batik.ext.awt.image.URLImageCache;
import org.apache.batik.ext.awt.image.renderable.Filter;
import org.apache.batik.ext.awt.image.renderable.ProfileRable;
import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.Service;


/**
 * This class handles the registered Image tag handlers.  These are
 * instances of RegisteryEntry in this package.
 *
 * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
 * @version $Id: ImageTagRegistry.java,v 1.14 2003/07/10 02:01:26 deweese Exp $
 */
public class ImageTagRegistry implements ErrorConstants {

    List entries    = new LinkedList();
    List extensions = null;
    List mimeTypes  = null;

    URLImageCache rawCache;
    URLImageCache imgCache;

    public ImageTagRegistry() {
        this(null, null);
    }

    public ImageTagRegistry(URLImageCache rawCache, URLImageCache imgCache) {
        if (rawCache == null)
            rawCache = new URLImageCache();
        if (imgCache == null)
            imgCache = new URLImageCache();

        this.rawCache= rawCache;
        this.imgCache= imgCache;
    }

    public void flushCache() {
        rawCache.flush();
        imgCache.flush();
    }

    public Filter checkCache(ParsedURL purl, ICCColorSpaceExt colorSpace) {
        // I just realized that this whole thing could 
        boolean needRawData = (colorSpace != null);

        Filter      ret        = null;
        URLImageCache cache;
        if (needRawData) cache = rawCache;
        else             cache = imgCache;

        ret = cache.request(purl);
        if (ret == null) {
            cache.clear(purl);
            return null;
        }

        // System.out.println("Image came from cache" + purl);
        if (colorSpace != null)
            ret = new ProfileRable(ret, colorSpace);
        return ret;
    }

    public Filter readURL(ParsedURL purl) {
        return readURL(null, purl, null, true, true);
    }

    public Filter readURL(ParsedURL purl, ICCColorSpaceExt colorSpace) {
        return readURL(null, purl, colorSpace, true, true);
    }

    public Filter readURL(InputStream is, ParsedURL purl, 
                          ICCColorSpaceExt colorSpace,
                          boolean allowOpenStream,
                          boolean returnBrokenLink) {
        if ((is != null) && !is.markSupported())
            // Doesn't support mark so wrap with
            // BufferedInputStream that does.
            is = new BufferedInputStream(is);

        // I just realized that this whole thing could 
        boolean needRawData = (colorSpace != null);

        Filter      ret     = null;
        URLImageCache cache = null;

        if (purl != null) {
            if (needRawData) cache = rawCache;
            else             cache = imgCache;

            ret = cache.request(purl);
            if (ret != null) {
                // System.out.println("Image came from cache" + purl);
                if (colorSpace != null)
                    ret = new ProfileRable(ret, colorSpace);
                return ret;
            }
        }
        // System.out.println("Image didn't come from cache: " + purl);

        boolean     openFailed = false;
        List mimeTypes = getRegisteredMimeTypes();

        Iterator i;
        i = entries.iterator();
        while (i.hasNext()) {
            RegistryEntry re = (RegistryEntry)i.next();

            if (re instanceof URLRegistryEntry) {
                if ((purl == null) || !allowOpenStream) continue;

                URLRegistryEntry ure = (URLRegistryEntry)re;
                if (ure.isCompatibleURL(purl)) {
                    ret = ure.handleURL(purl, needRawData);

                    // Check if we got an image.
                    if (ret != null) break;
                }
                continue;
            } 

            if (re instanceof StreamRegistryEntry) {
                StreamRegistryEntry sre = (StreamRegistryEntry)re;
                // Quick out last time the open didn't work for this
                // URL so don't try again...
                if (openFailed) continue;

                try {
                    if (is == null) {
                        // Haven't opened the stream yet let's try.
                        if ((purl == null) || !allowOpenStream)
                            break;  // No purl nothing we can do...
                        try {
                            is = purl.openStream(mimeTypes.iterator());
                        } catch(IOException ioe) {
                            // Couldn't open the stream, go to next entry.
                            openFailed = true;
                            continue;
                        }

                        if (!is.markSupported())
                            // Doesn't support mark so wrap with
                            // BufferedInputStream that does.
                            is = new BufferedInputStream(is);
                    }

                    if (sre.isCompatibleStream(is)) {
                        ret = sre.handleStream(is, purl, needRawData);
                        if (ret != null) break;
                    }
                } catch (StreamCorruptedException sce) {
                    // Stream is messed up so setup to reopen it..
                    is = null;
                }
                continue;
            }
        }
        
        if (cache != null)
            cache.put(purl, ret);

        if (ret == null) {
            if (!returnBrokenLink)
                return null;
            if (openFailed)
                // Technially it's possible that it's an unknown
                // 'protocol that caused the open to fail but probably
                // it's a bad URL...
                return getBrokenLinkImage(this, ERR_URL_UNREACHABLE,
                                          new Object[] { purl });

            // We were able to get to the data we just couldn't
            // make sense of it...
            return getBrokenLinkImage(this, ERR_URL_UNINTERPRETABLE, 
                                      new Object[] { purl } );
        }

        if (ret.getProperty(BrokenLinkProvider.BROKEN_LINK_PROPERTY) != null) {
            // Don't Return Broken link image unless requested
            return (returnBrokenLink)?ret:null;
        }

        if (colorSpace != null)
            ret = new ProfileRable(ret, colorSpace);

        return ret;
    }
    
    public Filter readStream(InputStream is) {
        return readStream(is, null);
    }

    public Filter readStream(InputStream is, ICCColorSpaceExt colorSpace) {
        if (!is.markSupported())
            // Doesn't support mark so wrap with BufferedInputStream that does.
            is = new BufferedInputStream(is);

        boolean needRawData = (colorSpace != null);

        Filter ret = null;;

        Iterator i = entries.iterator();
        while (i.hasNext()) {
            RegistryEntry re = (RegistryEntry)i.next();
            if (! (re instanceof StreamRegistryEntry))
                continue;
            StreamRegistryEntry sre = (StreamRegistryEntry)re;

            try {
                if (sre.isCompatibleStream(is)) {
                    ret = sre.handleStream(is, null, needRawData);

                    if (ret != null) break;
                }
            } catch (StreamCorruptedException sce) {
                break;
            }
        }

        if (ret == null)
            return getBrokenLinkImage(this, ERR_STREAM_UNREADABLE, null);

        if ((colorSpace != null) &&
            (ret.getProperty(BrokenLinkProvider.BROKEN_LINK_PROPERTY) == null))
            ret = new ProfileRable(ret, colorSpace);

        return ret;
    }

    public synchronized void register(RegistryEntry newRE) {
        float priority = newRE.getPriority();

        ListIterator li;
        li = entries.listIterator();
        while (li.hasNext()) {
            RegistryEntry re = (RegistryEntry)li.next();
            if (re.getPriority() > priority) {
                li.previous();
                li.add(newRE);
                return;
            }
        }
        li.add(newRE);
        extensions = null;
        mimeTypes = null;
    }

    /**
     * Returns a List that contains String of all the extensions that
     * can be handleded by the various registered image format
     * handlers.  
     */
    public synchronized List getRegisteredExtensions() {
        if (extensions != null)
            return extensions;
        
        extensions = new LinkedList();
        Iterator iter = entries.iterator();;
        while(iter.hasNext()) {
            RegistryEntry re = (RegistryEntry)iter.next();
            extensions.addAll(re.getStandardExtensions());
        }
        extensions = Collections.unmodifiableList(extensions);
        return extensions;
    }

    /**
     * Returns a List that contains String of all the mime types that
     * can be handleded by the various registered image format
     * handlers.  
     */
    public synchronized List getRegisteredMimeTypes() {
        if (mimeTypes != null) 
            return mimeTypes;

        mimeTypes = new LinkedList();
        Iterator iter = entries.iterator();;
        while(iter.hasNext()) {
            RegistryEntry re = (RegistryEntry)iter.next();
            mimeTypes.addAll(re.getMimeTypes());
        }
        mimeTypes = Collections.unmodifiableList(mimeTypes);
        return mimeTypes;
    }

    static ImageTagRegistry registry = null;

    public synchronized static ImageTagRegistry getRegistry() { 
        if (registry != null) 
            return registry;
        
        registry = new ImageTagRegistry();

        registry.register(new PNGRegistryEntry());
        registry.register(new TIFFRegistryEntry());
        registry.register(new JPEGRegistryEntry());
        registry.register(new JDKRegistryEntry());

        Iterator iter = Service.providers(RegistryEntry.class);
        while (iter.hasNext()) {
            RegistryEntry re = (RegistryEntry)iter.next();
            // System.out.println("RE: " + re);
            registry.register(re);
        }

        return registry;
    }

    static BrokenLinkProvider defaultProvider 
        = new DefaultBrokenLinkProvider();

    static BrokenLinkProvider brokenLinkProvider = null;

    public synchronized static Filter 
        getBrokenLinkImage(Object base, String code, Object [] params) {
        Filter ret = null;
        if (brokenLinkProvider != null)
            ret = brokenLinkProvider.getBrokenLinkImage(base, code, params);

        if (ret == null)
            ret = defaultProvider.getBrokenLinkImage(base, code, params);

        return ret;
    }


    public synchronized static void 
        setBrokenLinkProvider(BrokenLinkProvider provider) {
        brokenLinkProvider = provider;
    }
}

