/*
 * MegaMek - Copyright (C) 2004 Ben Mazur (bmazur@sev.org)
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 */

package megamek.common.util;

import java.util.TreeMap;
import java.util.Vector;
import java.io.File;
import java.util.Enumeration;
import megamek.common.util.Collections;
import megamek.common.util.StringUtil;

/**
 * This class represents a collection of files present within a directory
 * hierarchy, categorized according to their directories.  This collection
 * will include all files inside of JAR and ZIP files that are located in
 * the directory hierarchy.
 *
 * Created on January 17, 2004
 *
 * @author  James Damour
 * @version 1
 */
public class DirectoryItems implements Categorized {

    /**
     * The root category name.
     */
    private String rootName;

    /**
     * A map of the category names to the sub-categories.
     * Please note that this map includes the root category,
     * if the root category contains any items. 
    */
    private TreeMap categories = new TreeMap( StringUtil.stringComparator() );

    /**
     * A map of item names to the <code>ItemFile</code>s in the root category.
     */
    private TreeMap items = new TreeMap( StringUtil.stringComparator() );

    /**
     * The factory that will create <code>ItemFile</code>s for the
     * contents of the directory hierarchy.
     */
    private ItemFileFactory factory = null;

    /**
     * Helper function to file away new categories.  It adds one entry
     * in the map for each sub-category in the passed category.
     *
     * @param   category - the <code>Categorized</code> files.
     */
    private void addCategory( Categorized category ) {
        Enumeration names = category.getCategoryNames();
        while ( names.hasMoreElements() ) {
            categories.put( names.nextElement(), category );
        }
    }

    /**
     * Create a categorized collection of all files beneth the given directory.
     * Please note, the name of any sub-directories will be added to the root
     * category name to create the name of the sub-directories' category name.
     *
     * @param   rootDir - the <code>File</code> object for the root directory
     *          of the image files.  All files in this root, or in any sub-
     *          directory of this root will be included in this collection.
     *          This value must not be <code>null</code> and it must be a
     *          directory.
     * @param   categoryName - the <code>String</code> root category name for
     *          this collection.  All sub-categories will include this name.
     *          This value may be <code>null</code> (it will be replaced).
     * @param   itemFactory - the <code>ItemFileFactory</code> that will create
     *          <code>ItemFile</code>s for the contents of the directory.
     *          This value must not be <code>null</code>.
     * @throws  <code>IllegalArgumentException</code> if <code>rootDir</code>
     *          is null or if it is not a directory, or if a <code>null</code>
     *          is passed for <code>itemFactory</code>.
     */
    public DirectoryItems( File rootDir, String categoryName,
                           ItemFileFactory itemFactory )
        throws IllegalArgumentException {

        // Validate input.
        if ( null == rootDir ) {
            throw new IllegalArgumentException
                ( "A null root directory was passed." ); //$NON-NLS-1$
        }
        else if ( !rootDir.isDirectory() ) {
            throw new IllegalArgumentException
                ( "The passed file is not a directory." ); //$NON-NLS-1$
        }
        if ( null == itemFactory ) {
            throw new IllegalArgumentException
                ( "A null item factory was passed." ); //$NON-NLS-1$
        }

        // Save the root category name and the item factory.
        rootName = categoryName;
        factory = itemFactory;

        // Replace a null name with an empty name.
        if ( null == rootName ) {
            rootName = ""; //$NON-NLS-1$
        }

        /* DEBUG : uncomment this section to debug *
        System.out.print( "Loading items from ");
        System.out.println( rootDir.getPath() );
        /* DEBUG : uncomment this section to debug */

        // Walk through the contents of the root directory.
        String[] contents = rootDir.list();
        for ( int entry = 0; entry < contents.length; entry++ ) {

            // Get the entry's file.
            File file = new File( rootDir, contents[entry] );

            // Is this entry a sub-directory?
            if ( file.isDirectory() ) {

                // Construct the category name for this sub-directory.
                StringBuffer name = new StringBuffer();
                name.append( rootName )
                    .append( contents[entry] )
                    .append( "/" ); //$NON-NLS-1$

                // Parse the sub-directory, and add it to the map.
                this.addCategory
                    ( new DirectoryItems(file, name.toString(), factory) );

            }

            // Is this entry a ZIP or JAR file?
            else if ( ZippedItems.isZipName( contents[entry] ) ) {

                // Construct the category name for this ZIP file.
                StringBuffer name = new StringBuffer();
                name.append( rootName )
                    .append( contents[entry] );

                // Try to parse the ZIP file, and add it to the map.
                try {
                    this.addCategory
                        ( new ZippedItems(file, name.toString(), factory) );
                }
                catch ( Exception err ) {
                    // Print diagnostics and keep going.
                    System.err.print( "Could not parse "); //$NON-NLS-1$
                    System.err.println( contents[entry] );
                    err.printStackTrace();
                }
            }

            // Does the factory accept this entry?
            else if ( factory.accept(rootDir, contents[entry]) ) {

                // Save the ItemFile for this entry.
                items.put( contents[entry], factory.getItemFile(file) );
            }

            /* DEBUG : uncomment this section to debug *
            else {
                System.out.print( "... ignoring " );
                System.out.println( contents[entry] );
            }
            /* DEBUG : uncomment this section to debug */

        } // Get the next entry in the root directory.

        // If the root directory has any item files, add it to the map.
        if ( !items.isEmpty() ) {
            categories.put( rootName, this );
        }
    }

