//
// $Id: HashMap.h,v 1.17 2005/07/12 19:15:37 will_mason Exp $
//
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#if !defined(HASHMAP_OL_GUARD)
#define HASHMAP_OL_GUARD

#import <ObjectiveLib/Iterator.h>
#import <ObjectiveLib/Functional.h>

/**
 * @internal The underlying data structure for hash maps
 */
@class OLHashTableMap;

/**
 * @class OLHashMap HashMap.h ObjectiveLib/HashMap.h
 *
 * A map based on a hash table. This class provides very similar functionality to
 * OLMap, but it uses a hash table as the underyling data structure. Like OLMap
 * it is a @ref Containers "container" for associative values with unique keys, meaning that
 * attempting to insert a key that already exists in the container will fail.
 * However, there are important differences to OLMap due to the use of the hash
 * table as the data structure. Searching for a key in a hash map is inherently
 * very fast, which is the primary reason to choose OLHashMap over OLMap. Another
 * difference is that, unlike OLMap, items in a hash map do not appear in a
 * predictable order. Of course, the order is not random, but it does depend on the
 * results of the hash function used.
 *
 * OLHashMap requires a @ref Functors "function object" to test elements for equality,
 * as equal elements are grouped together. If no function is explicitly given, then
 * OLEqualTo is used.
 *
 * @note It is important only to use objects of the same type for the keys in a hash
 * map. If a mixture of types is used, then different hash functions will also be used,
 * resulting in corruption of the distribution of elements in the hash table.
 *
 * @sa OLHashIterator, OLMap
 *
 * @ingroup Containers
 */
@interface OLHashMap : NSObject <OLCopying, NSCoding>
{
@protected
    /**
     * The hash table that provides the underlying data structure
     */
    OLHashTableMap* table;
}

/**
 * Create and return a new hash map. The hash map is allocated using the default
 * allocator. If both the platform and the allocator support autorelease, then the
 * hash map will be autoreleased before being returned.
 *
 * @return a new hash map
 */
+ (id) hashMap;

/**
 * Create and return a new hash map. The hash map is allocated using the default
 * allocator. If both the platform and the allocator support autorelease, then the
 * hash map will be autoreleased before being returned. The hash map is initialized
 * with the contents of the range <tt>[first, last)</tt>.
 *
 * @pre Each element in the range <tt>[first, last)</tt> must be an instance of
 * OLPair. The first element of the pair is the key and the second element is the
 * key's value. Additionally, each key object must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @return a new hash map
 */
+ (id) hashMapFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Create and return a new hash map. The hash map is allocated using the default
 * allocator. If both the platform and the allocator support autorelease, then the
 * hash map will be autoreleased before being returned. The hash map is initialized
 * with the contents of @a right.
 *
 * @param right the hash map to copy
 * @return a new hash map
 */
+ (id) hashMapWithHashMap: (OLHashMap*)right;

/**
 * @name Initializers and Deallocators
 */
/* @{ */
/**
 * Initialize the map. The map is empty and uses OLEqualTo to test for equality and the
 * default memory allocator.
 *
 * @return a reference to this map
 */
- (id) init;

/**
 * Initialize the map. The map uses the default memory allocator, the comparison
 * function OLEqualTo to test for equality, and inserts all elements in
 * the range <tt>[first, last)</tt>.
 *
 * @pre Each element in the range <tt>[first, last)</tt> must be an instance of
 * OLPair. The first element of the pair is the key and the second element is the
 * key's value. Additionally, each key object must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @return a reference to this map
 */
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Initialize the map. The map uses the default memory allocator, the comparison
 * function OLEqualTo to test for equality, and inserts all elements in the range
 * <tt>[first, last)</tt>. The parameter @a size is uses as a hint for
 * the desired hash table size. The table size will grow as needed, so it is not crucial
 * that this parameter carry any particular meaning. However, providing a size can
 * reduce the number of memory allocations required to build the table.
 *
 * @pre Each element in the range <tt>[first, last)</tt> must be an instance of
 * OLPair. The first element of the pair is the key and the second element is the
 * key's value. Additionally, each key object must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @param size a hint as to the desired hash table size
 * @return a reference to this map
 */
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last tableSize: (unsigned)size;

