/*
 * Copyright 2001-2003, Danger, Inc.  All Rights Reserved.
 * This file is subject to the Danger, Inc. Sample Code License,
 * which is provided in the file SAMPLE_CODE_LICENSE.
 * Copies are also available from http://developer.danger.com/
 */

package com.danger.terminal;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;


// it would be so nice to have python's "yield" keyword right about now. :)

/**
 * Iterate over a subtree of files, breath-first, yielding files that match an
 * optional set of filters.
 */
public class FolderWalker
    implements Iterator
{
    /**
     * Filter files by any arbitrary criteria.
     */
    public static interface Filter
    {
        /**
         * Return <code>true</code> if the given file matches some criteria.
         * Typically this will be a check done on the filename for some
         * particular characteristic ("does it end in <code>'.txt'</code>?").
         * 
         * @param file the file to check
         * @return <code>true</code> if the file is acceptable
         */
        public boolean checkFilter (File file);
    }
    
    
    /**
     * Filter for FolderWalker that only returns files with filename extensions
     * matching the string given.  For example,
     * <code>new ExtensionFilter("mp3")</code>
     * will only return files that have a filename ending in <code>".mp3"</code>.
     */
    public static class ExtensionFilter
    {
        public
        ExtensionFilter (String extension)
        {
            mExtension = "." + extension;
        }
        
        public boolean
        checkFilter (File file)
        {
            return file.getName().endsWith(mExtension);
        }
        
        
        private String mExtension;
    }
    
    
    public
    FolderWalker (File root)
    {
        mFileQueue = new ArrayList();
        mFileQueue.add(root);
        mFilters = new ArrayList();
        mCachedNext = null;
    }
    
    public
    FolderWalker (File root, Filter filter)
    {
        this(root);
        addFilter(filter);
    }

    /**
     * Add another filter to the list of filters applied to this FolderWalker.
     * All filters are applied in the order they were added, using boolean
     * "and" -- that is, only files which return <code>true</code> from all
     * filters are returned by the walker.
     * 
     * @param f the filter to add
     */
    public final void
    addFilter (Filter f)
    {
        mFilters.add(f);
    }
    
    /**
     * Unsupported operation.
     */
    public void
    remove ()
    {
        throw new UnsupportedOperationException();
    }
    
    public Object
    next ()
    {
        if (mCachedNext != null) {
            Object ret = mCachedNext;
            mCachedNext = null;
            return ret;
        }
        
        while (true) {
            if (mFileQueue.isEmpty()) {
                throw new NoSuchElementException();
            }
            
            File f = (File) mFileQueue.remove(0);
            
            if (f.isDirectory()) {
                mFileQueue.addAll(Arrays.asList(f.listFiles()));
                continue;
            }
            
            // if any filters are defined, only return files that match them
            if (! mFilters.isEmpty()) {
                boolean skip = false;
                for (Iterator i = mFilters.iterator(); i.hasNext(); ) {
                    Filter filter = (Filter) i.next();
                    if (! filter.checkFilter(f)) {
                        skip = true;
                        break;
                    }
                }
                if (skip) {
                    continue;
                }
            }
            
            return f;
        }
    }
    
    public boolean
    hasNext ()
    {
        if (mCachedNext != null) {
            return true;
        }
        
        try {
            mCachedNext = next();
            return true;
        } catch (NoSuchElementException x) {
            return false;
        }
    }
    
    
    private List mFileQueue;
    private List mFilters;
    private Object mCachedNext;
}
