/* 
   EOFault.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 <objc/Object.h>
#include <objc/Protocol.h>

#include <Foundation/NSObject.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSMethodSignature.h>
#include <Foundation/NSInvocation.h>

#include <extensions/NSException.h>
#include <extensions/objc-runtime.h>
#include <extensions/exceptions/GeneralExceptions.h>

#include <eoaccess/EOEntity.h>
#include <eoaccess/exceptions/EOFExceptions.h>

#include "EOFault.h"
#include "EOFaultResolver.h"
#include "EODatabaseChannel.h"

typedef struct {
	Class isa;
} *my_objc_object;

#define object_is_instance(object) \
    ((object!=nil)&&CLS_ISCLASS(((my_objc_object)object)->isa))

/*
 * EOFault class
 */

@implementation EOFault

// Fault class methods

+ objectFaultWithPrimaryKey:(NSDictionary*)key
  entity:(EOEntity*)entity 
  databaseChannel:(EODatabaseChannel*)channel
  zone:(NSZone*)zone
{
    EOFault* fault;
    
    fault = [channel allocateObjectForRow:key entity:entity zone:zone];
    
    if (!fault)
	return nil;
    
    if ([fault class]->instance_size < ((Class)self)->instance_size) {
	[fault autorelease];
	THROW([[InvalidArgumentException alloc]
	    initWithFormat:
	    @"Instances from class %@ must be at least %d in size to fault",
	    NSStringFromClass([fault class]), ((Class)self)->instance_size]);
    }
    fault->faultResolver = [[EOObjectFault alloc] initWithPrimaryKey:key
    	entity:entity databaseChannel:channel zone:zone 
	targetClass:fault->isa fault:fault];
    fault->isa = self;
    
    return [fault autorelease];
}

+ (NSArray*)arrayFaultWithQualifier:(EOQualifier*)qualifier 
  fetchOrder:(NSArray*)fetchOrder 
  databaseChannel:(EODatabaseChannel*)channel
  zone:(NSZone*)zone
{
    EOFault* fault;
    
    fault = [NSMutableArray allocWithZone:zone];

    if ([fault class]->instance_size < ((Class)(self))->instance_size) {
	[fault autorelease];
	THROW([[InvalidArgumentException alloc]
		initWithFormat:
		    @"Instances from class %s must be at least %d "
		    @"in size to fault",
		    NSStringFromClass([fault class]),
		    ((Class)self)->instance_size]);
    }
    fault->faultResolver = [[EOArrayFault alloc] initWithQualifier:qualifier
    	fetchOrder:fetchOrder databaseChannel:channel zone:zone 
	targetClass:fault->isa fault:fault];
    fault->isa = self;

    return [fault autorelease];
}

+ (GCArray*)gcArrayFaultWithQualifier:(EOQualifier*)qualifier 
  fetchOrder:(NSArray*)fetchOrder 
  databaseChannel:(EODatabaseChannel*)channel
  zone:(NSZone*)zone
{
    EOFault* fault;
    
    fault = [GCMutableArray allocWithZone:zone];

    if ([fault class]->instance_size < ((Class)(self))->instance_size) {
	[fault autorelease];
	THROW([[InvalidArgumentException alloc]
		initWithFormat:
		    @"Instances from class %s must be at least %d "
		    @"in size to fault",
		    NSStringFromClass([fault class]),
		    ((Class)self)->instance_size]);
    }
    fault->faultResolver = [[EOArrayFault alloc] initWithQualifier:qualifier
    	fetchOrder:fetchOrder databaseChannel:channel zone:zone 
	targetClass:fault->isa fault:fault];
    fault->isa = self;

    return [fault autorelease];
}

+ (void)clearFault:fault
{
    BOOL gcEnabled=NO;
    unsigned gcCountainedObjectRefCount=0;
    EOFault* aFault = (EOFault*)fault;
    int refs;
    
    // check if fault
    if (aFault->isa != self)
	return;
    gcEnabled=aFault->faultResolver->gcEnabled;
    gcCountainedObjectRefCount=aFault->faultResolver->gcCountainedObjectRefCount;
    
    // get fault instance reference count + 1 set in creation methods
    refs = aFault->faultResolver->faultReferences;
    
    // make clear instance
    aFault->isa = [aFault->faultResolver targetClass];
    [aFault->faultResolver autorelease];
    aFault->faultResolver = nil;

    // set references to real instance so that
    // re-implemented retain/release mechanism take control
	NSAssert(refs>=0,@"gcCountainedObjectRefCount=0");
    while(refs-- > 0)
    	[aFault retain];
    if (gcEnabled)
	  {
	    [aFault gcIncrementRefCount];
	    [aFault gcSetNextObject:[self gcNextObject]];
	    [aFault gcSetPreviousObject:[self gcPreviousObject]];
	    while(gcCountainedObjectRefCount-- > 0)
    	  [aFault gcIncrementRefCountOfContainedObjects];
	  };
}

