/* 
   behavior.m

   Copyright (C) 1995, 1996, 1997 Ovidiu Predescu and Mircea Oancea.
   All rights reserved.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
   This file implements behaviors, "protocols with implementations",
   an original idea of Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>.

   This file is part of the Foundation Extensions Library.

   This implementation works with GNU and NeXT runtimes.

   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.

   We disclaim all warranties with regard to this software, including all
   implied warranties of merchantability and fitness, in no event shall
   we be liable for any special, indirect or consequential damages or any
   damages whatsoever resulting from loss of use, data or profits, whether in
   an action of contract, negligence or other tortious action, arising out of
   or in connection with the use or performance of this software.
*/

/*
   A Behavior can be seen as a "Protocol with an implementation" or a
   "Class without any instance variables".  A key feature of behaviors
   is that they give a degree of multiple inheritance.
   
   The following function is a sneaky hack way that provides Behaviors
   without adding any new syntax to the Objective C language.  Simply
   define a class with the methods you want in the behavior, then call
   this function with that class as the BEHAVIOR argument.
   
   This function should be called in CLASS's +initialize method.
  
   If you add several behaviors to a class, be aware that the order of 
   the additions is significant.
*/

#import <Foundation/NSString.h>

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

static void class_add_methods_if_not_there (Class, Class);

#if NeXT_RUNTIME
static struct objc_method*
search_for_method_in_list (struct objc_method_list**, SEL);
#else
static struct objc_method*
search_for_method_in_list (struct objc_method_list*, SEL);
#endif

static BOOL class_is_kind_of (Class, Class);

void class_add_behavior (Class class, Class behavior)
{
  Class behavior_super_class;

  if (!CLS_ISCLASS (class) || !CLS_ISCLASS (behavior))
    THROW ([[ObjcRuntimeException alloc] initWithFormat:
	    @"Only classes must be passed to class_add_behavior"]);

  class_add_methods_if_not_there (class, behavior);
  class_add_methods_if_not_there (class_get_meta_class (class),
				  class_get_meta_class (behavior));

  behavior_super_class = class_get_super_class (behavior);
  if (!class_is_kind_of (class, behavior_super_class))
    class_add_behavior (class, behavior_super_class);
}

static void class_add_methods_if_not_there (Class class, Class behavior)
{
  static SEL initialize_sel = 0;
  struct objc_method_list *mlist;
#if NeXT_RUNTIME
  int methodListIndex;
#endif

  if (!initialize_sel)
    initialize_sel = sel_register_name ("initialize");

#if NeXT_RUNTIME
  for (methodListIndex = 0;
       (mlist = behavior->methodLists[methodListIndex]);
       methodListIndex++)
#else
  for (mlist = behavior->methods; mlist; mlist = mlist->method_next)
#endif
    {
      int i;
      struct objc_method_list *new_list =
	Malloc (sizeof (struct objc_method_list)
		      + sizeof (struct objc_method[mlist->method_count - 1]));

#if !defined(NeXT_RUNTIME)
      new_list->method_next = NULL;
#endif
      new_list->method_count = 0;
      for (i = 0; i < mlist->method_count; i++)
	{
	  struct objc_method *behavior_method = &(mlist->method_list[i]);
	  struct objc_method *class_method =
#if NeXT_RUNTIME
	      search_for_method_in_list (class->methodLists,
					 behavior_method->method_name);
#else
	      search_for_method_in_list (class->methods,
					 behavior_method->method_name);
#endif
	  if (!class_method
	      && !SEL_EQ (behavior_method->method_name, initialize_sel))
	    {
	      /* As long as the method isn't defined in the CLASS, put the
		  BEHAVIOR method in there.  Thus, behavior methods override
		  the superclasses' methods. */
	      new_list->method_list[new_list->method_count++] =
		*behavior_method;
	    }
	}
      if (i)
	{
	  new_list = Realloc (new_list, sizeof (struct objc_method_list)
		+ sizeof (struct objc_method[new_list->method_count - 1]));
	  class_addMethods (class, new_list);
	}
      else
	Free (new_list);
  }
}

/* Given a linked list of method and a method's name.  Search for the named
   method's method structure.  Return a pointer to the method's method
   structure if found.  NULL otherwise. */
#if NeXT_RUNTIME
static struct objc_method *
search_for_method_in_list (struct objc_method_list **methodLists, SEL op)
#else
static struct objc_method *
search_for_method_in_list (struct objc_method_list *mlist, SEL op)
#endif
{
#if NeXT_RUNTIME
  int methodListIndex;
  struct objc_method_list *mlist;
#endif

  if (!sel_is_mapped (op))
    return NULL;

  /* If not found then we'll search the list.  */
#if NeXT_RUNTIME
  for (methodListIndex = 0;
       (mlist = methodLists[methodListIndex]);
       methodListIndex++)
#else
  for (; mlist; mlist = mlist->method_next)
#endif
    {
      int i;

      /* Search the method list.  */
      for (i = 0; i < mlist->method_count; ++i)
	{
	  struct objc_method *method = &mlist->method_list[i];

	  if (method->method_name)
	    if (SEL_EQ (method->method_name, op))
	      return method;
	}
    }

  return NULL;
}

static BOOL class_is_kind_of (Class a, Class b)
{
  for (; a; a = class_get_super_class (a))
    if (a == b)
      return YES;
  return NO;
}
