/*
 ************************************************************************
 *
 * Copyright (c) 2003-2004, C&C Research Laboratories, NEC Europe Ltd.
 *
 * Copyright in this software belongs to C&C Research Laboratories,
 * Rathausallee 10, 53757 Sankt Augustin, Germany.
 *
 * This software may not be used, sold, licensed, transferred, copied
 * or reproduced in whole or in part in any manner or form or in or
 * on any media by any person other than in accordance with the terms
 * of the Licence Agreement supplied with the software, or otherwise
 * without the prior written consent of the copyright owners.
 *
 * This software is distributed WITHOUT ANY WARRANTY, without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE, except where stated in the Licence Agreement supplied with
 * the software.
 *
 *  Created By :           G.A. Kohring
 *  Created for Project :  GEMSS (IST-2001-37153)
 *
 ************************************************************************
 *
 */
                                                                                
package de.nece.ccrle.util;

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * A hahtable implementation of the <cod>Catalog</code> interface. 
 * (See {@link java.util.HashMap} for details on how the hashing was
 * implemented.)
 *
 * @author  G.A. Kohring
 */
public class HashCatalog extends HashMap implements Catalog {
    //
    // Contructors
    //
    /**
     * Constructs an empty <tt>HashCatalog</tt> with the specified initial
     * capacity and load factor. 
     * (See <tt>java.util.HashMap</tt> for details.)
     *
     * @param  initialCapacity The initial capacity.
     * @param  loadFactor      The load factor.
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive.
     */
    public HashCatalog( int initialCapacity, float loadFactor ) {
        super( initialCapacity, loadFactor );
    }

    /**
     * Constructs an empty <tt>HashCatalog</tt> with the specified initial
     * capacity and the default load factor (0.75).
     * (See <tt>java.util.HashMap</tt> for details.)
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashCatalog( int initialCapacity ) {
        super( initialCapacity );
    }

    /**
     * Constructs an empty <tt>HashCatalog</tt> with the default initial 
     * capacity (16) and the default load factor (0.75).
     * (See <tt>java.util.HashMap</tt> for details.)
     */
    public HashCatalog() {
        super();
    }

    /**
     * Constructs a new <tt>HashCatalog</tt> with the same mappings as the
     * specified <tt>Catalog</tt>.  The <tt>HashCatalog</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Catalog</tt>.
     *
     * @param   catalog the <code>Catalog</code> whose mappings are to 
     *      be placed in this map.
     * @throws  NullPointerException if the specified map is null.
     */
    public HashCatalog(Catalog catalog) {
        super( catalog );
    }



    //
    // Query Operations
    //

    /**
     * Returns the number of category-item mappings in the catalog.
     * If the catalog contains more than 
     * <code>Integer.MAX_VALUE</code> items,
     * returns <code>Integer.MAX_VALUE</code>.
     *
     * @return the number of items in the catalog.
     */
    public int size(){
        int size = 0;
        for ( Iterator i = entrySet().iterator(); i.hasNext(); ){
            Map.Entry me = (Map.Entry) i.next();
            Set category = (Set) me.getValue();
            size += category.size();
        }
        return size;
    }

    /**
     * Returns the number of items in the specified category.
     * If the category contains more than 
     * <code>Integer.MAX_VALUE</code> items,
     * returns <code>Integer.MAX_VALUE</code>.
     *
     * @param category the category whose size is to be returned.
     * @return the number of items in associated
     *      with the category or <code>-1</code> if no such category 
     *      exists in this catalog.
     */
    public int size( Object category ){
        return containsKey( category ) ? getItems( category ).size() : -1;
    }

    public Set categories(){
        return keySet();
    }


    /**
     * Returns <tt>true</tt> if this category represented by the specified
     * category
     * contains no items or if no such category exists in the catalog.
     *
     * @param category the category whose size is to be returned.
     * @return <tt>true</tt> if this category contains no items.
     */
    public boolean isEmpty( Object category ){
        return containsKey( category ) ? getItems(category).isEmpty() : true;
    }