+ (BOOL)isFault:fault
{
    EOFault* aFault = (EOFault*)fault;
    
    return aFault->isa == self;
}

+ (Class)targetClassForFault:fault
{
    EOFault* aFault = (EOFault*)fault;

    // Check that argument is fault
    if (aFault->isa != self)
	return nil;
    
    return [aFault->faultResolver targetClass];
}

+ (NSDictionary*)primaryKeyForFault:fault
{
    EOFault* aFault = (EOFault*)fault;

    // Check that argument is fault
    if (aFault->isa != self)
	return nil;
    
    return [aFault->faultResolver primaryKey];
}

+ (EOEntity*)entityForFault:fault
{
    EOFault* aFault = (EOFault*)fault;

    // Check that argument is fault
    if (aFault->isa != self)
	return nil;

    return [aFault->faultResolver entity];
}

+ (EOQualifier*)qualifierForFault:fault
{
    EOFault* aFault = (EOFault*)fault;

    // Check that argument is fault
    if (aFault->isa != self)
	return nil;
    
    return [aFault->faultResolver qualifier];
}

+ (NSArray*)fetchOrderForFault:fault
{
    EOFault* aFault = (EOFault*)fault;

    // Check that argument is fault
    if (aFault->isa != self)
	return nil;
    
    return [aFault->faultResolver fetchOrder];
}

+ (EODatabaseChannel*)databaseChannelForFault:fault
{
    EOFault* aFault = (EOFault*)fault;

    // Check that argument is fault
    if (aFault->isa != self)
	return nil;
    
    return [aFault->faultResolver databaseChannel];
}

// Fault Instance methods

- superclass
{
    // For instance
    if (object_is_instance(self)) {
	return [[faultResolver targetClass] superclass];
    }
    // For class
    else {
	return object_get_super_class(self); 
    }
}

+ (Class)class
{
    return self;
}

- (Class)class
{
    // For instance
    if (object_is_instance(self)) {
	return [faultResolver targetClass];
    }
    // For class
    else {
	return object_get_class(self); 
    }
}

- (BOOL)isKindOfClass:(Class)aClass;
{
    Class class;

    // For instance
    if (object_is_instance(self)) {
	class = [faultResolver targetClass];
    }
    // For class
    else {
	class = (Class)self;
    }

    for(; class != Nil; class = class_get_super_class(class))
	if(class == aClass)
	    return YES;
    return NO;
}

- (BOOL)isMemberOfClass:(Class)aClass
{
    Class class;

    // For instance
    if (object_is_instance(self)) {
	class = [faultResolver targetClass];
    }
    // For class
    else {
	class = (Class)self;
    }

    return class == aClass;
}

- (BOOL)conformsToProtocol:(Protocol*)aProtocol
{
    int i;
    struct objc_protocol_list* protos;
    Class class, sClass;

    // For instance
    if (object_is_instance(self)) {
	class = [faultResolver targetClass];
    }
    // For class
    else {
	class = (Class)self;
    }

    for(protos = class->protocols; protos; protos = protos->next) {
	    for(i = 0; i < protos->count; i++)
		if([protos->list[i] conformsTo:aProtocol])
		    return YES;
    }

    sClass = [class superclass];
    if(sClass)
	return [sClass conformsToProtocol: aProtocol];
    else return NO;
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    Class class;
    
    // For instance
    if (object_is_instance(self)) {
	class = [faultResolver targetClass];
    }
    // For class
    else {
	class = (Class)self;
    }

    return (IMP)class_get_instance_method(class, aSelector) != (IMP)0;
}

+ self
{
    return self;
}

- retain
{
    // For instance
    if (object_is_instance(self)) {
	faultResolver->faultReferences++;
    }
    return self;
}

- (oneway void)release
{
    // For instance
    if (object_is_instance(self)) {
	if (faultResolver->faultReferences <= 0)
	    [self dealloc];
	else
	    faultResolver->faultReferences--;
    }
}

- (unsigned)retainCount
{
    // For instance
    if (object_is_instance(self)) {
	return faultResolver->faultReferences+1;
    }
    // For class
    else {
	return 1;
    }
}

- autorelease
{
    // For instance
    if (object_is_instance(self)) {
	[NSAutoreleasePool addObject:self];
    }
    return self;
}

