//
// $Id: HashTable.m,v 1.14 2005/05/18 06:46:53 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.
 */

#import "HashTable.h"
#import "Macros.h"
#import "VectorJunior.h"
#import <Foundation/NSValue.h>
#import <limits.h>
#import <stddef.h>
#import <string.h>

#define OL_NUMBER_OF_PRIMES 28

static unsigned __primes[] =
{
    53U,          97U,          193U,        389U,        769U, 
    1543U,        3079U,        6151U,       12289U,      24593U, 
    49157U,       98317U,       196613U,     393241U,     786433U, 
    1572869U,     3145739U,     6291469U,    12582917U,   25165843U, 
    50331653U,    100663319U,   201326611U,  402653189U,  805306457U, 
    1610612741U,  3221225473U,  4294967291U
};

OLHashTableNode* __bumpHashTableNode(OLHashTableNode* node, OLHashTable* table)
{
    OLHashTableNode* n = node->next;

    return (n == NULL) ? [table skipToNext: node->value] : n;
}

@interface OLHashIterator (PrivateMethods)

- (id) initWithTable: (OLHashTable*)tbl node: (OLHashTableNode*)nd;
- (OLHashTableNode*) current;

@end

@interface OLBucketHead : NSObject
{
@private
    OLHashTableNode* node;
}

- (id) initWithNode: (OLHashTableNode*)nde;

- (OLHashTableNode*)node;
- (void) setNode: (OLHashTableNode*)node;

@end

@implementation OLHashTable

- (id) initWithHashTable: (OLHashTable*)right
{
    [super init];
    allocator = OBJ_RETAIN(right->allocator);
    keyEqual = OBJ_RETAIN(right->keyEqual);
    buckets = [[allocator allocateObject: [OLVector class]]
        initWithAllocator: allocator];
    [self assign: right];
    return self;
}

- (id) initWithSize: (unsigned)sz equalFunc: (NSObject<OLBoolBinaryFunction>*)eq allocator: (OLAllocator*)alloc
{
    OLBucketHead* buck;
    unsigned numberOfBuckets;
    unsigned i;

    [super init];
    allocator = OBJ_RETAIN(alloc);
    keyEqual = OBJ_RETAIN(eq);
    numberOfBuckets = [self nextSize: sz];
    buckets = [[allocator allocateObject: [OLVector class]]
        initWithAllocator: allocator];
    [buckets reserve: numberOfBuckets];
    for (i = 0; i < numberOfBuckets; i++)
    {
        buck = [[allocator allocateObject: [OLBucketHead class]]
            initWithNode: NULL];
        [buckets pushBack: buck];
        [allocator deallocateObject: buck];
    }
    numElements = 0;
    return self;
}

- (void) dealloc
{
    [self clear];
    OBJ_RELEASE(keyEqual);
    [allocator deallocateObject: buckets];
    OBJ_RELEASE(allocator);
    [super dealloc];
}

- (void) assign: (OLHashTable*)right
{
    OLHashTableNode* cur;
    OLHashTableNode* next;
    OLHashTableNode* xcopy;
    OLBucketHead* newBkt;
    unsigned i;
    unsigned numberOfBuckets = [right->buckets size];

    [self clear];
    [buckets clear];
    [buckets reserve: numberOfBuckets];
    for (i = 0; i < numberOfBuckets; i++)
    {
        cur = [[right->buckets at: i] node];
        if (cur != NULL)
        {
            xcopy = [self newNode: cur->value];
            newBkt = [[allocator allocateObject: [OLBucketHead class]]
                initWithNode: xcopy];
            [buckets pushBack: newBkt];
            [allocator deallocateObject: newBkt];
            for (next = cur->next; next != NULL; cur = next, next = cur->next)
            {
                xcopy->next = [self newNode: next->value];
                xcopy = xcopy->next;
            }
        }
        else
        {
            newBkt = [[allocator allocateObject: [OLBucketHead class]]
                initWithNode: NULL];
            [buckets pushBack: newBkt];
            [allocator deallocateObject: newBkt];
        }
    }
    numElements = right->numElements;
}