    /**
     * Returns <tt>true</tt> if this catalog contains the specified item
     * under one or more categories.
     * More formally, returns <tt>true</tt> if and only if this catalog 
     * contains at least one item in any categories <tt>v</tt> such that
     * <tt>(item==null ? v==null : item.equals(v))</tt>.  This operation
     * will probably require time linear in the number of categories for most
     * implementations of the <tt>Catalog</tt> interface.
     *
     * @param item item whose presence in this catalog is to be tested.
     * @return <tt>true</tt> if this catalog contains the specified item
     * under one or more categories.
     */
    public boolean containsItem( Object item ){
        for ( Iterator i = entrySet().iterator(); i.hasNext(); ){
            Map.Entry me = (Map.Entry) i.next();
            Set category = (Set) me.getValue();
            if ( category.contains( item ) ){
                return true;
            }
        }
        return false;
    }

    /**
     * Return <code>true</code> if this map contains the
     * specified category.
     *
     * @param category the category whose presence in this catalog is to be
     *      tested.
     * @return <code>true</code> if this catalog contains the specified
     *      category.
     */
    public boolean containsCategory( Object category ){
        return containsKey( category );
    }


    /**
     * Returns the set of items under the specified category. Returns
     * <tt>null</tt> if the catalog does not contain the category. 
     *
     * @param category the category whose associated items are to be
     *        returned.
     * @return the set of items classified under this category, or
     *	       <tt>null</tt> if catalog does not contain the specified
     *	       category.
     * 
     * @see #containsItem(Object)
     */
    public Set getItems( Object category ){
        return (Set) get( category );
    }

    //
    // Modification Operations
    //

    /**
     * Associates the specified set of items with the specified category in 
     * this catalog.  If the catalog previously 
     * contained the specified category, then the new items are added to
     * the old set of items. If the set of
     * items is null, then a new, empty category is created in the catalog.
     *
     * @param category the category under which the set of
     *              items are to be associated
     * @param items set of items to be associated with the specified
     *      category.
     * @return <code>true</code> if the category already exists and the 
     *      new items
     *         where simply added to it.
     */
    public boolean addItems( Object category, Set items ){
        boolean status = false;

        if ( !containsKey( category ) ){
                super.put( category, createCategory()  );
                status = true;
        }

        if ( items != null ){
            for ( Iterator j = items.iterator(); j.hasNext(); ){
                status |= addItem( category, j.next() );
            }
        }

        return status;
    }

    /**
     * This is method overrides <code>put</code> from {@link java.util.Map} and
     * is equivalent to <code>addItems(category,item)</code>, 
     * when <tt>item</tt> is a  <code>Set</code>.
     *
     * @param category the category under which the specified item is to be         *      classified.
     * @param items set of items to be associated with the specified category.
     * @return previous set of items associated with specified category, or
     *         <tt>null</tt>  if there was no mapping for category.
     * @throws ClassCastException if item is not a <code>Set</code>.
     */
    public Object put(Object category, Object items) {
        Set newItems = (Set) items;
        Object oldItems = remove( category );
        addItems( category, newItems );
        return oldItems;
    }


    /**
     * Classifies the specified item under the specified category.
     * If the category does not exist in the catalog,
     * then it is first added before the item is classified.
     * <p>
     * This method should be used if the user wants to add a set as a item.
     *
     * @param category the category under which the specified item
     *              is to be classified.
     * @param item item to be classified under the specified category.
     * @return <code>true</code> if the item was not already classified under
     *          the specified category.
     * 
     */
    public boolean addItem(Object category, Object item){
        if ( containsKey( category ) ){
            return getItems( category ).add( item );
        } else {
            Set newCategory = createCategory();
            newCategory.add( item );
            super.put( category, newCategory );
            return true;
        }
    }
    /**
     * Adds the specified category to the catalog and associaties with it an
     * empty set (optional operation). If the specified category already exists,
     * then the catalog is not changed and the method returns <tt>true</tt>
     *
     * @param category the category under which the specified item
     *              is to be classified.
     * @return <code>true</code> if the category already exists.
     *
     * @throws UnsupportedOperationException if the <tt>put</tt> operation is
     *            not supported by this catalog.
     * @throws ClassCastException if the class of the specified category
     *            prevents it from being stored in this catalog.
     * @throws IllegalArgumentException if some aspect of this category
     *            prevents it from being stored in this catalog.
     * @throws NullPointerException if this catalog does not permit
     *         <tt>null</tt>
     *         categories, and the specified category is <tt>null</tt>.
     */
    public boolean add( Object category ){
        if ( containsKey( category ) ){
            return true;
        } else {
            super.put( category, createCategory() );
            return false;
        }
    }