/**
 * Initialize the map. The map uses the default memory allocator, the comparison
 * function @a eq to test for equality, and inserts all elements in
 * the range <tt>[first, last)</tt>. The parameter @a size is used as a hint for
 * the desired hash table size. The table size will grow as needed, so it is not crucial
 * that this parameter carry any particular meaning. However, providing a size can
 * reduce the number of memory allocations required to build the table.
 *
 * @pre Each element in the range <tt>[first, last)</tt> must be an instance of
 * OLPair. The first element of the pair is the key and the second element is the
 * key's value. Additionally, each key object must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @param size a hint as to the desired hash table size
 * @param eq the @ref Functors "function object" used to test elements for equality
 * @return a reference to this map
 */
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last tableSize: (unsigned)size keyEqual: (NSObject<OLBoolBinaryFunction>*)eq;

/**
 * Initialize the map. The map uses the memory allocator @a alloc, the comparison
 * function @a eq to test for equality, and inserts all elements in
 * the range <tt>[first, last)</tt>. The parameter @a size is used as a hint for
 * the desired hash table size. The table size will grow as needed, so it is not crucial
 * that this parameter carry any particular meaning. However, providing a size can
 * reduce the number of memory allocations required to build the table.
 *
 * @pre Each element in the range <tt>[first, last)</tt> must be an instance of
 * OLPair. The first element of the pair is the key and the second element is the
 * key's value. Additionally, each key object must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @param size a hint as to the desired hash table size
 * @param eq the @ref Functors "function object" used to test elements for equality
 * @param alloc the memory allocator to use
 * @return a reference to this map
 */
- (id) initFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last tableSize: (unsigned)size keyEqual: (NSObject<OLBoolBinaryFunction>*)eq allocator: (OLAllocator*)alloc;

/**
 * Initialize the map. This initializer creates a new map
 * from an archive and returns it.
 *
 * @post The map returned will be identical to the map
 * saved to the archive using the #encodeWithCoder: message.
 *
 * @param decoder the coder which will decode the archived map
 * @return a reference to this map
 */
- (id) initWithCoder: (NSCoder*)decoder;

/**
 * Initialize the map. The map copies the memory allocator, the comparison function
 * and all elements from @a right.
 *
 * @param right the hash map to copy
 * @return a reference to this map
 */
- (id) initWithHashMap: (OLHashMap*)right;

/**
 * Initialize the map. The map is initially empty, uses the default memory allocator and
 * OLEqualTo to test elements for equality. The parameter @a size is a hint as to the
 * desired capacity for the hash table. The table will grow as needed, so it isn't
 * strictly necessary to specify a table size, but providing a valid hint can reduce the
 * number of memory allocations performed.
 *
 * @param size a hint as to the desired hash table size
 * @return a reference to this map
 */
- (id) initWithTableSize: (unsigned)size;

/**
 * Initialize the map. The map is initially empty, uses the default memory allocator and
 * @a eq to test elements for equality. The parameter @a size is a hint as to the
 * desired capacity for the hash table. The table will grow as needed, so it isn't
 * strictly necessary to specify a table size, but providing a valid hint can reduce the
 * number of memory allocations performed.
 *
 * @param size a hint as to the desired hash table size
 * @param eq the @ref Functors "function object" used to test elements for equality
 * @return a reference to this map
 */
- (id) initWithTableSize: (unsigned)size keyEqual: (NSObject<OLBoolBinaryFunction>*)eq;

/**
 * Initialize the map. The map is initially empty, uses the memory allocator @a alloc and
 * @a eq to test elements for equality. The parameter @a size is a hint as to the
 * desired capacity for the hash table. The table will grow as needed, so it isn't
 * strictly necessary to specify a table size, but providing a valid hint can reduce the
 * number of memory allocations performed.
 *
 * @param size a hint as to the desired hash table size
 * @param eq the @ref Functors "function object" used to test elements for equality
 * @param alloc the memory allocator to use
 * @return a reference to this map
 */