- (OLHashIterator*) begin
{
    return OBJ_COND_AUTORELEASE([self beginImpl]);
}

- (OLHashIterator*) beginImpl
{
    OLHashTableNode* node;
    OLHashIterator* itor = nil;
    unsigned size = [buckets size];
    unsigned i;

    for (i = 0; i < size; i++)
    {
        node = [[buckets at: i] node];
        if (node != NULL)
        {
            itor = [[allocator allocateObject: [OLHashIterator class]]
                initWithTable: self node: node];
            break;
        }
    }
    if (itor == nil)
    {
        itor = [[allocator allocateObject: [OLHashIterator alloc]]
            initWithTable: self node: NULL];
    }
    return itor;
}

- (unsigned) bucketOfKey: (id)key
{
    return [self bucketOfKey: key size: [buckets size]];
}

- (unsigned) bucketOfKey: (id)key size: (unsigned)sz
{
    return [key hash] % sz;
}

- (unsigned) bucketOfValue: (id)value
{
    return [self bucketOfKey: [self keyOfValue: value]];
}

- (unsigned) bucketOfValue: (id)value size: (unsigned)sz
{
    return [self bucketOfKey: [self keyOfValue: value] size: sz];
}

- (void) clear
{
    OLHashTableNode* cur;
    OLHashTableNode* next;
    unsigned size = [buckets size];
    unsigned i;

    for (i = 0; i < size; i++)
    {
        cur = [[buckets at: i] node];
        while (cur != NULL)
        {
            next = cur->next;
            [self destroyNode: cur];
            cur = next;
        }
        [[buckets at: i] setNode: NULL];
    }
    numElements = 0;
}

- (id) copyWithAllocator: (OLAllocator*)alloc
{
    return [[alloc allocateObject: [OLHashTable class]]
        initWithHashTable: self];
}

- (id) copyWithZone: (NSZone*)zone
{
    return [[OLHashTable allocWithZone: zone] initWithHashTable: self];
}

- (unsigned) count: (id)key
{
    OLHashTableNode* node;
    unsigned result = 0;

    for (node = [[buckets at: [self bucketOfKey: key]] node];
         node != NULL;
         node = node->next)
    {
        if ([keyEqual performBinaryFunctionWithArg: [self keyOfValue: node->value]
                andArg: key])
        {
            result++;
        }
    }
    return result;
}

- (void) destroyNode: (OLHashTableNode*)node
{
    OBJ_RELEASE(node->value);
    [allocator deallocateBytes: node];
}

- (BOOL) empty
{
    return numElements == 0 ? YES : NO;
}

- (OLHashIterator*) end
{
    return OBJ_COND_AUTORELEASE([self endImpl]);
}

- (OLHashIterator*) endImpl
{
    return [[allocator allocateObject: [OLHashIterator class]]
        initWithTable: self node: NULL];
}

- (OLPair*) equalRange: (id)key
{
    OLPair* p;
    OLHashTableNode* first;
    OLHashTableNode* last;
    OLHashIterator* start;
    OLHashIterator* finish;

    [self equalRangeImpl: key first: &first last: &last];
    start = [[allocator allocateObject: [OLHashIterator class]]
        initWithTable: self node: first];
    finish = [[allocator allocateObject: [OLHashIterator class]]
        initWithTable: self node: last];
    p = [[allocator allocateObject: [OLPair class]]
        initWithFirst: start second: finish];
    [allocator deallocateObject: start];
    [allocator deallocateObject: finish];
    return OBJ_COND_AUTORELEASE(p);
}

