// TEST_CONFIG

// initialize.m
// Test basic +initialize behavior
// * +initialize before class method
// * superclass +initialize before subclass +initialize
// * subclass inheritance of superclass implementation
// * messaging during +initialize
// * +initialize provoked by class_getMethodImplementation
// * +initialize not provoked by objc_getClass
#include "test.h"
#include "testroot.i"

int state = 0;

@interface Super0 : TestRoot @end
@implementation Super0
+(void)initialize {
    fail("objc_getClass() must not trigger +initialize");
}
@end

@interface Super : TestRoot @end
@implementation Super 
+(void)initialize {
    testprintf("in [Super initialize]\n");
    testassert(state == 0);
    state = 1;
}
+(void)method { 
    fail("[Super method] shouldn't be called");
}
@end

@interface Sub : Super @end
@implementation Sub
+(void)initialize { 
    testprintf("in [Sub initialize]\n");
    testassert(state == 1);
    state = 2;
}
+(void)method { 
    testprintf("in [Sub method]\n");
    testassert(state == 2);
    state = 3;
}
@end


@interface Super2 : TestRoot @end
@interface Sub2 : Super2 @end

@implementation Super2
+(void)initialize { 
    if (self == objc_getClass("Sub2")) {
        testprintf("in [Super2 initialize] of Sub2\n");
        testassert(state == 1);
        state = 2;
    } else if (self == objc_getClass("Super2")) {
        testprintf("in [Super2 initialize] of Super2\n");
        testassert(state == 0);
        state = 1;
    } else {
        fail("in [Super2 initialize] of unknown class");
    }
}
+(void)method { 
    testprintf("in [Super2 method]\n");
    testassert(state == 2);
    state = 3;
}
@end

@implementation Sub2
// nothing here
@end


@interface Super3 : TestRoot @end
@interface Sub3 : Super3 @end

@implementation Super3
+(void)initialize { 
    if (self == [Sub3 class]) {  // this message triggers [Sub3 initialize]
        testprintf("in [Super3 initialize] of Sub3\n");
        testassert(state == 0);
        state = 1;
    } else if (self == [Super3 class]) {
        testprintf("in [Super3 initialize] of Super3\n");
        testassert(state == 1);
        state = 2;
    } else {
        fail("in [Super3 initialize] of unknown class");
    }
}
+(void)method { 
    testprintf("in [Super3 method]\n");
    testassert(state == 2);
    state = 3;
}
@end

@implementation Sub3
// nothing here
@end


@interface Super4 : TestRoot @end
@implementation Super4
-(void)instanceMethod { 
    testassert(state == 1);
    state = 2;
}
+(void)initialize {
    testprintf("in [Super4 initialize]\n");
    testassert(state == 0);
    state = 1;
    id x = [[self alloc] init];
    [x instanceMethod];
    RELEASE_VALUE(x);
}
@end


@interface Super5 : TestRoot @end
@implementation Super5
-(void)instanceMethod { 
}
+(void)classMethod {
    testassert(state == 1);
    state = 2;
}
+(void)initialize {
    testprintf("in [Super5 initialize]\n");
    testassert(state == 0);
    state = 1;
    class_getMethodImplementation(self, @selector(instanceMethod));
    // this is the "memoized" case for getNonMetaClass
    class_getMethodImplementation(object_getClass(self), @selector(classMethod));
    [self classMethod];
}
@end


@interface Super6 : TestRoot @end
@interface Sub6 : Super6 @end
@implementation Super6
+(void)initialize {
    static bool once;
    bool wasOnce;
    testprintf("in [Super6 initialize] (#%d)\n", 1+(int)once);
    if (!once) {
        once = true;
        wasOnce = true;
        testassert(state == 0);
        state = 1;
    } else {
        wasOnce = false;
        testassert(state == 2);
        state = 3;
    }
    [Sub6 class];
    if (wasOnce) {
        testassert(state == 5);
        state = 6;
    } else {
        testassert(state == 3);
        state = 4;
    }
}
@end
@implementation Sub6
+(void)initialize {
    testprintf("in [Sub6 initialize]\n");
    testassert(state == 1);
    state = 2;
    [super initialize];
    testassert(state == 4);
    state = 5;
}
@end


@interface Super7 : TestRoot @end
@interface Sub7 : Super7 @end
@implementation Super7
+(void)initialize {
    static bool once;
    bool wasOnce;
    testprintf("in [Super7 initialize] (#%d)\n", 1+(int)once);
    if (!once) {
        once = true;
        wasOnce = true;
        testassert(state == 0);
        state = 1;
    } else {
        wasOnce = false;
        testassert(state == 2);
        state = 3;
    }
    [Sub7 class];
    if (wasOnce) {
        testassert(state == 5);
        state = 6;
    } else {
        testassert(state == 3);
        state = 4;
    }
}
@end
@implementation Sub7
+(void)initialize {
    testprintf("in [Sub7 initialize]\n");
    testassert(state == 1);
    state = 2;
    [super initialize];
    testassert(state == 4);
    state = 5;
}
@end



@interface SuperThrower : TestRoot @end
@implementation SuperThrower
+(void)initialize {
    testprintf("in [SuperThrower initialize]\n");
    testassert(state == 0);
    state = 10;
    @throw AUTORELEASE([TestRoot new]);
    fail("@throw didn't throw");
}
@end

@interface SubThrower : SuperThrower @end
@implementation SubThrower
+(void)initialize {
    testprintf("in [SubThrower initialize]\n");
    testassert(state == 0);
    state = 20;
}
@end

int main()
{
    Class cls;

    // objc_getClass() must not +initialize anything
    state = 0;
    objc_getClass("Super0");
    testassert(state == 0);

    // initialize superclass, then subclass
    state = 0;
    [Sub method];
    testassert(state == 3);

    // check subclass's inheritance of superclass initialize
    state = 0;
    [Sub2 method];
    testassert(state == 3);

    // check subclass method called from superclass initialize
    state = 0;
    [Sub3 method];
    testassert(state == 3);

    // check class_getMethodImplementation (instance method)
    state = 0;
    cls = objc_getClass("Super4");
    testassert(state == 0);
    class_getMethodImplementation(cls, @selector(classMethod));
    testassert(state == 2);

    // check class_getMethodImplementation (class method)
    // this is the "slow" case for getNonMetaClass
    state = 0;
    cls = objc_getClass("Super5");
    testassert(state == 0);
    class_getMethodImplementation(object_getClass(cls), @selector(instanceMethod));
    testassert(state == 2);

    // check +initialize cycles
    // this is the "cls is a subclass" case for getNonMetaClass
    state = 0;
    [Super6 class];
    testassert(state == 6);

    // check +initialize cycles
    // this is the "cls is a subclass" case for getNonMetaClass
    state = 0;
    [Sub7 class];
    testassert(state == 6);

    // exception from +initialize must be handled cleanly
    PUSH_POOL {
        alarm(3);
        testonthread( ^{
            @try {
                state = 0;
                [SuperThrower class];
                fail("where's the beef^Wexception?");
            } @catch (...) {
                testassert(state == 10);
                state = 11;
            }
            testassert(state == 11);
        });
        @try {
            state = 0;
            [SuperThrower class];
            testassert(state == 0);
            [SubThrower class];
            testassert(state == 20);
        } @catch (...) {
            fail("+initialize called again after exception");
        }
    } POP_POOL;
    
    succeed(__FILE__);

    return 0;
}