- (id) initWithTableSize: (unsigned)size keyEqual: (NSObject<OLBoolBinaryFunction>*)eq allocator: (OLAllocator*)alloc;

/**
 * Finalize the map and deallocate any allocated memory.
 */
- (void) dealloc;
/* @} */

/**
 * Assign a value to a key. If the key exists in the map, then @a value is assigned to
 * the key and the old value is dropped. If the key does not exist in the map, then
 * the key-value pair is inserted.
 *
 * @pre @a key must respond to the message @c hash.
 *
 * @param key the key to which to assign
 * @param value the value to assign
 */
- (void) assignKey: (id)key value: (id)value;

/**
 * Return an iterator that points to the first element in the map. The object to which
 * the iterator points is an instance of OLPair. The first element of the pair is the
 * key and the second element of the pair is the key's value. If the platform and
 * the allocator both support autorelease, then the iterator will be scheduled for
 * autorelease before being returned.
 *
 * @return the first iterator for the map
 */
- (OLHashIterator*) begin;

/**
 * Remove all elements.
 */
- (void) clear;
- (id) copyWithAllocator: (OLAllocator*)alloc;
- (id) copyWithZone: (NSZone*)zone;

/**
 * Return the number of instances of @a key in the map.
 *
 * @pre @a key must respond to the message @c hash.
 *
 * @param key the key for which to search
 * @return the number of values that exist for @a key
 */
- (unsigned) count: (id)key;

/**
 * Return whether the map is empty.
 *
 * @return YES if the map is empty, NO otherwise
 */
- (BOOL) empty;

/**
 * Encode the map. The map is saved to an archive using @a encoder. The map
 * will be retrieved from the archive using the initializer #initWithCoder:.
 *
 * @param encoder the coder which will save the bit set to the archive
 */
- (void) encodeWithCoder: (NSCoder*)encoder;

/**
 * Return an iterator that points to one position beyond the last element. The object to which
 * the iterator points is an instance of OLPair. The first element of the pair is the
 * key and the second element of the pair is the key's value. If the platform and
 * the allocator both support autorelease, then the iterator will be scheduled for
 * autorelease before being returned.
 *
 * @return an iterator one position beyond the last element in the map
 */
- (OLHashIterator*) end;

/**
 * Return a range whose elements are equal to @a key. The returned pair contains two instances
 * of OLHashIterator delimiting the range of elements. The objects to which
 * the iterators point are instances of OLPair. The first element of the pair is the
 * key and the second element of the pair is the key's value.If the platform and allocator
 * both support autorelease, then the pair returned will be scheduled for
 * autorelease before being returned.
 *
 * @pre @a key must respond to the message @c hash.
 *
 * @param key the value for which to search
 * @return a pair of OLHashIterator instances that define the range of elements
 * in the map equal to @a key
 */
- (OLPair*) equalRange: (id)key;

/**
 * Remove the element designated by @a where.
 *
 * @pre @a where must point to an element in this map.
 *
 * @param where an iterator designating the element to remove
 */
- (void) erase: (OLHashIterator*)where;

/**
 * Remove a range of elements. All elements in the range <tt>[first, last)</tt> will
 * be removed from the map.
 *
 * @pre @a first and @a last must refer to elements in this map.
 *
 * @param first the first in the range of elements to remove
 * @param last one position beyond the last in the range of elements to remove
 */
- (void) eraseFrom: (OLHashIterator*)first to: (OLHashIterator*)last;

/**
 * Erase all instances of @a key. The map is searched for @a key, all instances are removed
 * from the map and the number removed is returned.
 *
 * @pre @a key must respond to the message @c hash.
 *
 * @param key the key to remove from the map
 * @return the number of elements removed
 */
- (unsigned) eraseKey: (id)key;

/**
 * Find an element. The element @a key is searched for in the map and an iterator to its
 * location is returned. If the element does not exist in the map, then the returned
 * iterator will be equal to the iterator returned by #end. If the platform and allocator
 * both support autorelease, then the iterator returned will be scheduled for
 * autorelease before being returned.
 *
 * The object to which the iterator points is an instance of OLPair. The first element
 * of the pair is the key and the second element of the pair is the key's value.
 *
 * @pre @a key must respond to the mssage @c hash.
 *
 * @param key the key for which to search
 * @return an iterator pointing to the location of @a key, or an iterator equal to that
 * returned by #end if @a key does not exist
 */