    /**
     * Get the names of all the categories.
     *
     * @return  an <code>Enumeration</code> of <code>String</code> names.
     *          This value will not be <code>null</code>, but it may be empty.
     */
    public Enumeration getCategoryNames() {
        return Collections.elements( categories.keySet() );
    }

    /**
     * Get the names of all the items in one of the categories.
     *
     * @param   categoryName - the <code>String</code> name of the category
     *          whose item names are required.
     * @return  an <code>Enumeration</code> of <code>String</code> names.
     *          This value will not be <code>null</code>, but it may be empty.
     */
    public Enumeration getItemNames( String categoryName ) {

        // Get the category with the given name.
        Categorized category = (Categorized) categories.get( categoryName );

        // Return an empty Enumeration if we couldn't find the category.
        if ( null == category ) {
            return new Vector().elements();
        }

        // Is this a subcategory?
        if ( this != category ) {

            // Yup.  Pass the request on.
            return category.getItemNames( categoryName );
        }

        // Return the names of this directory's items.
        return Collections.elements( items.keySet() );
    }

    /**
     * Get the indicated item from the correct catagory.
     *
     * @param   categoryName - the <code>String</code> name of the category
     *          whose item names are required.
     *          This value may be <code>null</code>.
     * @param   itemName - the <code>String</code> name of the indicated item.
     * @return  the <code>Object<code> in the given category with the given
     *          name.  This value may be <code>null</code>.
     * @throws  <code>Exception</code> if there's any error getting the item.
     */
    public Object getItem( String categoryName, String itemName )
        throws Exception {

        // Validate input.
        if ( null == categoryName ) {
            throw new IllegalArgumentException
                ( "A null category name was passed." ); //$NON-NLS-1$
        }
        if ( null == itemName ) {
            throw new IllegalArgumentException
                ( "A null item name was passed." ); //$NON-NLS-1$
        }

        // Make sure we have this category?
        if ( !categories.containsKey( categoryName ) ) {
            return null;
        }

        // Get the category with the given name.
        Categorized category = (Categorized) categories.get( categoryName );

        // Is this a subcategory?
        if ( this != category ) {

            // Yup.  Pass the request on.
            return category.getItem( categoryName, itemName );
        }

        // Make sure we have an item by that name.
        if ( !items.containsKey( itemName ) ) {
            return null;
        }

        // Find the named entry.
        ItemFile entry = (ItemFile) items.get( itemName );

        // Return the item.
        return entry.getItem();
    }

}
