/** 
   EOFault.m <title>EOFault Class</title>

   Copyright (C) 1996-2002,2003,2004,2005 Free Software Foundation, Inc.

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

   Author: Mirko Viviani <mirko.viviani@gmail.com>
   Date: June 2000

   $Revision: 26225 $
   $Date: 2008-03-06 07:33:48 -0600 (Thu, 06 Mar 2008) $

   <abstract></abstract>

   This file is part of the GNUstep Database Library.

   <license>
   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 3 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,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   </license>
**/

#include "config.h"

RCS_ID("$Id: EOFault.m 26225 2008-03-06 13:33:48Z ayers $")

#ifdef GNUSTEP
#include <Foundation/NSObject.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSInvocation.h>
#include <Foundation/NSThread.h>
#include <Foundation/NSException.h>
#include <Foundation/NSDebug.h>
#else
#include <Foundation/Foundation.h>
#endif

#ifndef GNUSTEP
#include <GNUstepBase/GNUstep.h>
#include <GNUstepBase/GSObjCRuntime.h>
#include <GNUstepBase/GSCategories.h>
#endif

#include <objc/Protocol.h>

#include <EOControl/EOFault.h>
#include <EOControl/EOKeyGlobalID.h>
#include <EOControl/EOEditingContext.h>
#include <EOControl/EODebug.h>

#include <limits.h>

/*
 * EOFault class
 */

@implementation EOFault

static Class EOFaultClass = NULL;

+ (void)initialize
{
  // Must be here as initialize is called for each root class
  // without asking if it responds to it !
  if (EOFaultClass == NULL)
    {
      GSMethod nsfwd = GSGetMethod([NSObject class],
				    @selector(forward::),
				    YES,NO);
      if (nsfwd != NULL)
        {
          GSMethod eofwd = GSGetMethod(self,
                                        @selector(forward::),
                                        YES,NO);
          eofwd->method_imp = nsfwd->method_imp;
          GSFlushMethodCacheForClass(self);
        }

      EOFaultClass = [EOFault class];
    }
}

+ (Class)superclass
{
  return GSObjCSuper(self);
}

+ (Class)class
{
  return self;
}

+ self
{
  return self;
}

+ (id)retain
{
  return self;
}

+ (void)release
{
  return;
}

+ (id)autorelease
{
  return self;
}

+ (unsigned)retainCount
{
  return UINT_MAX;
}

+ (BOOL)isKindOfClass: (Class)aClass
{
  if (aClass == EOFaultClass)
    return YES;

  return NO;
}

+ (void)doesNotRecognizeSelector: (SEL)selector
{
  [NSException raise: NSInvalidArgumentException
	       format: @"%@ -- %@ 0x%x: selector \"%@\" not recognized",
	       NSStringFromSelector(_cmd),
	       NSStringFromClass([self class]),
	       self,
	       NSStringFromSelector(selector)];
}

+ (BOOL)respondsToSelector: (SEL)selector
{
  return (GSGetMethod(self, selector, NO, YES) != (GSMethod)0);
}

/**
 * Returns a pointer to the C function implementing the method used
 * to respond to messages with selector by instances of the receiving
 * class.
 * <br />Raises NSInvalidArgumentException if given a null selector.
 *
 * It's a temporary fix to support NSAutoreleasePool optimization
 */
+ (IMP) instanceMethodForSelector: (SEL)selector
{
  if (selector == 0)
    [NSException raise: NSInvalidArgumentException
		format: @"%@ null selector given", NSStringFromSelector(_cmd)];
  /*
   *	Since 'self' is an class, get_imp() will get the instance method.
   */
  return get_imp((Class)self, selector);
}

// Fault class methods

+ (void)makeObjectIntoFault: (id)object
                withHandler: (EOFaultHandler *)handler
{
  if (object)
    {
      EOFault *fault = object;
      unsigned int refs;

      NSAssert(handler, @"No Handler");

      refs = [object retainCount];
      
      [handler setTargetClass: [object class]
               extraData: fault->_handler];
      fault->isa = self;
      fault->_handler = [handler retain];
      
      while (refs-- > 0)
        [fault retain];
    }
}

+ (BOOL)isFault: (id)object
{
  //See also EOPrivat.h
//  NSDebugFLLog(@"gsdb",@"object=%p",object);

  if (object == nil)
    return NO;
  else
    return ((EOFault *)object)->isa == self;
}

+ (void)clearFault: (id)fault
{
  EOFaultHandler *handler;
  EOFault *aFault = (EOFault *)fault;
  int refs = 0;

  NSDebugFLLog(@"gsdb", @"START fault=%p", fault);

  if ([EOFaultClass isFault:fault] == NO)
    {
//REVOIR!!!
/*
      [NSException raise:NSInvalidArgumentException
                   format:@"%@ -- %@ 0x%x: object %@ of class %@ is not a fault object", 
                   NSStringFromSelector(_cmd), 
                   NSStringFromClass([self class]),
                   self,
                   fault,
                   [fault class]];
*/
    }
  else
    {
      handler = aFault->_handler;
      
      [handler faultWillFire: fault];
      
      refs = [handler extraRefCount];

      aFault->isa = [handler targetClass];
      aFault->_handler = [handler extraData];

      [handler autorelease];

      refs -= [fault retainCount];

      if (refs > 0)
        while (refs-- > 0)
          [aFault retain];
      else
        while (refs++ < 0)
          [aFault release];
    }

  NSDebugFLLog(@"gsdb", @"STOP fault=%p", fault);
}

