// TEST_CFLAGS -Wno-deprecated-declarations

#include "test.h"

#include "testroot.i"
#include <objc/runtime.h>
#include <string.h>
#ifndef OBJC_NO_GC
#include <objc/objc-auto.h>
#include <auto_zone.h>
#endif

@protocol Proto
-(void) instanceMethod;
+(void) classMethod;
@optional
-(void) instanceMethod2;
+(void) classMethod2;
@end

@protocol Proto2
-(void) instanceMethod;
+(void) classMethod;
@optional
-(void) instanceMethod2;
+(void) classMethod_that_does_not_exist;
@end

@protocol Proto3
-(void) instanceMethod;
+(void) classMethod_that_does_not_exist;
@optional
-(void) instanceMethod2;
+(void) classMethod2;
@end

static int super_initialize;

@interface Super : TestRoot
@property int superProp;
@end
@implementation Super 
@dynamic superProp;
+(void)initialize { super_initialize++; } 

+(void) classMethod { fail("+[Super classMethod] called"); }
+(void) classMethod2 { fail("+[Super classMethod2] called"); }
-(void) instanceMethod { fail("-[Super instanceMethod] called"); }
-(void) instanceMethod2 { fail("-[Super instanceMethod2] called"); }
@end

@interface WeakSuper : Super { __weak id weakIvar; } @end
@implementation WeakSuper @end

static int state;

static void instance_fn(id self, SEL _cmd __attribute__((unused)))
{
    testassert(!class_isMetaClass(object_getClass(self)));
    state++;
}

static void class_fn(id self, SEL _cmd __attribute__((unused)))
{
    testassert(class_isMetaClass(object_getClass(self)));
    state++;
}

static void fail_fn(id self __attribute__((unused)), SEL _cmd)
{
    fail("fail_fn '%s' called", sel_getName(_cmd));
}


