package com.ca.commons.cbutil;

import java.awt.*;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Logger;
import java.util.zip.ZipException;

/**
 * This Class provides access to a group of zip files.  In the case of namespace conflicts between
 * Zip files, the first zip file registered has priority.  The zip files are accessed through CBJarResource.
 *
 * @author Chris Betts
 * @see com.ca.commons.cbutil.CBJarResource
 */


public class CBResourceLoader
{
    /**
     * a cached list of 'unknown' resources that have been (unsuccessfully) looked up before.
     */

    protected HashSet unknownResources = new HashSet();

    /**
     * A list of CBJarResources objects that may be searched for packaged resources.
     */

    protected CBJarResource[] resourceFiles = null;

    /**
     * Whether to print out shtuff on the way.
     */

    protected static boolean debug = false;


    private static Logger log = Logger.getLogger(CBResourceLoader.class.getName());

    /**
     * Constructor - note that that the class is useless until at least one resource file has been
     * registered with it using the addResource() method.
     */

    public CBResourceLoader()
    {
        log.fine("Started CBResourceLoader");
    }

    /**
     * Adds a new zip/jar resource file to the list of files to be examined for resources.
     */

    public void addResource(CBJarResource resource)
    {
        int size = (resourceFiles == null) ? 0 : (resourceFiles.length);

        // The clumsiness of using an array is balanced by the need for quick
        // access, and the fact that normally only a small number of resources are added,
        // and only at the start of program operation.

        CBJarResource[] newArray = new CBJarResource[size + 1];
        for (int i = 0; i < size; i++)
            newArray[i] = resourceFiles[i];
        newArray[size] = resource;
        resourceFiles = newArray;

        log.fine("Added CBJarResource: " + resource.toString());
    }

    /**
     * Search all registered zip files for a particular file, and return an input stream to that
     * file.
     */

    public InputStream getInputStream(String resourceName) throws ZipException
    {
        CBJarResource resourceFile = getJarContainingResource(resourceName);
        if (resourceFile != null)
            return resourceFile.getInputStream(resourceName);

        throw new ZipException("File: '" + resourceName + "' not found");
    }

    /**
     * Search all registered zip files for a particular Image, and return an awt Image
     * object.
     */

    public Image getImage(String imageName, Toolkit imageCreator) throws ZipException
    {
        CBJarResource resourceFile = getJarContainingResource(imageName);
        if (resourceFile != null)
            return resourceFile.getImage(imageName, imageCreator);

        throw new ZipException("Image File: '" + imageName + "' not found");
    }

    /**
     * Search all registered zip files for a particular file, and return the data within that
     * file as a byte array.  (This might be used to initialise a String object if the file is
     * a text file, for instance.)
     */

    public byte[] getResource(String resourceName) throws ZipException
    {
        log.finer("  $$ getting resource '" + resourceName + "' from CBResourceLoader");
        CBJarResource resourceFile = getJarContainingResource(resourceName);
        if (resourceFile != null)
        {
            log.finer("  $$ found resource '" + resourceName + "' in zip file '" + resourceFile.getZipFileName() + "' - extracting");
            return resourceFile.getResource(resourceName);
        }
        throw new ZipException("File: '" + resourceName + "' not found");

    }

    public long getLastModified(String resourceName) throws ZipException
    {
        CBJarResource resourceFile = getJarContainingResource(resourceName);
        if (resourceFile != null)
        {
            return resourceFile.getLastModified();
        }
        throw new ZipException("File: '" + resourceName + "' not found");

    }


    /**
     * Searches an internal hash, followed by all Jar files, for a particular resource.
     */
    public CBJarResource getJarContainingResource(String resourceName)
    {
        if (resourceFiles == null) return null;  // we don't have any resources...
    
        // check to see if we've already looked for this resource.
        if (unknownResources.contains(resourceName)) return null;

        for (int i = 0; i < resourceFiles.length; i++)
            if (resourceFiles[i].hasResource(resourceName))
                return resourceFiles[i];

        // nothing found!  Add an entry to the unknownResources hashset so that we don't look for
        // it again.

        unknownResources.add(resourceName);

        return null; // nothing found
    }

    /**
     * Returns all resources with the given prefix.
     *
     * @param prefix a string to match the start of resources against, e.g. 'icons/'
     */

    public String[] getPrefixedResources(String prefix)
    {
        if (resourceFiles == null) return new String[]{};  // don't have anything.

        Vector resources = new Vector();

        // cycle through all resource files, gathering prefixed resources.
        // name clashes are simply included twice :-)
		    	
        for (int i = 0; i < resourceFiles.length; i++)
        {
            resources.addAll(Arrays.asList(resourceFiles[i].getPrefixedResources(prefix)));
        } 
        
        // cast stuff back to string for return.
        if (resources.size() == 0)
        {
            return new String[]{};
        }
        else
        {
            return (String[]) resources.toArray(new String[resources.size()]);
        }
    }

    /**
     * This is a very PRIMATIVE wildcard matching routine - it allows
     * only ONE wildcard, and that wildcard MUST be a '*' character.
     * (So in effect this is simply a prefix + suffix match).
     *
     * @param exp a SIMPLE wildcard expression to match, e.g. 'templates/plain*.html'
     */

/*
XXX Under Development...

    public String[] getWildCardResources(String exp)
    {
        if (resourceFiles == null) return new String[]{};  // don't have anything.to search

        int wildpos = exp.indexOf('*');
        if (wildpos == -1) return new String[]{exp};

        if (wildpos == exp.length() - 1) // i.e. last character
            return getPrefixedResources(exp.substring(0, exp.length() - 1));

        Vector resources = new Vector();

        // cycle through all resource files, gathering prefixed resources.
        // name clashes are simply included twice :-)

        String prefix = exp.substring(0, wildpos);
        String suffix = exp.substring(wildpos + 1);

        for (int i = 0; i < resourceFiles.length; i++)
        {
            resources.addAll(Arrays.asList(resourceFiles[i].getBoundedResources(prefix, suffix)));
        }

        // cast stuff back to string for return.
        if (resources.size() == 0)
        {
            return new String[]{};
        }
        else
        {
            return (String[]) resources.toArray(new String[resources.size()]);
        }
    }
*/
}