- (OLHashIterator*) find: (id)key;

/**
 * Return the memory allocator being used.
 *
 * @return the memory allocator that this map uses
 */
- (OLAllocator*) allocator;

/**
 * Insert a key-value pair into the map. An attempt is made to insert @a keyValue into the map,
 * and an instance of OLPair is returned indicating the state of the insertion. The first
 * element in the returned pair is an instance of OLHashIterator indicating the
 * position of @a keyValue in the map, and the second element of the returned pair is
 * an object that responds to the message @c boolValue. The message @c boolValue will
 * return YES if the insertion was successful, or NO if not. If the platform and
 * the allocator both support autorelease, then the returned pair will be scheduled for
 * autorelease before being returned.
 *
 * @pre The first element of @a keyValue must be the key and the second element must
 * be the key's value. Additionally, the key object must resopnd to the message @c hash.
 *
 * @param keyValue the key-value pair to insert
 * @return an instance of OLPair indicating the position of @a keyVal in the map and
 * whether the insertion succeeded or failed
 */
- (id) insert: (OLPair*)keyValue;

/**
 * Insert a range of key-value pairs into the map. An attempt is made to insert all pairs
 * in the range <tt>[first, last)</tt>, however there is no guarantee that any of
 * the elements in the range will actually be inserted if they already exist in the
 * map.
 *
 * @pre All elements in the range <tt>[first, last)</tt> must be instances of OLPair
 * where the first element of the pair is the key and the second element is the key's
 * value. Additionally, each key object must respond to the message @c hash.
 *
 * @param first the first in the range of pairs to insert
 * @param last one position beyond the last in the range of pairs to insert
 */
- (void) insertFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Insert a key-value pair. This message is simply a wrapper for #insert:, provided
 * as a convenience. It elminiates the need to create an instance of OLPair before
 * performing insertion. As with #insert: the message returns an instance of OLPair
 * indicating the state of the insertion. The first element of the pair returned
 * is an instance of OLHashIterator indicating the position of @a key in the
 * map, and the second element of the pair is an object that responds to the
 * message @c boolValue. The message @c boolValue will return YES if the insertion
 * succeeded, or NO if it did not.
 *
 * @pre @a key must respond to the message @c hash.
 *
 * @param key the key to insert
 * @param val the value of the key
 * @return an instance of OLPair indicating the position of @a key in the map and
 * whether the insertion succeeded or failed
 */
- (id) insertKey: (id)key value: (id)val;

/**
 * Test whether another map is equal to this one. Two maps are considered equal if they
 * contain the same number of objects and the and the value of each key is equal to the
 * value of the corresponding key in the other map. Values are always compared using
 * the message @c isEqual:, while keys are compared using the functor returned by the
 * message #keyEqual. The two maps do not have
 * to use the same memory allocator to be considered equal.
 *
 * @param object the object to test
 * @return YES if @a object is equal to this map, NO otherwise
 */
- (BOOL) isEqual: (id)object;

/**
 * Return the comparison function for keys. The function is used to group equal elements
 * together.
 *
 * @return the @ref Functors "function object" used to compare keys for equality
 */
- (NSObject<OLBoolBinaryFunction>*) keyEqual;

/**
 * Return the maxiumum number of objects that can be stored in a map. This limit is
 * theoretical, meaning that there is no guarantee that you can insert this many
 * objects into any given map. The memory conditions of the run-time system play an
 * important role.
 *
 * @return the maximum number of objects for a map
 */
- (unsigned) maxSize;

/**
 * Return the number of elements in the map.
 *
 * @return the number of elements
 */
- (unsigned) size;

/**
 * Swap this map with another one. All elements in @a right will be placed into
 * this map, and all elements in this map will be placed in @a right. Unless the
 * allocators of the two maps are different, the implementation of this message
 * results in a very fast swap.
 *
 * @param right the map with which to swap this one
 */
- (void) swap: (OLHashMap*)right;