static void cycle(void)
{    
    Class cls;
    BOOL ok;
    objc_property_t prop;
    char namebuf[256];
    
    testassert(!objc_getClass("Sub"));
    testassert([Super class]);

    // Test subclass with bells and whistles
    
    cls = objc_allocateClassPair([Super class], "Sub", 0);
    testassert(cls);
#ifndef OBJC_NO_GC
    if (objc_collectingEnabled()) {
        testassert(auto_zone_size(objc_collectableZone(), objc_unretainedPointer(cls)));
        testassert(auto_zone_size(objc_collectableZone(), objc_unretainedPointer(object_getClass(cls))));
    }
#endif
    
    class_addMethod(cls, @selector(instanceMethod), 
                    (IMP)&instance_fn, "v@:");
    class_addMethod(object_getClass(cls), @selector(classMethod), 
                    (IMP)&class_fn, "v@:");
    class_addMethod(object_getClass(cls), @selector(initialize), 
                    (IMP)&class_fn, "v@:");
    class_addMethod(object_getClass(cls), @selector(load), 
                    (IMP)&fail_fn, "v@:");

    ok = class_addProtocol(cls, @protocol(Proto));
    testassert(ok);
    ok = class_addProtocol(cls, @protocol(Proto));
    testassert(!ok);

    char attrname[2];
    char attrvalue[2];
    objc_property_attribute_t attrs[1];
    unsigned int attrcount = sizeof(attrs) / sizeof(attrs[0]);

    attrs[0].name = attrname;
    attrs[0].value = attrvalue;
    strcpy(attrname, "T");
    strcpy(attrvalue, "x");

    strcpy(namebuf, "subProp");
    ok = class_addProperty(cls, namebuf, attrs, attrcount);
    testassert(ok);
    strcpy(namebuf, "subProp");
    ok = class_addProperty(cls, namebuf, attrs, attrcount);
    testassert(!ok);
    strcpy(attrvalue, "i");
    class_replaceProperty(cls, namebuf, attrs, attrcount);
    strcpy(namebuf, "superProp");
    ok = class_addProperty(cls, namebuf, attrs, attrcount);
    testassert(!ok);
    bzero(namebuf, sizeof(namebuf));
    bzero(attrs, sizeof(attrs));
    bzero(attrname, sizeof(attrname));
    bzero(attrvalue, sizeof(attrvalue));

#ifndef __LP64__
# define size 4
# define align 2
#else
#define size 8
# define align 3
#endif

    /*
      {
        int ivar;
        id ivarid;
        id* ivaridstar;
        Block_t ivarblock;
      }
    */
    ok = class_addIvar(cls, "ivar", 4, 2, "i");
    testassert(ok);
    ok = class_addIvar(cls, "ivarid", size, align, "@");
    testassert(ok);
    ok = class_addIvar(cls, "ivaridstar", size, align, "^@");
    testassert(ok);
    ok = class_addIvar(cls, "ivarblock", size, align, "@?");
    testassert(ok);

    ok = class_addIvar(cls, "ivar", 4, 2, "i");
    testassert(!ok);
    ok = class_addIvar(object_getClass(cls), "classvar", 4, 2, "i");
    testassert(!ok);

    objc_registerClassPair(cls);

    // should call cls's +initialize, not super's
    // Provoke +initialize using class_getMethodImplementation(class method)
    //   in order to test getNonMetaClass's slow case
    super_initialize = 0;
    state = 0;
    class_getMethodImplementation(object_getClass(cls), @selector(class));
    testassert(super_initialize == 0);
    testassert(state == 1);

    testassert(cls == [cls class]);
    testassert(cls == objc_getClass("Sub"));

    testassert(!class_isMetaClass(cls));
    testassert(class_isMetaClass(object_getClass(cls)));

    testassert(class_getSuperclass(cls) == [Super class]);
    testassert(class_getSuperclass(object_getClass(cls)) == object_getClass([Super class]));

    testassert(class_getInstanceSize(cls) >= sizeof(Class) + 4 + 3*size);
    testassert(class_conformsToProtocol(cls, @protocol(Proto)));

    if (objc_collectingEnabled()) {
        testassert(0 == strcmp((char *)class_getIvarLayout(cls), "\x01\x13"));
        testassert(NULL == class_getWeakIvarLayout(cls));
    }

    class_addMethod(cls, @selector(instanceMethod2), 
                    (IMP)&instance_fn, "v@:");
    class_addMethod(object_getClass(cls), @selector(classMethod2), 
                    (IMP)&class_fn, "v@:");

    ok = class_addIvar(cls, "ivar2", 4, 4, "i");
    testassert(!ok);
    ok = class_addIvar(object_getClass(cls), "classvar2", 4, 4, "i");
    testassert(!ok);

    ok = class_addProtocol(cls, @protocol(Proto2));
    testassert(ok);
    ok = class_addProtocol(cls, @protocol(Proto2));
    testassert(!ok);
    ok = class_addProtocol(cls, @protocol(Proto));
    testassert(!ok);

    attrs[0].name = attrname;
    attrs[0].value = attrvalue;
    strcpy(attrname, "T");
    strcpy(attrvalue, "i");

    strcpy(namebuf, "subProp2");
    ok = class_addProperty(cls, namebuf, attrs, attrcount);
    testassert(ok);
    strcpy(namebuf, "subProp");
    ok = class_addProperty(cls, namebuf, attrs, attrcount);
    testassert(!ok);
    strcpy(namebuf, "superProp");
    ok = class_addProperty(cls, namebuf, attrs, attrcount);
    testassert(!ok);
    bzero(namebuf, sizeof(namebuf));
    bzero(attrs, sizeof(attrs));
    bzero(attrname, sizeof(attrname));
    bzero(attrvalue, sizeof(attrvalue));

    prop = class_getProperty(cls, "subProp");
    testassert(prop);
    testassert(0 == strcmp(property_getName(prop), "subProp"));
    testassert(0 == strcmp(property_getAttributes(prop), "Ti"));
    prop = class_getProperty(cls, "subProp2");
    testassert(prop);
    testassert(0 == strcmp(property_getName(prop), "subProp2"));
    testassert(0 == strcmp(property_getAttributes(prop), "Ti"));

    // note: adding more methods here causes a false leak check failure
    state = 0;
    [cls classMethod];
    [cls classMethod2];
    testassert(state == 2);

    // put instance tests on a separate thread so they 
    // are reliably GC'd before class destruction
    testonthread(^{
        id obj = [cls new];
        state = 0;
        [obj instanceMethod];
        [obj instanceMethod2];
        testassert(state == 2);
        RELEASE_VAR(obj);
    });

    // Test ivar layouts of sub-subclass
    Class cls2 = objc_allocateClassPair(cls, "SubSub", 0);
    testassert(cls2);

    /*
      {
        id ivarid2;
        id idarray[16];
        void* ptrarray[16];
        char a;
        char b;
        char c;
      }
    */
    ok = class_addIvar(cls2, "ivarid2", size, align, "@");
    testassert(ok);
    ok = class_addIvar(cls2, "idarray", 16*sizeof(id), align, "[16@]");
    testassert(ok);
    ok = class_addIvar(cls2, "ptrarray", 16*sizeof(void*), align, "[16^]");
    testassert(ok);
    ok = class_addIvar(cls2, "a", 1, 0, "c");
    testassert(ok);    
    ok = class_addIvar(cls2, "b", 1, 0, "c");
    testassert(ok);    
    ok = class_addIvar(cls2, "c", 1, 0, "c");
    testassert(ok);    

    objc_registerClassPair(cls2);

    if (objc_collectingEnabled()) {
        testassert(0 == strcmp((char *)class_getIvarLayout(cls2), "\x01\x1f\x05\xf0\x10"));
        testassert(NULL == class_getWeakIvarLayout(cls2));
    }

    // 1-byte ivars should be well packed
    testassert(ivar_getOffset(class_getInstanceVariable(cls2, "b")) == 
               ivar_getOffset(class_getInstanceVariable(cls2, "a")) + 1);
    testassert(ivar_getOffset(class_getInstanceVariable(cls2, "c")) == 
               ivar_getOffset(class_getInstanceVariable(cls2, "b")) + 1);

    testcollect();  // GC: finalize "obj" above before disposing its class
    objc_disposeClassPair(cls2);
    objc_disposeClassPair(cls);
    
    testassert(!objc_getClass("Sub"));


    // Test unmodified ivar layouts

    cls = objc_allocateClassPair([Super class], "Sub2", 0);
    testassert(cls);
    objc_registerClassPair(cls);
    if (objc_collectingEnabled()) {
        const char *l1, *l2;
        l1 = (char *)class_getIvarLayout([Super class]);
        l2 = (char *)class_getIvarLayout(cls);
        testassert(l1 == l2  ||  0 == strcmp(l1, l2));
        l1 = (char *)class_getWeakIvarLayout([Super class]);
        l2 = (char *)class_getWeakIvarLayout(cls);
        testassert(l1 == l2  ||  0 == strcmp(l1, l2));
    }
    objc_disposeClassPair(cls);

    cls = objc_allocateClassPair([WeakSuper class], "Sub3", 0);
    testassert(cls);
    objc_registerClassPair(cls);
    if (objc_collectingEnabled()) {
        const char *l1, *l2;
        l1 = (char *)class_getIvarLayout([WeakSuper class]);
        l2 = (char *)class_getIvarLayout(cls);
        testassert(l1 == l2  ||  0 == strcmp(l1, l2));
        l1 = (char *)class_getWeakIvarLayout([WeakSuper class]);
        l2 = (char *)class_getWeakIvarLayout(cls);
        testassert(l1 == l2  ||  0 == strcmp(l1, l2));
    }
    objc_disposeClassPair(cls);

    // Test layout setters
    if (objc_collectingEnabled()) {
        cls = objc_allocateClassPair([Super class], "Sub4", 0);
        testassert(cls);
        class_setIvarLayout(cls, (uint8_t *)"foo");
        class_setWeakIvarLayout(cls, NULL);
        objc_registerClassPair(cls);
        testassert(0 == strcmp("foo", (char *)class_getIvarLayout(cls)));
        testassert(NULL == class_getWeakIvarLayout(cls));
        objc_disposeClassPair(cls);

        cls = objc_allocateClassPair([Super class], "Sub5", 0);
        testassert(cls);
        class_setIvarLayout(cls, NULL);
        class_setWeakIvarLayout(cls, (uint8_t *)"bar");
        objc_registerClassPair(cls);
        testassert(NULL == class_getIvarLayout(cls));
        testassert(0 == strcmp("bar", (char *)class_getWeakIvarLayout(cls)));
        objc_disposeClassPair(cls);
    }
}

int main()
{
    cycle();
    cycle();

    int count = 1000;
    leak_mark();
    while (count--) {
        testonthread(^{ cycle(); });
    }
    leak_check(256);  // fixme should be 0

    succeed(__FILE__);
}