- (void) equalRangeImpl: (id)key first: (OLHashTableNode**)firstOut last: (OLHashTableNode**)lastOut
{
    OLHashTableNode* first;
    OLHashTableNode* cur = NULL;
    unsigned n = [self bucketOfKey: key];
    unsigned m;
    unsigned size;

    for (first = [[buckets at: n] node]; first != NULL; first = first->next)
    {
        if ([keyEqual performBinaryFunctionWithArg: [self keyOfValue: first->value]
                andArg: key])
        {
            for (cur = first->next; cur != NULL; cur = cur->next)
            {
                if (![keyEqual performBinaryFunctionWithArg: [self keyOfValue: cur->value]
                        andArg: key])
                {
                    break;
                }
            }
            if (cur == NULL)
            {
                for (m = n + 1, size = [buckets size]; m < size; m++)
                {
                    if ([[buckets at: m] node] != NULL)
                    {
                        cur = [[buckets at: m] node];
                        break;
                    }
                }
            }
            break;
        }
    }
    *firstOut = first;
    *lastOut = cur;
}

- (void) erase: (OLHashIterator*)where
{
    OLHashTableNode* node = [where current];
    OLHashTableNode* cur;
    OLHashTableNode* next;
    unsigned n;

    if (node != NULL)
    {
        n = [self bucketOfValue: node->value];
        cur = [[buckets at: n] node];
        if (cur == node)
        {
            [[buckets at: n] setNode: cur->next];
            [self destroyNode: cur];
            numElements--;
        }
        else
        {
            next = cur->next;
            while (next != NULL)
            {
                if (next == node)
                {
                    cur->next = next->next;
                    [self destroyNode: next];
                    --numElements;
                    break;
                }
                else
                {
                    cur = next;
                    next = cur->next;
                }
            }
        }
    }
}

- (void) eraseBucket: (unsigned)num from: (OLHashTableNode*)first to: (OLHashTableNode*)last
{
    OLHashTableNode* cur = [[buckets at: num] node];
    OLHashTableNode* next;

    if (cur == first)
    {
        [self eraseBucket: num to: last];
    }
    else
    {
        next = cur->next;
        while (next != first)
        {
            cur = next;
            next = cur->next;
        }
        while (next != last)
        {
            cur->next = next->next;
            [self destroyNode: next];
            next = cur->next;
            numElements--;
        }
    }
}

- (void) eraseBucket: (unsigned)num to: (OLHashTableNode*)last
{
    OLBucketHead* head = [buckets at: num];
    OLHashTableNode* cur = [head node];
    OLHashTableNode* next;

    while (cur != NULL && cur != last)
    {
        next = cur->next;
        [self destroyNode: cur];
        cur = next;
        [head setNode: cur];
        numElements--;
    }
}

- (void) eraseFrom: (OLHashIterator*)first to: (OLHashIterator*)last
{
    OLHashTableNode* fnode = [first current];
    OLHashTableNode* lnode = [last current];
    unsigned fBucket;
    unsigned lBucket;
    unsigned n;

    if (fnode != lnode)
    {
        fBucket = fnode != NULL ? [self bucketOfValue: fnode->value] : [buckets size];
        lBucket = lnode != NULL ? [self bucketOfValue: lnode->value] : [buckets size];
        if (fBucket == lBucket)
        {
            [self eraseBucket: fBucket from: fnode to: lnode];
        }
        else
        {
            [self eraseBucket: fBucket from: fnode to: NULL];
            for (n = fBucket + 1; n < lBucket; n++)
                [self eraseBucket: n to: NULL];
            if (lBucket != [buckets size])
                [self eraseBucket: lBucket to: lnode];
        }
    }
}

- (unsigned) eraseKey: (id)key
{
    OLHashTableNode* first;
    OLHashTableNode* cur;
    OLHashTableNode* next;
    unsigned n = [self bucketOfKey: key];
    unsigned erased = 0;

    first = [[buckets at: n] node];
    if (first != NULL)
    {
        cur = first;
        next = cur->next;
        while (next != NULL)
        {
            if ([keyEqual performBinaryFunctionWithArg: [self keyOfValue: next->value]
                    andArg: key])
            {
                cur->next = next->next;
                [self destroyNode: next];
                next = cur->next;
                erased++;
                numElements--;
            }
            else
            {
                cur = next;
                next = cur->next;
            }
        }
        if ([keyEqual performBinaryFunctionWithArg: [self keyOfValue: first->value]
                andArg: key])
        {
            [[buckets at: n] setNode: first->next];
            [self destroyNode: first];
            erased++;
            numElements--;
        }
    }
    return erased;
}