/**
 * Find the value of a given key. The map is searched for @a key, and if it is found
 * its value is returned. Otherwise, the message returns @c nil.
 *
 * @pre @a key must respond to the message @c hash.
 *
 * @param key the key for which to search
 * @return the key's value or @c nil if the key does not exist in the map
 */
- (id) valueForKey: (id)key;

@end

/**
 * @class OLHashMultiMap HashMap.h ObjectiveLib/HashMap.h
 *
 * A hash map that allows many values to be associated with equivilant keys. Hash
 * multimap is identical to hash map except that as many instances of a given key
 * may be inserted as desired.
 *
 * @sa OLHashMap, OLHashIterator
 *
 * @ingroup Containers
 */
@interface OLHashMultiMap : OLHashMap
{
}

/**
 * Create and return a new hash map. The hash map is allocated using the default
 * allocator. If both the platform and the allocator support autorelease, then the
 * hash map will be autoreleased before being returned.
 *
 * @return a new hash map
 */
+ (id) hashMultiMap;

/**
 * Create and return a new hash map. The hash map is allocated using the default
 * allocator. If both the platform and the allocator support autorelease, then the
 * hash map will be autoreleased before being returned. The hash map is initialized
 * with the contents of the range <tt>[first, last)</tt>.
 *
 * @pre Each element in the range <tt>[first, last)</tt> must be an instance of
 * OLPair. The first element of the pair is the key and the second element is the
 * key's value. Additionally, each key object must respond to the message @c hash.
 *
 * @param first the first in the range of elements to insert
 * @param last one beyond the last in the range of elements to insert
 * @return a reference to this map
 * @return a new hash map
 */
+ (id) hashMultiMapFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Create and return a new hash map. The hash map is allocated using the default
 * allocator. If both the platform and the allocator support autorelease, then the
 * hash map will be autoreleased before being returned. The hash map is initialized
 * with the contents of @a right.
 *
 * @note The argument @a right may be an instance of OLHashMap or of OLHashMultiMap.
 *
 * @param right the hash map to copy
 * @return a new hash map
 */
+ (id) hashMultiMapWithHashMap: (OLHashMap*)right;

/**
 * Assign a value to a key. This message always inserts the key-value pair
 * <tt>(key, value)</tt>.
 *
 * @pre @a key must respond to the message @c hash.
 *
 * @param key the key to insert
 * @param value the key's value
 */
- (void) assignKey: (id)key value: (id)value;

/**
 * Insert a key-value pair into the map. The pair is inserted and an instance of
 * OLHashIterator is returned indicating the position of @a keyValue
 * in the set. The object to which the iterator points is an instance
 * of OLPair. The first element of the pair is the key and the second element of the
 * pair is the key's value. If the platform and the allocator both support autorelease,
 * then the iterator will be scheduled for autorelease before being returned.
 *
 * @pre The first element of @a keyValue must be the key and the second element must
 * be the key's value. Additionally, the key object must resopnd to the message @c hash.
 *
 * @param keyValue the element to insert
 * @return an instance of OLHashIterator pointing to the newly inserted object
 */
- (id) insert: (OLPair*)keyValue;
- (void) insertFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last;

/**
 * Test whether another map is equal to this one. Two maps are considered equal if they
 * contain the same number of objects and the and the value of each key is equal to the
 * value of the corresponding key in the other map. Values are always compared using
 * the message @c isEqual:, while keys are compared using the functor returned by the
 * message #keyEqual. The two maps do not have
 * to use the same memory allocator to be considered equal.
 *
 * @note When two maps each contain
 * a range of equal keys, the values that correspond to those keys do not have to appear
 * in the same order. They just have to exist. 
 *
 * @param object the object to test
 * @return YES if @a object is equal to this map, NO otherwise
 */
- (BOOL) isEqual: (id)object;

/**
 * Find the value of a given key. Since a hash multimap can have any number of values
 * associated with equivilent keys, this message does nothing. The value @c nil
 * is @b always returned.
 *
 * @param key the key for which to search
 * @return @c nil
 */
- (id) valueForKey: (id)key;

@end

#endif