+ (EOFaultHandler *)handlerForFault:(id)fault
{
  BOOL isFault = [EOFaultClass isFault: fault];

  NSDebugFLLog(@"gsdb", @"object %p is%s a fault", fault,
	       (isFault ? "" : " not"));

  if (isFault)
    return ((EOFault *)fault)->_handler;
  else
    return nil;
}

+ (Class)targetClassForFault: (id)fault
{
  if ([EOFaultClass isFault:fault])
    return [((EOFault *)fault)->_handler targetClass];
  else
    return nil;
}


// Fault Instance methods

- (Class) superclass
{
  return [[_handler targetClass] superclass];
}

- (Class)class
{
  return [_handler targetClass];
}

- (BOOL)isKindOfClass: (Class)aClass
{
  Class class;
  BOOL koc=NO;

  class = [_handler targetClass];

  for (; !koc && class != Nil; class = GSObjCSuper(class))
    if (class == aClass)
      koc = YES;

  return koc;
}

- (BOOL)isMemberOfClass: (Class)aClass
{
  return [_handler targetClass] == aClass;
}

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

  class = [_handler targetClass];

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

  sClass = [class superclass];

  if (sClass)
    return [sClass conformsToProtocol: protocol];
  else
    return NO;
}

- (BOOL)respondsToSelector: (SEL)selector
{
  Class class;
  BOOL respondsToSelector;

  NSDebugFLLog(@"gsdb", @"START self=%p", self);

  class = [_handler targetClass];
  NSDebugFLLog(@"gsdb", @"class=%@ selector=%@", class,
	       NSStringFromSelector(selector));

  respondsToSelector 
    = (GSGetMethod(class, selector, YES, YES) != (GSMethod)0);
  NSDebugFLLog(@"gsdb", @"STOP self=%p", self);

  return respondsToSelector;
}

- (NSMethodSignature *)methodSignatureForSelector: (SEL)selector
{
  NSMethodSignature *sig;

  NSDebugFLLog(@"gsdb", @"START self=%p", self);
  NSDebugFLLog(@"gsdb", @"_handler=%p", _handler);

  sig = [_handler methodSignatureForSelector: selector
		  forFault: self];

  NSDebugFLLog(@"gsdb", @"STOP self=%p", self);

  return sig;
}

- retain
{
  [_handler incrementExtraRefCount];

  return self;
}

- (void)release
{
  if ([_handler extraRefCount] <= 0)
    [self dealloc];
  else
    [_handler decrementExtraRefCountIsZero];
}

- autorelease
{
  [NSAutoreleasePool addObject: self];

  return self;
}

- (unsigned)retainCount
{
  return [_handler extraRefCount];
}

- (NSString *)description
{
  return [_handler descriptionForObject: self];
}

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

- (NSString *)descriptionWithLocale: (NSDictionary *)locale
{
  //OK
  return [self description];
}

- (NSString *)descriptionWithLocale: (NSDictionary *)locale
			     indent: (unsigned)level
{
  return [self description];
}

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

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

- (EOKeyGlobalID *)globalID
{
  if ([_handler respondsToSelector: @selector(globalID)])
    return [(id)_handler globalID];
  else
    {
      [_handler completeInitializationOfObject: self];
      return [self globalID];
    }
}

- (EOEditingContext *)editingContext
{
  return [_handler editingContext];
}

/*
- (EOKeyGlobalID *)sourceGlobalID;
- (NSString *)relationshipName;
*/

- (void)dealloc
{
#ifdef DEBUG
  NSDebugFLog(@"Dealloc EOFault %p. %@",
              (void*)self,GSCurrentThread());
#endif
  [EOFaultClass clearFault: self];
  NSDebugMLog(@"EOFault dealloc self=%p",self);
  if (![EOFaultClass isFault:self]) // otherwise, this loop. 
    [self dealloc];
#ifdef DEBUG
  NSDebugFLog(@"Stop Dealloc EOFault %p. %@",
              (void*)self,GSCurrentThread());
#endif
}

- (NSZone *)zone
{
  return NSZoneFromPointer(self);
}

- (BOOL)isProxy
{
  return NO;
}

- (id)self
{
  [_handler completeInitializationOfObject: self];

  return self;
}

- (void)doesNotRecognizeSelector: (SEL)selector
{
  [NSException raise: NSInvalidArgumentException
               format: @"%@ -- %@ 0x%x: selector \"%@\" not recognized",
               NSStringFromSelector(_cmd),
               NSStringFromClass([self class]),
               self,
               NSStringFromSelector(selector)];
}

/**
 * This method is replaced by the current implementation of NSObject (if any).
 *
 * It is assumed that this method will not be called if NSObject does not
 * implement it.
 */
- (retval_t)forward: (SEL)selector
                   : (arglist_t)args
{
  retval_t ret = 0;
  NSAssert(NO,@"This should never be called (see +initialize).");
  return ret;
}

- (void)forwardInvocation: (NSInvocation *)invocation
{
  NSDebugFLLog(@"gsdb",@"invocation selector=%@ target: %p",
               NSStringFromSelector([invocation selector]),
               [invocation target]);

  if ([_handler shouldPerformInvocation: invocation])
    [_handler completeInitializationOfObject: self];

  [invocation invoke];
}

- (unsigned int)hash
{
  unsigned int hash;
  EOFaultHandler *handler;
  Class fault;

  fault = isa;
  handler = _handler;

  isa = [handler targetClass];
  _handler = [handler extraData];

  hash = [self hash];

  isa = fault;
  _handler = handler;

  return hash;
}

@end