- (OLHashIterator*) find: (id)key
{
    return OBJ_COND_AUTORELEASE([[allocator allocateObject: [OLHashIterator class]]
        initWithTable: self node: [self findImpl: key]]);
}

- (OLHashTableNode*) findImpl: (id)key
{
    OLHashTableNode* node = [[buckets at: [self bucketOfKey: key]] node];

    while (node != NULL &&
            ![keyEqual performBinaryFunctionWithArg: [self keyOfValue: node->value]
                andArg: key])
    {
        node = node->next;
    }
    return node;
}

- (OLAllocator*) allocator
{
    return allocator;
}

- (OLHashIterator*) insertEqual: (id)value
{
    [self resize: numElements + 1];
    return OBJ_COND_AUTORELEASE([self insertEqualImpl: value needItor: YES]);
}

- (void) insertEqualFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last
{
    OLForwardIterator* f = [first copyWithAllocator: allocator];
    unsigned n = [OLIterator distanceFrom: first to: last allocator: allocator];

    [self resize: numElements + n];
    for ( ; n > 0; n--, [f advance])
        [self insertEqualImpl: [f dereference] needItor: NO];
    [allocator deallocateObject: f];
}

- (OLHashIterator*) insertEqualImpl: (id)value needItor: (BOOL)needItor
{
    OLHashTableNode* first;
    OLHashTableNode* cur;
    OLHashTableNode* tmp;
    id key = [self keyOfValue: value];
    unsigned n = [self bucketOfValue: value];

    first = [[buckets at: n] node];
    for (cur = first; cur != NULL; cur = cur->next)
    {
        if ([keyEqual performBinaryFunctionWithArg: [self keyOfValue: cur->value]
                andArg: key])
        {
            tmp = [self newNode: value];
            tmp->next = cur->next;
            cur->next = tmp;
            goto leaveInsertEqualImpl;
        }
    }
    tmp = [self newNode: value];
    tmp->next = first;
    [[buckets at: n] setNode: tmp];
leaveInsertEqualImpl:
    numElements++;
    return needItor ? [[allocator allocateObject: [OLHashIterator class]]
        initWithTable: self node: tmp] : nil;
}

- (OLPair*) insertUnique: (id)value
{
    [self resize: numElements + 1];
    return OBJ_COND_AUTORELEASE([self insertUniqueImpl: value needPair: YES]);
}

- (void) insertUniqueFrom: (OLForwardIterator*)first to: (OLForwardIterator*)last
{
    OLForwardIterator* f = [first copyWithAllocator: allocator];
    unsigned n = [OLIterator distanceFrom: first to: last allocator: allocator];

    [self resize: numElements + n];
    for ( ; n > 0; n--, [f advance])
        [self insertUniqueImpl: [f dereference] needPair: NO];
    [allocator deallocateObject: f];
}

- (OLPair*) insertUniqueImpl: (id)value needPair: (BOOL)needPair
{
    OLHashTableNode* first;
    OLHashTableNode* cur;
    OLPair* p;
    OLHashIterator* itor;
    NSNumber* result;
    id key = [self keyOfValue: value];
    unsigned n = [self bucketOfValue: value];
    BOOL insertOkay = YES;

    first = [[buckets at: n] node];
    for (cur = first; cur != NULL; cur = cur->next)
    {
        if ([keyEqual performBinaryFunctionWithArg: [self keyOfValue: cur->value]
                andArg: key])
        {
            insertOkay = NO;
            break;
        }
    }
    if (insertOkay)
    {
        cur = [self newNode: value];
        cur->next = first;
        [[buckets at: n] setNode: cur];
        numElements++;
    }
    if (needPair)
    {
        itor = [[allocator allocateObject: [OLHashIterator class]]
            initWithTable: self node: cur];
        result = [[allocator allocateObject: [NSNumber class]]
            initWithBool: insertOkay];
        p = [[allocator allocateObject: [OLPair class]]
            initWithFirst: itor second: result];
        [allocator deallocateObject: itor];
        [allocator deallocateObject: result];
        return p;
    }
    return nil;
}

