/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <stdlib.h>

#if defined(OF_APPLE_RUNTIME) || defined(OF_GNU_RUNTIME)
# import <objc/runtime.h>
#elif defined(OF_OLD_GNU_RUNTIME)
# import <objc/objc-api.h>
#endif

#import "OFIntrospection.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFAutoreleasePool.h"

#import "macros.h"

@implementation OFMethod
#if defined(OF_APPLE_RUNTIME) || defined(OF_GNU_RUNTIME)
- _initWithMethod: (Method)method
{
	self = [super init];

	@try {
		selector = method_getName(method);
		name = [[OFString alloc]
		    initWithCString: sel_getName(selector)
			   encoding: OF_STRING_ENCODING_ASCII];
		typeEncoding = method_getTypeEncoding(method);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
#elif defined(OF_OLD_GNU_RUNTIME)
- _initWithMethod: (Method_t)method
{
	self = [super init];

	@try {
		selector = method->method_name;
		name = [[OFString alloc]
		    initWithCString: sel_get_name(selector)
			   encoding: OF_STRING_ENCODING_ASCII];
		typeEncoding = method->method_types;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
#endif

- (void)dealloc
{
	[name release];

	[super dealloc];
}

- (SEL)selector
{
	return selector;
}

- (OFString*)name
{
	OF_GETTER(name, YES)
}

- (const char*)typeEncoding
{
	return typeEncoding;
}

- (OFString*)description
{
	return [OFString stringWithFormat: @"<OFMethod: %@ [%s]>",
					   name, typeEncoding];
}
@end

@implementation OFInstanceVariable
#if defined(OF_APPLE_RUNTIME) || defined(OF_GNU_RUNTIME)
- _initWithIvar: (Ivar)ivar
{
	self = [super init];

	@try {
		name = [[OFString alloc]
		    initWithCString: ivar_getName(ivar)
			   encoding: OF_STRING_ENCODING_ASCII];
		offset = ivar_getOffset(ivar);
		typeEncoding = ivar_getTypeEncoding(ivar);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
#elif defined(OF_OLD_GNU_RUNTIME)
- _initWithIvar: (Ivar_t)ivar
{
	self = [super init];

	@try {
		name = [[OFString alloc]
		    initWithCString: ivar->ivar_name
			   encoding: OF_STRING_ENCODING_ASCII];
		offset = ivar->ivar_offset;
		typeEncoding = ivar->ivar_type;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
#endif

- (void)dealloc
{
	[name release];

	[super dealloc];
}

- (OFString*)name
{
	OF_GETTER(name, YES);
}

- (ptrdiff_t)offset
{
	return offset;
}

- (const char*)typeEncoding
{
	return typeEncoding;
}

- (OFString*)description
{
	return [OFString stringWithFormat:
	    @"<OFInstanceVariable: %@ [%s] @ 0x%tx>",
	    name, typeEncoding, offset];
}
@end

#ifdef OF_HAVE_PROPERTIES
@implementation OFProperty
@synthesize name, attributes;

#if defined(OF_APPLE_RUNTIME) || defined(OF_GNU_RUNTIME)
- _initWithProperty: (objc_property_t)property
{
	self = [super init];

	@try {
		name = [[OFString alloc]
		    initWithCString: property_getName(property)
			   encoding: OF_STRING_ENCODING_ASCII];
		attributes = property_getAttributes(property);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
#endif

- (void)dealloc
{
	[name release];

	[super dealloc];
}

- (OFString*)description
{
	return [OFString stringWithFormat: @"<OFProperty %@ [%s]>",
					   name, attributes];
}
@end
#endif

@implementation OFIntrospection
#ifdef OF_HAVE_PROPERTIES
@synthesize properties;
#endif

+ introspectionWithClass: (Class)class
{
	return [[[self alloc] initWithClass: class] autorelease];
}

- initWithClass: (Class)class
{
	self = [super init];

	@try {
		OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
#if defined(OF_APPLE_RUNTIME) || defined(OF_GNU_RUNTIME)
		Method *methodList;
		Ivar *ivarList;
# ifdef OF_HAVE_PROPERTIES
		objc_property_t *propertyList;
# endif
		unsigned i, count;
#elif defined(OF_OLD_GNU_RUNTIME)
		MethodList_t methodList;
#endif

		classMethods = [[OFMutableArray alloc] init];
		instanceMethods = [[OFMutableArray alloc] init];
		instanceVariables = [[OFMutableArray alloc] init];
#ifdef OF_HAVE_PROPERTIES
		properties = [[OFMutableArray alloc] init];
#endif

#if defined(OF_APPLE_RUNTIME) || defined(OF_GNU_RUNTIME)
		methodList = class_copyMethodList(((OFObject*)class)->isa,
		    &count);
		@try {
			for (i = 0; i < count; i++) {
				[classMethods addObject: [[[OFMethod alloc]
				    _initWithMethod: methodList[i]]
				    autorelease]];
				[pool releaseObjects];
			}
		} @finally {
			free(methodList);
		}

		methodList = class_copyMethodList(class, &count);
		@try {
			for (i = 0; i < count; i++) {
				[instanceMethods addObject: [[[OFMethod alloc]
				    _initWithMethod: methodList[i]]
				    autorelease]];
				[pool releaseObjects];
			}
		} @finally {
			free(methodList);
		}

		ivarList = class_copyIvarList(class, &count);
		@try {
			for (i = 0; i < count; i++) {
				[instanceVariables addObject:
				    [[[OFInstanceVariable alloc]
				    _initWithIvar: ivarList[i]] autorelease]];
				[pool releaseObjects];
			}
		} @finally {
			free(ivarList);
		}

		propertyList = class_copyPropertyList(class, &count);
		@try {
			for (i = 0; i < count; i++) {
				[properties addObject:
				    [[[OFProperty alloc]
				    _initWithProperty: propertyList[i]]
				    autorelease]];
				[pool releaseObjects];
			}
		} @finally {
			free(propertyList);
		}
#elif defined(OF_OLD_GNU_RUNTIME)
		for (methodList = class->class_pointer->methods;
		    methodList != NULL; methodList = methodList->method_next) {
			int i;

			for (i = 0; i < methodList->method_count; i++) {
				[classMethods addObject: [[[OFMethod alloc]
				    _initWithMethod:
				    &methodList->method_list[i]] autorelease]];
				[pool releaseObjects];
			}
		}

		for (methodList = class->methods; methodList != NULL;
		    methodList = methodList->method_next) {
			int i;

			for (i = 0; i < methodList->method_count; i++) {
				[instanceMethods addObject: [[[OFMethod alloc]
				    _initWithMethod:
				    &methodList->method_list[i]] autorelease]];
				[pool releaseObjects];
			}
		}

		if (class->ivars != NULL) {
			int i;

			for (i = 0; i < class->ivars->ivar_count; i++) {
				[instanceVariables addObject:
				    [[[OFInstanceVariable alloc]
				    _initWithIvar: class->ivars->ivar_list + i]
				    autorelease]];
				[pool releaseObjects];
			}
		}
#endif

		[classMethods makeImmutable];
		[instanceMethods makeImmutable];
		[instanceVariables makeImmutable];
#ifdef OF_HAVE_PROPERTIES
		[properties makeImmutable];
#endif

		[pool release];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[classMethods release];
	[instanceMethods release];
	[instanceVariables release];
#ifdef OF_HAVE_PROPERTIES
	[properties release];
#endif

	[super dealloc];
}

- (OFArray*)classMethods
{
	OF_GETTER(classMethods, YES)
}

- (OFArray*)instanceMethods
{
	OF_GETTER(instanceMethods, YES)
}

- (OFArray*)instanceVariables
{
	OF_GETTER(instanceVariables, YES)
}
@end
