// TEST_CFLAGS -Wno-deprecated-declarations -Wl,-no_objc_category_merging

#include "test.h"
#include "testroot.i"
#include <objc/runtime.h>

static int state;

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

@interface Super : TestRoot <Proto> { 
    int i;
} 
@property int i;
@end

@implementation Super 
@synthesize i;

+(void)classMethod { 
    state = 1;
}

-(void)instanceMethod {
    state = 3;
}

@end


#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
#endif

@implementation Super (Category)

+(void)classMethod { 
    state = 2;
}

-(void)instanceMethod {
    state = 4;
}

@end

#if __clang__
#pragma clang diagnostic pop
#endif


int main()
{
    Class clone;
    Class cls;
    Method *m1, *m2;
    int i;

    cls = [Super class];
    clone = objc_duplicateClass(cls, "Super_copy", 0);

    testassert(clone != cls);
    testassert(object_getClass(clone) == object_getClass(cls));
    testassert(class_getSuperclass(clone) == class_getSuperclass(cls));
    testassert(class_getVersion(clone) == class_getVersion(cls));
    testassert(class_isMetaClass(clone) == class_isMetaClass(cls));
    testassert(class_getIvarLayout(clone) == class_getIvarLayout(cls));
    testassert(class_getWeakIvarLayout(clone) == class_getWeakIvarLayout(cls));

    // Check method list

    m1 = class_copyMethodList(cls, NULL);
    m2 = class_copyMethodList(clone, NULL);
    testassert(m1);
    testassert(m2);
    for (i = 0; m1[i]  &&  m2[i]; i++) {
        testassert(m1[i] != m2[i]);  // method list must be deep-copied
        testassert(method_getName(m1[i]) == method_getName(m2[i]));
        testassert(method_getImplementation(m1[i]) == method_getImplementation(m2[i]));
        testassert(method_getTypeEncoding(m1[i]) == method_getTypeEncoding(m2[i]));
    }
    testassert(m1[i] == NULL  &&  m2[i] == NULL);
    free(m1);
    free(m2);

    // Check ivar list
    Ivar *i1 = class_copyIvarList(cls, NULL);
    Ivar *i2 = class_copyIvarList(clone, NULL);
    testassert(i1);
    testassert(i2);
    for (i = 0; i1[i]  &&  i2[i]; i++) {
        testassert(i1[i] == i2[i]);  // ivars are not deep-copied
    }
    testassert(i1[i] == NULL  &&  i2[i] == NULL);
    free(i1);
    free(i2);

    // Check protocol list
    Protocol * __unsafe_unretained *p1 = class_copyProtocolList(cls, NULL);
    Protocol * __unsafe_unretained *p2 = class_copyProtocolList(clone, NULL);
    testassert(p1);
    testassert(p2);
    for (i = 0; p1[i]  &&  p2[i]; i++) {
        testassert(p1[i] == p2[i]);  // protocols are not deep-copied
    }
    testassert(p1[i] == NULL  &&  p2[i] == NULL);
    free(p1);
    free(p2);

    // Check property list
    objc_property_t *o1 = class_copyPropertyList(cls, NULL);
    objc_property_t *o2 = class_copyPropertyList(clone, NULL);
    testassert(o1);
    testassert(o2);
    for (i = 0; o1[i]  &&  o2[i]; i++) {
        testassert(o1[i] == o2[i]);  // properties are not deep-copied
    }
    testassert(o1[i] == NULL  &&  o2[i] == NULL);
    free(o1);
    free(o2);

    // Check method calls

    state = 0;
    [cls classMethod];
    testassert(state == 2);
    state = 0;
    [clone classMethod];
    testassert(state == 2);

    // #4511660 Make sure category implementation is still the preferred one
    id obj;
    obj = [cls new];
    state = 0;
    [obj instanceMethod];
    testassert(state == 4);
    RELEASE_VAR(obj);

    obj = [clone new];
    state = 0;
    [obj instanceMethod];
    testassert(state == 4);
    RELEASE_VAR(obj);

    succeed(__FILE__);
}