- (BOOL) isEqualNonUnique: (id)object
{
    OLHashIterator* myCur;
    OLHashIterator* myLast;
    OLHashTable* other;
    OLVectorJunior* scratch;
    OLHashTableNode* first;
    OLHashTableNode* last;
    id curKey = nil;
    id nextKey;
    BOOL equal = YES;

    if (![object isKindOfClass: [OLHashTable class]])
        return NO;
    other = (OLHashTable*)object;
    if (other->numElements != numElements)
        return NO;
    if (numElements == 0)
        return YES;
    myCur = [self beginImpl];
    myLast = [self endImpl];
    scratch = [[allocator allocateObject: [OLVectorJunior class]]
        initWithAllocator: allocator];
    for ( ; ![myCur isEqual: myLast]; [myCur advance])
    {
        nextKey = [self keyOfValue: [myCur dereference]];
        if (curKey == nil || ![curKey isEqual: nextKey])
        {
            curKey = nextKey;
            [other equalRangeImpl: curKey first: &first last: &last];
            [scratch clear];
            for ( ; first != last; first = __bumpHashTableNode(first, other))
                [scratch pushBack: [other valueOfValue: first->value]];
        }
        if (![scratch checkAndClear: [self valueOfValue: [myCur dereference]]])
        {
            equal = NO;
            break;
        }
    }
    [allocator deallocateObject: myCur];
    [allocator deallocateObject: myLast];
    [allocator deallocateObject: scratch];
    return equal;
}

- (BOOL) isEqualUnique: (id)object
{
    OLHashIterator* myCur;
    OLHashIterator* myLast;
    OLHashTableNode* node;
    OLHashTable* other;
    BOOL equal = YES;

    if (![object isKindOfClass: [OLHashTable class]])
        return NO;
    other = (OLHashTable*)object;
    if (other->numElements != numElements)
        return NO;
    myCur = [self beginImpl];
    myLast = [self endImpl];
    for ( ; ![myCur isEqual: myLast]; [myCur advance])
    {
        node = [other findImpl: [other keyOfValue: [myCur dereference]]];
        if (node == NULL ||
            ![[other valueOfValue: node->value] isEqual: [self valueOfValue: [myCur dereference]]])
        {
            equal = NO;
            break;
        }
    }
    [allocator deallocateObject: myCur];
    [allocator deallocateObject: myLast];
    return equal;
}

- (NSObject<OLBoolBinaryFunction>*) keyEqual
{
    return keyEqual;
}

- (id) keyOfValue: (id)value
{
    return value;
}

- (unsigned) maxSize
{
    return UINT_MAX;
}

- (unsigned) nextSize: (unsigned)n
{
    unsigned len = OL_NUMBER_OF_PRIMES;
    unsigned half;
    unsigned* first = __primes;
    unsigned* middle;

    while (len > 0)
    {
        half = len >> 1;
        middle = first + half;
        if (*middle < n)
        {
            first = middle + 1;
            len = len - half - 1;
        }
        else
        {
            len = half;
        }
    }
    return first == (__primes + OL_NUMBER_OF_PRIMES) ?
        *(__primes + OL_NUMBER_OF_PRIMES - 1) : *first;
}

- (OLHashTableNode*) newNode: (id)value
{
    OLHashTableNode* node = (OLHashTableNode*)[allocator allocateBytes: sizeof(OLHashTableNode)];

    node->next = NULL;
    node->value = OBJ_RETAIN(value);
    return node;
}

