/* 
   EOObjectUniquer.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
   Date: 1996

   This file is part of the GNUstep Database Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <eoaccess/common.h>

#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>

#include <extensions/NSException.h>
#include <extensions/exceptions/GeneralExceptions.h>

#include "EOObjectUniquer.h"
#include "EOPrimaryKeyDictionary.h"

static unsigned uniquerHash
    (NSMapTable* table, EOUniquerRecord* rec)
{
    // efficient hash for dictionaries is done in the concrete
    // dictionaries implementation; dictionaries are allocated
    // from EOPrimaryKeyDictionary concrete subclasses
    return  ((unsigned)(rec->entity) >> 4) + 
	    ((EOPrimaryKeyDictionary*)(rec->pkey))->fastHash;
}

static BOOL uniquerCompare
    (NSMapTable* table, EOUniquerRecord* rec1, EOUniquerRecord* rec2)
{
    // efficient compare between dictionaries is done in the concrete
    // dictionaries implementation; dictionaries are allocated
    // from EOPrimaryKeyDictionary concrete subclasses
    return (rec1->entity == rec2->entity) && 
	   [rec1->pkey fastIsEqual:rec2->pkey];
}

static NSString* uniqDescription
    (NSMapTable *t, EOUniquerRecord* rec)
{
    return [NSString stringWithFormat:
	@"<<pkey:%08x entity:%08x object:%08x snapshot:%08x>>",
	rec->pkey, rec->entity, rec->object, rec->snapshot];
}

static void uniquerRetain
    (NSMapTable *table, EOUniquerRecord* rec)
{
    rec->refCount++;
}

static void uniquerRelease
    (NSMapTable* table, EOUniquerRecord* rec)
{
    rec->refCount--;
    if (rec->refCount > 0)
	return;
    else {
	[rec->pkey release];
	[rec->entity release];
	[rec->snapshot release];
	free(rec);
    }
}

static inline EOUniquerRecord* uniquerCreate
    (id pkey, id entity, id object, id snapshot)
{
    EOUniquerRecord* rec=(EOUniquerRecord*)malloc(sizeof(EOUniquerRecord));
    
    rec->refCount = 0;
    rec->pkey = [pkey retain];
    rec->entity = [entity retain];
    rec->object = object;
    rec->snapshot = [snapshot retain];
    
    return rec;
}

static void uniquerNoAction
    (NSMapTable * t, const void *anObject)
{
}

static NSMapTableKeyCallBacks uniquerKeyMapCallbacks = {
	(unsigned(*)(NSMapTable *, const void *))uniquerHash,
	(BOOL(*)(NSMapTable *, const void *, const void *))uniquerCompare,
	(void (*)(NSMapTable *, const void *))uniquerNoAction,
	(void (*)(NSMapTable *, void *))uniquerNoAction,
	(NSString *(*)(NSMapTable *, const void *))uniqDescription,
	(const void *)NULL
}; 

static NSMapTableValueCallBacks uniquerValueMapCallbacks = {
	(void (*)(NSMapTable *, const void *))uniquerRetain,
	(void (*)(NSMapTable *, void *))uniquerRelease,
	(NSString *(*)(NSMapTable *, const void *))uniqDescription
}; 

static int initialHashSize = 1021;

@implementation EOObjectUniquer

// Initializing a uniquing dictionary

- init
{
    primaryKeyToRec = NSCreateMapTable(uniquerKeyMapCallbacks, 
	uniquerValueMapCallbacks, initialHashSize);
    objectsToRec = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, 
	uniquerValueMapCallbacks, initialHashSize);
    keyRecord = uniquerCreate(nil, nil, nil, nil);
    return self;
}

- (void)dealloc
{
    [self forgetAllObjects];
    NSFreeMapTable(objectsToRec);
    NSFreeMapTable(primaryKeyToRec);
    free(keyRecord);
    [super dealloc];
}

// Transfer self to parent

- (void)transferTo:(EOObjectUniquer*)dest
  objects:(BOOL)isKey andSnapshots:(BOOL)isSnap
{
    EOUniquerRecord* key;
    EOUniquerRecord* rec;
    NSMapEnumerator enumerator = NSEnumerateMapTable(primaryKeyToRec);
    
    while(NSNextMapEnumeratorPair(&enumerator, (void**)(&key), (void**)(&rec))) 
    {
	[dest recordObject: rec->object
	    primaryKey:isKey ? rec->pkey : nil
	    entity:isKey ? rec->entity : nil
	    snapshot:isSnap ? rec->snapshot : nil];
    }
    NSResetMapTable(primaryKeyToRec);
    NSResetMapTable(objectsToRec);
}

// Handling objects

- (void)forgetObject:anObj
{
    EOUniquerRecord* rec;
    
    if (!anObj)
	return;
    
    rec = (EOUniquerRecord*)NSMapGet(objectsToRec, anObj);
    if (!rec)
	return;
    if (rec->pkey)
	NSMapRemove(primaryKeyToRec, rec);
    NSMapRemove(objectsToRec, anObj);
}

- (void)forgetAllObjects
{
    NSResetMapTable(primaryKeyToRec);
    NSResetMapTable(objectsToRec);
}

- (void)forgetAllSnapshots
{

    EOUniquerRecord* key;
    EOUniquerRecord* rec;
    NSMapEnumerator enumerator = NSEnumerateMapTable(objectsToRec);
    
    while(NSNextMapEnumeratorPair(&enumerator, (void**)(&key), (void**)(&rec))) 
    {
	[rec->snapshot release];
	rec->snapshot = nil;
    }
}

- objectForPrimaryKey:(NSDictionary*)aKey
  entity:(EOEntity*)anEntity
{
    EOUniquerRecord* rec;
    
    if (!aKey || !anEntity)
	return nil;

    if (![aKey isKindOfClass:[EOPrimaryKeyDictionary class]])
	THROW([[InvalidArgumentException alloc] 
	    initWithReason:@"attempt to record object with non "
			   @" EOPrimaryKeyDictionary class in EOObjectUniquer."
			   @"This is a bug in EODatabase/Context/Channel."]);

    keyRecord->pkey = aKey;
    keyRecord->entity = anEntity;
    
    rec = (EOUniquerRecord*)NSMapGet(primaryKeyToRec, keyRecord);
    
    return rec ? rec->object : nil;
}

- (EOUniquerRecord*)recordForObject:anObj
{
    EOUniquerRecord* rec;
    
    if (!anObj)
	return (EOUniquerRecord*)NULL;
    
    rec = (EOUniquerRecord*)NSMapGet(objectsToRec, anObj);

    return rec;
}

- (void)recordObject:anObj
  primaryKey:(NSDictionary*)aKey
  entity:(EOEntity*)anEntity
  snapshot:(NSDictionary*)aSnapshot
{
    EOUniquerRecord* rec;
    EOUniquerRecord* orc;
    
    if (!anObj)
	return;
    
    if (!aKey || !anEntity) {
	aKey = nil;
	anEntity = nil;
    }
    
    if (!aKey && !aSnapshot)
	return;
    
    if (aKey && ![aKey isKindOfClass:[EOPrimaryKeyDictionary class]])
	THROW([[InvalidArgumentException alloc] 
	    initWithReason:@"attempt to record object with non "
			   @" EOPrimaryKeyDictionary class in EOObjectUniquer."
			   @"This is a bug in EODatabase/Context/Channel."]);

    keyRecord->pkey = aKey;
    keyRecord->entity = anEntity;
    
    rec = (EOUniquerRecord*)NSMapGet(objectsToRec, anObj);
    if (rec) {
	if (aKey && uniquerCompare(NULL, rec, keyRecord)) {
	    [aSnapshot retain];
	    [rec->snapshot release];
	    rec->snapshot = aSnapshot;
	    return;
	}
	if (aKey) {
	    orc = (EOUniquerRecord*)NSMapGet(primaryKeyToRec, keyRecord);
	    if (orc && orc != rec) {
		if (orc->pkey)
		    NSMapRemove(primaryKeyToRec, orc);
		NSMapRemove(objectsToRec, orc->object);
	    }
	    NSMapRemove(primaryKeyToRec, rec);
	}
	[aKey retain];
	[rec->pkey release];
	rec->pkey = aKey;
	[(id)anEntity retain];
	[rec->entity release];
	rec->entity = anEntity;
	[aSnapshot retain];
	[rec->snapshot release];
	rec->snapshot = aSnapshot;
	if (aKey)
	    NSMapInsertKnownAbsent(primaryKeyToRec, rec, rec);
	return;
    }
    
    if (aKey)
	rec = (EOUniquerRecord*)NSMapGet(primaryKeyToRec, keyRecord);
    if (rec) {
	if (rec->object == anObj) {
	    [aSnapshot retain];
	    [rec->snapshot release];
	    rec->snapshot = aSnapshot;
	    return;
	}
	NSMapRemove(objectsToRec, rec->object);
	[aSnapshot retain];
	[rec->snapshot release];
	rec->snapshot = aSnapshot;
	rec->object = anObj;
	NSMapInsertKnownAbsent(objectsToRec, anObj, rec);
	return;
    }
    
    rec = uniquerCreate(aKey, anEntity, anObj, aSnapshot);
    if (aKey)
	NSMapInsertKnownAbsent(primaryKeyToRec, rec, rec);
    NSMapInsertKnownAbsent(objectsToRec, anObj, rec);
}

@end /* EOObjectUniquer */

