/* Test the Modern GNU Objective-C Runtime API.

  This is test 'class', covering all functions starting with 'class'.
  Tests calling the functions with a meta class as argument are covered
  in the separate file, gnu-api-2-class-meta.m.  */

/* { dg-do run } */
/* { dg-skip-if "No API#2 pre-Darwin9" { *-*-darwin[5-8]* } { "-fnext-runtime" } { "" } } */
/* { dg-xfail-run-if "Needs OBJC2 ABI" { *-*-darwin* && { lp64 && { ! objc2 } } } { "-fnext-runtime" } { "" } } */
/* { dg-additional-options "-DOBJC_OLD_DISPATCH_PROTOTYPES" { target { *-*-darwin* } } } */

/* To get the modern GNU Objective-C Runtime API, you include
   objc/runtime.h.  */
#include <objc/runtime.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

@interface MyRootClass
{ Class isa; }
+ alloc;
- init;
+ initialize;
@end

@implementation MyRootClass
+ alloc { return class_createInstance (self, 0); }
- init  { return self; }
+ initialize { return self; }
@end

@protocol MyProtocol
- (id) variable;
@end

@protocol MySecondProtocol
- (id) setVariable: (id)value;
@end

@interface MySubClass : MyRootClass <MyProtocol>
{ id variable_ivar; }
- (void) setVariable: (id)value;
- (id) variable;
@end

@implementation MySubClass
- (void) setVariable: (id)value { variable_ivar = value; }
- (id) variable { return variable_ivar; }
@end

@interface MyOtherSubClass : MySubClass
@end

@implementation MyOtherSubClass
@end

@interface DifferentClass : MyRootClass
- (id) myClass;
- (id) self;
@end

@implementation DifferentClass
- (id) myClass { return object_getClass (self); }
- (id) self { return self; }
@end

@interface MySubClass (MySelf)
- (id) mySelf;
@end

/* Hack to calculate the log2 of a byte alignment.  */
unsigned char
log_2_of (unsigned int x)
{
  unsigned char result = 0;

  /* We count how many times we need to divide by 2 before we reach 1.
     This algorithm is good enough for the small numbers (such as 8,
     16 or 64) that we have to deal with.  */
  while (x > 1)
    {
      x = x / 2;
      result++;
    }

  return result;
}

