//
// BeanUtils.m
// GotoBus
//
// Created by haibara on 1/19/15.
//
//

#import "BeanUtils.h"
#import <objc/message.h>
#import <BlocksKit/BlocksKit.h>
#import <MAObjCRuntime/MARTNSObject.h>
#import <MAObjCRuntime/RTProperty.h>
#import <MAObjCRuntime/RTMethod.h>

@implementation BeanUtils

#pragma mark - Private

+ (NSArray *)getPropertyList:(Class)beanClass accessible:(BOOL)accessible simple:(BOOL)simple parentsUtil:(Class)superClass {
	NSMutableArray *properties = [NSMutableArray array];
	superClass = superClass ? superClass : [NSObject class];
	Class thisClass = beanClass;
	while (thisClass != superClass && [thisClass isSubclassOfClass:superClass]) {
		[properties addObjectsFromArray:[thisClass rt_properties]];
		thisClass = [thisClass superclass];
	}
	[properties bk_performSelect:^BOOL (RTProperty *property) {
		return property.ivarName;
	}];
	if (accessible) {
		[properties bk_performSelect:^BOOL (RTProperty *property) {
			return !property.isReadOnly;
		}];
	}
	if (simple) {
		[properties bk_performSelect:^BOOL (RTProperty *property) {
			return ![property.typeEncoding hasPrefix:@"@"] || [property.typeEncoding isEqualToString:@"@\"NSNull\""] || [property.typeEncoding isEqualToString:@"@\"NSString\""] || [property.typeEncoding isEqualToString:@"@\"NSNumber\""];
		}];
	}
	return [NSArray arrayWithArray:properties];
}

+ (id)internalDescribe:(id)bean {
	if ([bean isKindOfClass:[NSArray class]] || [bean isKindOfClass:[NSSet class]] || [bean isKindOfClass:[NSOrderedSet class]]) {
		return [bean bk_map:^id (id obj) {
			return [self internalDescribe:obj];
		}];
	} else if ([bean isKindOfClass:[NSDictionary class]] || [bean isKindOfClass:[NSMapTable class]]) {
		return [bean bk_map:^id (id key, id obj) {
			return [self internalDescribe:obj];
		}];
	} else {
		return [bean description];
	}
	return [self describe:bean];
}

#pragma mark - Public

+ (NSDictionary *)describe:(id)bean {
	NSMutableArray *keys = [NSMutableArray arrayWithArray:[self keysForAccessiblePropertyWithClass:[bean class]]];
	if (keys.count == 0) {
		return nil;
	}
	NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[self describeAccessibleSimple:bean]];
	[keys removeObjectsInArray:dict.allKeys];
	for (NSString *key in keys) {
		dict[key] = [self internalDescribe:[bean valueForKey:key]];
	}
	return [NSDictionary dictionaryWithDictionary:dict];
}

+ (void)populate:(id)bean properties:(NSDictionary *)properties {
	[bean setValuesForKeysWithDictionary:properties];
}

+ (void)copyProperties:(id)dest origin:(id)orig {
	[self populate:dest properties:[self describeAccessible:orig]];
}

+ (NSArray *)keysForAccessiblePropertyWithClass:(Class)beanClass {
	return [[self getPropertyList:beanClass accessible:YES simple:NO parentsUtil:nil] valueForKeyPath:@"name"];
}

+ (NSArray *)keysForAccessibleSimplePropertyWithClass:(Class)beanClass {
	return [[self getPropertyList:beanClass accessible:YES simple:YES parentsUtil:nil] valueForKeyPath:@"name"];
}

+ (NSDictionary *)describe:(id)bean keys:(NSArray *)keys {
	return [bean dictionaryWithValuesForKeys:keys];
}

+ (NSDictionary *)describeAccessible:(id)bean {
	return [self describe:bean keys:[self keysForAccessiblePropertyWithClass:[bean class]]];
}

+ (NSDictionary *)describeAccessibleSimple:(id)bean {
	return [self describe:bean keys:[self keysForAccessibleSimplePropertyWithClass:[bean class]]];
}

+ (NSDictionary *)describeAccessibleSimple:(id)bean superClass:(Class)superClass {
	return [self describe:bean keys:[[self getPropertyList:[bean class] accessible:YES simple:YES parentsUtil:superClass] valueForKeyPath:@"name"]];
}

+ (id)copyWithZone:(NSZone *)zone origin:(id)orig {
	id dest = [[[orig class] allocWithZone:zone] init];
	for (RTProperty *property in [self getPropertyList:[orig class] accessible:YES simple:NO parentsUtil:nil]) {
		id bean = [orig valueForKey:property.name];
		if (![property.typeEncoding hasPrefix:@"@"]) {
			[dest setValue:bean forKey:property.name];
		} else if ([bean conformsToProtocol:@protocol(NSCopying)]) {
			[dest setValue:[bean copyWithZone:zone] forKey:property.name];
		}
	}
	return dest;
}

+ (id)copyWithZone:(NSZone *)zone origin:(id)orig superClass:(Class)superClass {
	struct objc_super s;
	s.receiver = orig;
	s.super_class = superClass;
	id dest = objc_msgSendSuper(&s, @selector(copyWithZone:), zone);
	for (RTProperty *property in [self getPropertyList:[orig class] accessible:YES simple:NO parentsUtil:superClass]) {
		id bean = [orig valueForKey:property.name];
		if (![property.typeEncoding hasPrefix:@"@"]) {
			[dest setValue:bean forKey:property.name];
		} else if ([bean conformsToProtocol:@protocol(NSCopying)]) {
			[dest setValue:[bean copyWithZone:zone] forKey:property.name];
		}
	}
	return dest;
}

@end