- (NSZone*)zone
{
    // For instance
    if (object_is_instance(self)) {
	return NSZoneFromPointer(self);
    }
    // For class
    else {
	return NSZoneFromPointer(self);
    }
}

- (BOOL)isProxy
{
    return NO;
}
/*
- (BOOL)isGarbageCollectable
{
    return NO;
}
*/
- (void)dealloc
{
    // For instance
    if (object_is_instance(self)) {
	[faultResolver release];
	NSDeallocateObject((NSObject*)self);
    }
}

- (NSString*)description
{
    NSString* str;
    // For instance
    if (object_is_instance(self)) {
	str = [faultResolver description];
    }
    // For class
    else {
	str = NSStringFromClass((Class)self);
    }
    return str;
}

- (NSString*)descriptionWithIndent:(unsigned)level
{
    return [self description];
}

- (NSString*)stringRepresentation
{
    return [self description];
}

// Forwarding stuff

+ (void)initialize
{
    // Must be here as initialize is called for each root class
    // without asking if it responds to it !
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
   EOFaultResolver* resolver = faultResolver;

   // If in class
   if (!object_is_instance(self)) {
      THROW([[InvalidArgumentException alloc]
           initWithFormat:@"EOFault class does not responds to %s'",
           sel_get_name([anInvocation selector])]);
   }

   [resolver beforeFault];
   [resolver fault];
   [resolver afterFault];

   if (isa == [EOFault class]) {
      THROW([[InvalidArgumentException alloc]
           initWithFormat:
           @"fault error: %@ was not cleared during fault fetching",
    [resolver description]]);
   }

   [anInvocation invokeWithTarget: self];

}

- (retval_t)forward:(SEL)sel :(arglist_t)args
{
    struct objc_method* m;
    EOFaultResolver* resolver = faultResolver;

    // If in class
    if (!object_is_instance(self)) {
	THROW([[InvalidArgumentException alloc]
		initWithFormat:@"EOFault class does not responds to `%s'", 
		sel_get_name(sel)]);
    }

    [resolver beforeFault];
    [resolver fault];
    [resolver afterFault];
    
    if (isa == [EOFault class]) {
	THROW([[InvalidArgumentException alloc]
		initWithFormat:
		    @"fault error: %@ was not cleared during fault fetching",
		    [resolver description]]);
    }
    
    m = class_get_instance_method(self->isa, sel);
    
    if (!m) {
	THROW([[InvalidArgumentException alloc]
		initWithFormat:
		    @"fault error: %@ does not responds to selector %s",
		    [resolver description], sel_get_name(sel)]);
    }
    
    return objc_msg_sendv(self, sel, args);
}

- gcSetNextObject:(id)anObject
{
  return [faultResolver gcSetNextObject:anObject];
}

- gcSetPreviousObject:(id)anObject
{
  return [faultResolver gcSetPreviousObject:anObject];
}

- (id)gcNextObject
{
  return [faultResolver gcNextObject];
};
- (id)gcPreviousObject
{
  return [faultResolver gcPreviousObject];
}

-(BOOL)gcAlreadyVisited
{
  return [faultResolver gcAlreadyVisited];
}

- (void)gcSetVisited:(BOOL)flag
{
  [faultResolver gcSetVisited:flag];
}

- (void)gcDecrementRefCountOfContainedObjects
{
  [faultResolver gcDecrementRefCountOfContainedObjects];
}

- (BOOL)gcIncrementRefCountOfContainedObjects
{
  return [faultResolver gcIncrementRefCountOfContainedObjects];
}

- (BOOL)isGarbageCollectable
{
  return [faultResolver isGarbageCollectable];
}

- (void)gcIncrementRefCount
{
  [faultResolver gcIncrementRefCount];
}

- (void)gcDecrementRefCount
{
  [faultResolver gcDecrementRefCount];
}

@end /* EOFault */

/*
 * Informal protocol that informs an instance that a to-one
 * relationship could not be resoved to get data for self.
 * Its implementation in NSObject raises NSObjectNotAvailableException. 
 */

@implementation NSObject(EOUnableToFaultToOne)
- (void)unableToFaultWithPrimaryKey:(NSDictionary*)key 
  entity:(EOEntity*)entity 
  databaseChannel:(EODatabaseChannel*)channel
{
    // TODO - throw exception form derived class
    THROW([[ObjectNotAvailableException alloc]
	    initWithFormat:@"cannot fault to-one for primary key %@ entity %@",
			    [key description], [entity name]]);
}
@end