int main(int argc, void **args)
{
  /* Functions are tested in alphabetical order.  */

  printf ("Testing class_addIvar ()...\n");
  {
    Class new_class = objc_allocateClassPair (objc_getClass ("MySubClass"), "MySubSubClass", 0);

    if (new_class == Nil)
      abort ();
    
    if (! class_addIvar (new_class, "variable2_ivar", sizeof (id),
			 log_2_of (__alignof__ (id)), @encode (id)))
      abort ();

    if (! class_addIvar (new_class, "variable3_ivar", sizeof (unsigned char),
			 log_2_of (__alignof__ (unsigned char)), @encode (unsigned char)))
      abort ();

    if (! class_addIvar (new_class, "variable4_ivar", sizeof (unsigned long),
			 log_2_of (__alignof__ (unsigned long)), @encode (unsigned long)))
      abort ();

    objc_registerClassPair (new_class);    

    {
      MySubClass *o = [[(Class)objc_getClass ("MySubSubClass") alloc] init];
      Ivar variable2 = class_getInstanceVariable (objc_getClass ("MySubSubClass"), "variable2_ivar");
      Ivar variable3 = class_getInstanceVariable (objc_getClass ("MySubSubClass"), "variable3_ivar");
      Ivar variable4 = class_getInstanceVariable (objc_getClass ("MySubSubClass"), "variable4_ivar");

      if (variable2 == NULL  || variable3 == NULL  ||  variable4 == NULL)
	abort ();
      
      if (strcmp (ivar_getName (variable2), "variable2_ivar") != 0)
	abort ();

      if (strcmp (ivar_getName (variable3), "variable3_ivar") != 0)
	abort ();

      if (strcmp (ivar_getName (variable4), "variable4_ivar") != 0)
	abort ();

      {
	unsigned char *var3 = (unsigned char *)((char *)o + ivar_getOffset (variable3));
	unsigned long *var4 = (unsigned long *)((char *)o + ivar_getOffset (variable4));

	object_setIvar (o, variable2, new_class);
	*var3 = 230;
	*var4 = 89000L;

	if (object_getIvar (o, variable2) != new_class)
	  abort ();

	if (*var3 != 230)
	  abort ();

	if (*var4 != 89000L)
	  abort ();
      }
    }
  }

  printf ("Testing class_addMethod ()...\n");
  {
    Class new_class = objc_allocateClassPair (objc_getClass ("MyRootClass"), "MySubClass2", 0);
    Method method1 = class_getInstanceMethod (objc_getClass ("MySubClass"), @selector (setVariable:));
    Method method2 = class_getInstanceMethod (objc_getClass ("MySubClass"), @selector (variable));

    if (new_class == Nil)
      abort ();
    
    if (! class_addIvar (new_class, "variable_ivar", sizeof (id),
			 log_2_of (__alignof__ (id)), @encode (id)))
      abort ();

    if (! class_addMethod (new_class, @selector (setVariable:), method_getImplementation (method1),
			   method_getTypeEncoding (method1)))
      abort ();

    if (! class_addMethod (new_class, @selector (variable), method_getImplementation (method2),
			   method_getTypeEncoding (method2)))
      abort ();

    /* Test that if the method already exists in the class,
       class_addMethod() returns NO.  */
    if (class_addMethod (new_class, @selector (variable), method_getImplementation (method2),
			 method_getTypeEncoding (method2)))
      abort ();

    objc_registerClassPair (new_class);    

    /* Now, MySubClass2 is basically the same as MySubClass!  We'll
       use the variable and setVariable: methods on it.  */
    {
      MySubClass *o = (MySubClass *)[[(Class)objc_getClass ("MySubClass2") alloc] init];

      [o setVariable: o];

      if ([o variable] != o)
	abort ();
    }

    /* Now, try that if you take an existing class and try to add an
       already existing method, class_addMethod returns NO.  This is
       subtly different from before, when 'new_class' was still in
       construction.  Now it's a real class and the libobjc internals
       differ between the two cases.  */
    if (class_addMethod (new_class, @selector (variable), method_getImplementation (method2),
			 method_getTypeEncoding (method2)))
      abort ();
  }

  printf ("Testing class_addProtocol ()...\n");
  {
    if (!class_addProtocol (objc_getClass ("MySubClass"), @protocol (MySecondProtocol)))
      abort ();
    
    if (!class_conformsToProtocol (objc_getClass ("MySubClass"), @protocol (MyProtocol)))
      abort ();

    if (!class_conformsToProtocol (objc_getClass ("MySubClass"), @protocol (MySecondProtocol)))
      abort ();
  }

  printf ("Testing class_conformsToProtocol ()...\n");
  {
    if (class_conformsToProtocol (objc_getClass ("MyRootClass"), @protocol (MyProtocol)))
      abort ();

    if (!class_conformsToProtocol (objc_getClass ("MySubClass"), @protocol (MyProtocol)))
      abort ();

    /* Test that class_conformsToProtocol checks the class, but not
       superclasses.  */
    if (class_conformsToProtocol (objc_getClass ("MyOtherSubClass"), @protocol (MyProtocol)))
      abort ();
  }

  printf ("Testing class_copyIvarList ()...\n");
  {
    unsigned int count;
    Ivar * list = class_copyIvarList (objc_getClass ("MySubClass"), &count);

    if (count != 1)
      abort ();

    if (strcmp (ivar_getName (list[0]), "variable_ivar") != 0)
      abort ();
    
    if (list[1] != NULL)
      abort ();
  }

  printf ("Testing class_copyIvarList () on class with no instance variables...\n");
  {
    unsigned int count;
    Ivar * list = class_copyIvarList (objc_getClass ("MyOtherSubClass"),
                                      &count);

    if (count != 0)
      abort ();
    
    if (list != NULL)
      abort ();
  }

  printf ("Testing class_copyMethodList ()...\n");
  {
    unsigned int count;
    Method * list = class_copyMethodList (objc_getClass ("MySubClass"), &count);

    if (count != 2)
      abort ();
    
    if (! ((strcmp (sel_getName (method_getName (list[0])), "variable") == 0
	    && strcmp (sel_getName (method_getName (list[1])), "setVariable:") == 0)
	   || (strcmp (sel_getName (method_getName (list[0])), "setVariable:") == 0
	       && strcmp (sel_getName (method_getName (list[1])), "variable") == 0)))
      abort ();
    
    if (list[2] != NULL)
      abort ();
  }

  /* TODO: Test new ABI (when available).  */
  printf ("Testing class_copyPropertyList ()...\n");
  {
    unsigned int count;
    objc_property_t * list = class_copyPropertyList (objc_getClass ("MySubClass"), &count);

    if (count != 0  ||  list != NULL)
      abort ();
  }

  printf ("Testing class_copyProtocolList ()...\n");
  {
    unsigned int count;
    Protocol ** list = class_copyProtocolList (objc_getClass ("MySubClass"), &count);

    /* Remember that we added MySecondProtocol in the test above.  */
    if (count != 2)
      abort ();

    if (! ((strcmp (protocol_getName (list[0]), "MyProtocol") == 0
	    && strcmp (protocol_getName (list[1]), "MySecondProtocol") == 0)
	   || (strcmp (protocol_getName (list[0]), "MySecondProtocol") == 0
	       && strcmp (protocol_getName (list[1]), "MyProtocol") == 0)))
      abort ();
    
    if (list[2] != NULL)
      abort ();
  }

  printf ("Testing class_createInstance ()...\n");
  {
    MySubClass *object = [[MySubClass alloc] init];

    [object setVariable: object];
    if ([object variable] != object)
      abort ();
  }

  printf ("Testing class_getClassMethod ()...\n");
  {
    Method method = class_getClassMethod (objc_getClass ("MySubClass"),
					  @selector(alloc));

    if (method == NULL)
      abort ();

    if (strcmp (sel_getName (method_getName (method)), "alloc") != 0)
      abort ();

    if (class_getClassMethod (objc_getClass ("MySubClass"), 
			      @selector(variable)))
      abort ();
  }

  printf ("Testing class_getClassVariable ()...\n");
  {
    if (class_getClassVariable (objc_getClass ("MySubClass"), "variable_ivar"))
      abort ();
  }

  printf ("Testing class_getInstanceMethod ()...\n");
  {
    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"), 
					     @selector(variable));

    if (method == NULL)
      abort ();

    if (strcmp (sel_getName (method_getName (method)), "variable") != 0)
      abort ();

    if (class_getInstanceMethod (objc_getClass ("MySubClass"), 
				 @selector(alloc)))
      abort ();
  }

  printf ("Testing class_getInstanceSize ()...\n");
  {
    if (class_getInstanceSize (objc_getClass ("MyRootClass")) != sizeof (struct objc_object))
      abort ();
  }

  printf ("Testing class_getInstanceVariable ()...\n");
  {
    Ivar variable = class_getInstanceVariable (objc_getClass ("MySubClass"), "variable_ivar");

    if (variable == NULL)
      abort ();

    if (strcmp (ivar_getName (variable), "variable_ivar") != 0)
      abort ();

    if (class_getInstanceVariable (objc_getClass ("MySubClass"), "variable_ivar_no"))
      abort ();
  }

  printf ("Testing class_getIvarLayout ()...\n");
  {
    if (class_getIvarLayout (objc_getClass ("MyRootClass")) != NULL)
      abort ();
  }

  printf ("Testing class_getMethodImplementation ()...\n");
  {
    MySubClass *object = [[MySubClass alloc] init];
    IMP imp = class_getMethodImplementation (objc_getClass ("MySubClass"), 
					     @selector(variable));

    if (imp == NULL)
      abort ();

    [object setVariable: object];

    if ((*imp)(object, @selector(variable)) != object)
      abort ();
  }

  /* This function does not exist with the GNU runtime.  */
  /* printf ("Testing class_getMethodImplementation_stret ()...\n"); */

  printf ("Testing class_getName ()...\n");
  {
    if (strcmp (class_getName (objc_getClass ("MyRootClass")),
		"MyRootClass") != 0)
      abort ();
  }

  /* TODO: Test new ABI (when available).  */
  printf ("Testing class_getProperty ()...\n");
  {
    if (class_getProperty (objc_getClass ("MyRootClass"), "property") != NULL)
      abort ();
  }

  printf ("Testing class_getSuperclass ()...\n");
  {
    MySubClass *object = [[MySubClass alloc] init];
    if (class_getSuperclass (object_getClass (object)) != objc_getClass ("MyRootClass"))
      abort ();

    /* Test that it works on a newly created, but not registered, class.  */
    {
      Class new_class = objc_allocateClassPair (objc_getClass ("MyRootClass"), "MySubClass3", 0);

      if (class_getSuperclass (new_class) != objc_getClass ("MyRootClass"))
	abort ();
    }
  }

  printf ("Testing class_getVersion ()...\n");
  {
    if (class_getVersion (objc_getClass ("MySubClass")) != 0)
      abort ();
  }

   printf ("Testing class_getWeakIvarLayout ()...\n");
  {
    if (class_getWeakIvarLayout (objc_getClass ("MyRootClass")) != NULL)
      abort ();
  }

  printf ("Testing class_isMetaClass ()...\n");
  {
    MySubClass *object = [[MySubClass alloc] init];
    if (class_isMetaClass (object_getClass (object)) 
	|| ! class_isMetaClass (object_getClass (object_getClass (object))))
      abort ();
  }

  printf ("Testing class_replaceMethod ()...\n");
  {
    Method new_method = class_getInstanceMethod (objc_getClass ("DifferentClass"),
						 @selector (myClass));
    Method old_method = class_getInstanceMethod (objc_getClass ("MySubClass"),
						 @selector (variable));
    const char *new_types = method_getTypeEncoding (new_method);
    IMP new_imp = method_getImplementation (new_method);
    const char *old_types = method_getTypeEncoding (old_method);
    IMP old_imp = class_replaceMethod (objc_getClass ("MySubClass"), @selector (variable),
				       method_getImplementation (new_method),
				       method_getTypeEncoding (new_method));
    MySubClass *o = [[MySubClass alloc] init];

    [o setVariable: o];

    /* Try the new method implementation.  */
    if ([o variable] != objc_getClass ("MySubClass"))
      abort ();

    /* Put the original method back.  */
    class_replaceMethod (objc_getClass ("MySubClass"), @selector (variable),
			 old_imp, old_types);

    /* Test it's back to what it was.  */
    if ([o variable] != o)
      abort ();    

    {
      DifferentClass *o = [[DifferentClass alloc] init];

      /* Finally, try adding a new method.  */
      class_replaceMethod (objc_getClass ("DifferentClass"), @selector (mySelf),
			   new_imp, new_types);
      
      if ([(MySubClass*)o mySelf] != objc_getClass ("DifferentClass"))
	abort ();
    }
  }

  printf ("Testing class_respondsToSelector ()...\n");
  {
    if (! class_respondsToSelector (objc_getClass ("MySubClass"), @selector(setVariable:)))
      abort ();

    if (class_respondsToSelector (objc_getClass ("MyRootClass"), @selector(setVariable:)))
      abort ();
  }

  /* This is not really implemented with the GNU runtime.  */
  /* printf ("Testing class_setIvarLayout ()...\n"); */

  printf ("Testing class_setVersion ()...\n");
  {
    class_setVersion (objc_getClass ("MySubClass"), 45);
    
    if (class_getVersion (objc_getClass ("MySubClass")) != 45)
      abort ();

    class_setVersion (objc_getClass ("MySubClass"), 46);

    if (class_getVersion (objc_getClass ("MySubClass")) != 46)
      abort ();
  }

  /* This is not really implemented with the GNU runtime.  */
  /* printf ("Testing class_setWeakIvarLayout ()...\n"); */

  return 0;
}
