//
//  FLEXMirror.m
//  FLEX
//
//  Derived from MirrorKit.
//  Created by Tanner on 6/29/15.
//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
//

#import "FLEXMirror.h"
#import "FLEXProperty.h"
#import "FLEXMethod.h"
#import "FLEXIvar.h"
#import "FLEXProtocol.h"
#import "FLEXUtility.h"


#pragma mark FLEXMirror

@implementation FLEXMirror

- (id)init {
    [NSException
        raise:NSInternalInconsistencyException
        format:@"Class instance should not be created with -init"
    ];
    return nil;
}

#pragma mark Initialization
+ (instancetype)reflect:(id)objectOrClass {
    return [[self alloc] initWithValue:objectOrClass];
}

- (id)initWithValue:(id)value {
    NSParameterAssert(value);
    
    self = [super init];
    if (self) {
        _value = value;
        [self examine];
    }
    
    return self;
}

- (NSString *)description {
    NSString *type = self.isClass ? @"metaclass" : @"class";
    return [NSString
        stringWithFormat:@"<%@ %@=%@, %lu properties, %lu ivars, %lu methods, %lu protocols>",
        NSStringFromClass(self.class),
        type,
        self.className,
        (unsigned long)self.properties.count,
        (unsigned long)self.ivars.count,
        (unsigned long)self.methods.count,
        (unsigned long)self.protocols.count
    ];
}

- (void)examine {
    // cls is a metaclass if self.value is a class
    Class cls = object_getClass(self.value);
    
    unsigned int pcount, mcount, ivcount, pccount;
    objc_property_t *objcproperties     = class_copyPropertyList(cls, &pcount);
    Protocol*__unsafe_unretained *procs = class_copyProtocolList(cls, &pccount);
    Method *objcmethods                 = class_copyMethodList(cls, &mcount);
    Ivar *objcivars                     = class_copyIvarList(cls, &ivcount);
    
    _className = NSStringFromClass(cls);
    _isClass   = class_isMetaClass(cls); // or object_isClass(self.value)
    
    NSMutableArray *properties = [NSMutableArray new];
    for (int i = 0; i < pcount; i++)
        [properties addObject:[FLEXProperty property:objcproperties[i]]];
    _properties = properties;
    
    NSMutableArray *methods = [NSMutableArray new];
    for (int i = 0; i < mcount; i++)
        [methods addObject:[FLEXMethod method:objcmethods[i]]];
    _methods = methods;
    
    NSMutableArray *ivars = [NSMutableArray new];
    for (int i = 0; i < ivcount; i++)
        [ivars addObject:[FLEXIvar ivar:objcivars[i]]];
    _ivars = ivars;
    
    NSMutableArray *protocols = [NSMutableArray new];
    for (int i = 0; i < pccount; i++)
        [protocols addObject:[FLEXProtocol protocol:procs[i]]];
    _protocols = protocols;
    
    // Cleanup
    free(objcproperties);
    free(objcmethods);
    free(objcivars);
    free(procs);
    procs = NULL;
}

#pragma mark Misc

- (FLEXMirror *)superMirror {
    return [FLEXMirror reflect:[self.value superclass]];
}

@end


#pragma mark ExtendedMirror

@implementation FLEXMirror (ExtendedMirror)

- (id)filter:(NSArray *)array forName:(NSString *)name {
    NSPredicate *filter = [NSPredicate predicateWithFormat:@"%K = %@", @"name", name];
    return [array filteredArrayUsingPredicate:filter].firstObject;
}

- (FLEXMethod *)methodNamed:(NSString *)name {
    return [self filter:self.methods forName:name];
}

- (FLEXProperty *)propertyNamed:(NSString *)name {
    return [self filter:self.properties forName:name];
}

- (FLEXIvar *)ivarNamed:(NSString *)name {
    return [self filter:self.ivars forName:name];
}

- (FLEXProtocol *)protocolNamed:(NSString *)name {
    return [self filter:self.protocols forName:name];
}

@end