    /**
     * Removes the specified item from the specified category.
     * <p>
     * Returns the <code>true</code> if the specified category contained the 
     * specified item and <code>false</code> otherwise.
     *
     * @param category the category from which the specified item is
     *          to be removed or null if no such item exists.
     * @param item item which is to be removed from this category.
     * @return <code>true</code> if the item was classified under
     *          the specified category.
     *
     */
    public boolean remove( Object category, Object item ){
        if ( containsKey( category ) ){
            Set theCategory = (Set) get( category );
            return theCategory.remove( item );
        } else {
            return false;
        }
    }


    // 
    // Bulk Operations
    // 

    /**
     * Adds all of the category and their associated items from the 
     * specified catalog to this catalog.
     *
     * @param catalog a <code>Catalog</catalog> container to be stored 
     *      in this catalog.
     */
    public void putAll( Catalog catalog ){
        for ( Iterator i = catalog.entrySet().iterator(); i.hasNext(); ){
            Map.Entry me = (Map.Entry) i.next();
            Object categoryID = me.getKey();
            Set category = (Set) me.getValue();
            if ( containsKey( categoryID ) ){
                for ( Iterator j = category.iterator(); j.hasNext(); ){
                    Object item = j.next();
                    addItem( categoryID, item );
                }
            } else {
                addItems( categoryID, category );
            }
        }
    }

    /**
     * Copies all of the mappings from the specified map to this catalog. 
     * The values are assumed to be category entries and not categories.
     *
     * @param m map structure to be stored in this catalog.
     */
    public void putAll( Map m ){
        for ( Iterator i = m.entrySet().iterator(); i.hasNext(); ){
            Map.Entry me = (Map.Entry) i.next();
           addItem( me.getKey(), me.getValue() );
        }
    }


    /**
     * Removes all the items from the specified category, but leaves the
     * category in the catalog.
     * <p>
     * Returns the set of items which the catalog previously 
     * classified under the category, or
     * <tt>null</tt> if the catalog contained no such category.
     *
     * @param category the category whose items are to be deleted.
     * @return previous set of items associated with specified category, 
     *         or <tt>null</tt>
     *	       if no such category previously existed in this catalog.  
     */
    public Set clear( Object category ){
        if ( containsKey( category ) ){
            Set oldCategory = getItems( category );
            remove( category );
            addItems( category, createCategory() );
            return oldCategory;
        } else {
            return null;
        }
    }


    //
    // Views
    //

    /**
     * A Collection view of the items in the catalog. This view is
     * stateless.
     */
    transient volatile Collection items = null;

    /**
     * Returns a collection view of the items contained in this catalog.  The
     * collection is backed by the catalog, so changes to the catalog are 
     * reflected in
     * the collection, and vice-versa.  The collection supports element
     * removal, which removes the corresponding mapping from this catalog, 
     * via the
     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
     *
     * @return a collection view of the items contained in this catalog.
     */
    public Collection items() {
        return ( items != null ? items : ( items = new CategoryItems() ) );
    }

    /**
    * Creates a new category. The default implementation returns a HashSet
    * for use as a category, but subclasses can override this behavior to
    * return any subclass of <code>Set</code> for use as a category.
    *
    * @return a <code>Set</code> usable as a category.
    */
    protected Set createCategory() {
        return new HashSet();
    }
                                                                                
    private class CategoryItems extends AbstractCollection {
        public Iterator iterator() {
            return new CategoryItemIterator();
        }
        public int size() {
            return HashCatalog.this.size();
        }
        public boolean contains(Object o) {
            return HashCatalog.this.containsItem(o);
        }
        public void clear() {
            HashCatalog.this.clear();
        }
    }

    private class CategoryItemIterator implements Iterator {

        Iterator categoryIt;
        Iterator itemIt;

        CategoryItemIterator() {
            categoryIt = HashCatalog.this.entrySet().iterator();
            if ( categoryIt.hasNext() ){
                Map.Entry me = (Map.Entry) categoryIt.next();
                Set category = (Set) me.getValue();
                itemIt = category.iterator();
            } else {
                itemIt = categoryIt;
            }
        }

        public Object next() {
            return itemIt.next();
        }

        public boolean hasNext() {
            if ( itemIt.hasNext() ){
                return true;
            } else {
                while( categoryIt.hasNext() ){
                    Map.Entry me = (Map.Entry) categoryIt.next();
                    Set category = (Set) me.getValue();
                    itemIt = category.iterator();
                    if ( itemIt.hasNext() ) break;
                }
                return itemIt.hasNext();
            }
        }

        public void remove() {
            itemIt.remove();
        }

    }

}