- (void) resize: (unsigned)hint
{
    OLVector* tmp;
    OLHashTableNode* node;
    OLBucketHead* head;
    unsigned oldSize = [buckets size];
    unsigned n;
    unsigned bucket;
    unsigned newBucket;
    unsigned i;

    if (hint > oldSize)
    {
        n = [self nextSize: hint];
        if (n > oldSize)
        {
            tmp = [[allocator allocateObject: [OLVector class]]
                initWithAllocator: allocator];
            [tmp reserve: n];
            for (i = 0; i < n; i++)
            {
                head = [[allocator allocateObject: [OLBucketHead class]]
                    initWithNode: NULL];
                [tmp pushBack: head];
                [allocator deallocateObject: head];
            }
            for (bucket = 0; bucket < oldSize; bucket++)
            {
                node = [[buckets at: bucket] node];
                while (node != NULL)
                {
                    newBucket = [self bucketOfValue: node->value size: n];
                    [[buckets at: bucket] setNode: node->next];
                    node->next = [[tmp at: newBucket] node];
                    [[tmp at: newBucket] setNode: node];
                    node = [[buckets at: bucket] node];
                }
            }
            [buckets swap: tmp];
            [allocator deallocateObject: tmp];
        }
    }
}

- (unsigned) size
{
    return numElements;
}

- (OLHashTableNode*) skipToNext: (id)value
{
    unsigned bucket = [self bucketOfValue: value];
    unsigned sz = [buckets size];
    OLHashTableNode* node = NULL;

    while (node == NULL && ++bucket < sz)
        node = [[buckets at: bucket] node];
    return node;
}

- (void) swap: (OLHashTable*)right
{
    OL_PREPARE_SLOW_SWAP;
    OLHashTable* tmp;

    if (self != right)
    {
        if ([allocator isEqual: right->allocator])
        {
            OL_SLOW_SWAP(buckets, right->buckets);
            OL_SLOW_SWAP(keyEqual, right->keyEqual);
            OL_SLOW_SWAP(allocator, right->allocator);
            OL_FAST_SWAP(numElements, right->numElements);
        }
        else
        {
            tmp = [[allocator allocateObject: [OLHashTable class]]
                initWithHashTable: self];
            [self assign: right];
            [right assign: tmp];
            [allocator deallocateObject: tmp];
        }
    }
}

- (unsigned) tableSize
{
    return [buckets size];
}

- (id) valueOfValue: (id)value
{
    return value;
}

@end

@implementation OLHashIterator (PrivateMethods)

- (id) initWithTable: (OLHashTable*)tbl node: (OLHashTableNode*)nd
{
    [super init];
    table = tbl;
    current = nd;
    return self;
}

- (OLHashTableNode*) current
{
    return current;
}

@end

@implementation OLBucketHead

- (id) initWithNode: (OLHashTableNode*)nde
{
    [super init];
    node = nde;
    return self;
}

- (OLHashTableNode*)node
{
    return node;
}

- (void) setNode: (OLHashTableNode*)nde
{
    node = nde;
}

@end

@implementation OLHashTableMap

- (void) assignEqualKey: (id)key value: (id)val
{
    OLPair* p = [[allocator allocateObject: [OLPair class]]
        initWithFirst: key second: val];
    [self insertEqualImpl: p needItor: NO];
    [allocator deallocateObject: p];
}

- (void) assignUniqueKey: (id)key value: (id)val
{
    OLHashTableNode* node = [self findImpl: key];
    OLPair* p;

    if (node == NULL)
    {
        p = [[allocator allocateObject: [OLPair class]]
            initWithFirst: key second: val];
        [self insertUniqueImpl: p needPair: NO];
        [allocator deallocateObject: p];
    }
    else
    {
        [node->value setSecond: val];
    }
}

- (id) findValue: (id)key
{
    OLHashTableNode* node = [self findImpl: key];

    return (node == NULL) ? nil : [node->value second];
}

- (id) keyOfValue: (id)value
{
    return [value first];
}

- (id) valueOfValue: (id)value
{
    return [value second];
}

@end


