#ifdef SHOULD_COMPILE_LOOKIN_SERVER 

//
//  LKS_RequestHandler.m
//  LookinServer
//
//  Created by Li Kai on 2019/1/15.
//  https://lookin.work
//

#import "LKS_RequestHandler.h"
#import "NSObject+LookinServer.h"
#import "UIImage+LookinServer.h"
#import "LKS_ConnectionManager.h"
#import "LookinConnectionResponseAttachment.h"
#import "LookinAttributeModification.h"
#import "LookinDisplayItemDetail.h"
#import "LookinHierarchyInfo.h"
#import "LookinServerDefines.h"
#import <objc/runtime.h>
#import "LookinObject.h"
#import "LookinAppInfo.h"
#import "LKS_AttrGroupsMaker.h"
#import "LKS_InbuiltAttrModificationHandler.h"
#import "LKS_CustomAttrModificationHandler.h"
#import "LKS_AttrModificationPatchHandler.h"
#import "LKS_HierarchyDetailsHandler.h"
#import "LookinStaticAsyncUpdateTask.h"

@interface LKS_RequestHandler ()

@property(nonatomic, strong) NSMutableSet<LKS_HierarchyDetailsHandler *> *activeDetailHandlers;

@end

@implementation LKS_RequestHandler {
    NSSet *_validRequestTypes;
}

- (instancetype)init {
    if (self = [super init]) {
        _validRequestTypes = [NSSet setWithObjects:@(LookinRequestTypePing),
                              @(LookinRequestTypeApp),
                              @(LookinRequestTypeHierarchy),
                              @(LookinRequestTypeInbuiltAttrModification),
                              @(LookinRequestTypeCustomAttrModification),
                              @(LookinRequestTypeAttrModificationPatch),
                              @(LookinRequestTypeHierarchyDetails),
                              @(LookinRequestTypeFetchObject),
                              @(LookinRequestTypeAllAttrGroups),
                              @(LookinRequestTypeAllSelectorNames),
                              @(LookinRequestTypeInvokeMethod),
                              @(LookinRequestTypeFetchImageViewImage),
                              @(LookinRequestTypeModifyRecognizerEnable),
                              @(LookinPush_CanceHierarchyDetails),
                              nil];
        
        self.activeDetailHandlers = [NSMutableSet set];
    }
    return self;
}

- (BOOL)canHandleRequestType:(uint32_t)requestType {
    if ([_validRequestTypes containsObject:@(requestType)]) {
        return YES;
    }
    return NO;
}

- (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object {
    if (requestType == LookinRequestTypePing) {
        LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
        // 当 app 处于后台时，可能可以执行代码也可能不能执行代码，如果运气好了可以执行代码，则这里直接主动使用 appIsInBackground 标识 app 处于后台，不要让 Lookin 客户端傻傻地等待超时了
        if (![LKS_ConnectionManager sharedInstance].applicationIsActive) {
            responseAttachment.appIsInBackground = YES;            
        }
        [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
        
    } else if (requestType == LookinRequestTypeApp) {
        // 请求可用设备信息
        if (![object isKindOfClass:[NSDictionary class]]) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        NSDictionary<NSString *, id> *params = object;
        BOOL needImages = ((NSNumber *)params[@"needImages"]).boolValue;
        NSArray<NSNumber *> *localIdentifiers = params[@"local"];
        
        LookinAppInfo *appInfo = [LookinAppInfo currentInfoWithScreenshot:needImages icon:needImages localIdentifiers:localIdentifiers];
        
        LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
        responseAttachment.data = appInfo;
        [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
        
    } else if (requestType == LookinRequestTypeHierarchy) {
        // 从 LookinClient 1.0.4 开始有这个参数，之前是 nil
        NSString *clientVersion = nil;
        if ([object isKindOfClass:[NSDictionary class]]) {
            NSDictionary<NSString *, id> *params = object;
            NSString *version = params[@"clientVersion"];
            if ([version isKindOfClass:[NSString class]]) {
                clientVersion = version;
            }
        }
        
        LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
        responseAttachment.data = [LookinHierarchyInfo staticInfoWithLookinVersion:clientVersion];
        [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
        
    } else if (requestType == LookinRequestTypeInbuiltAttrModification) {
        // 请求修改某个属性
        [LKS_InbuiltAttrModificationHandler handleModification:object completion:^(LookinDisplayItemDetail *data, NSError *error) {
            LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
            if (error) {
                attachment.error = error;
            } else {
                attachment.data = data;
            }
            [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
        }];
        
    } else if (requestType == LookinRequestTypeCustomAttrModification) {
        BOOL succ = [LKS_CustomAttrModificationHandler handleModification:object];
        if (succ) {
            [self _submitResponseWithData:nil requestType:requestType tag:tag];
        } else {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
        }
        
    } else if (requestType == LookinRequestTypeAttrModificationPatch) {
        NSArray<LookinStaticAsyncUpdateTask *> *tasks = object;
        NSUInteger dataTotalCount = tasks.count;
        [LKS_InbuiltAttrModificationHandler handlePatchWithTasks:tasks block:^(LookinDisplayItemDetail *data) {
            LookinConnectionResponseAttachment *attrAttachment = [LookinConnectionResponseAttachment new];
            attrAttachment.data = data;
            attrAttachment.dataTotalCount = dataTotalCount;
            attrAttachment.currentDataCount = 1;
            [[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag];
        }];
        
    } else if (requestType == LookinRequestTypeHierarchyDetails) {
        NSArray<LookinStaticAsyncUpdateTasksPackage *> *packages = object;
        NSUInteger responsesDataTotalCount = [packages lookin_reduceInteger:^NSInteger(NSInteger accumulator, NSUInteger idx, LookinStaticAsyncUpdateTasksPackage *package) {
            accumulator += package.tasks.count;
            return accumulator;
        } initialAccumlator:0];
        
        LKS_HierarchyDetailsHandler *handler = [LKS_HierarchyDetailsHandler new];
        [self.activeDetailHandlers addObject:handler];
        
        [handler startWithPackages:packages block:^(NSArray<LookinDisplayItemDetail *> *details) {
            LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
            attachment.data = details;
            attachment.dataTotalCount = responsesDataTotalCount;
            attachment.currentDataCount = details.count;
            [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag];
            
        } finishedBlock:^{
            [self.activeDetailHandlers removeObject:handler];
        }];
        
    } else if (requestType == LookinRequestTypeFetchObject) {
        unsigned long oid = ((NSNumber *)object).unsignedLongValue;
        NSObject *object = [NSObject lks_objectWithOid:oid];
        LookinObject *lookinObj = [LookinObject instanceWithObject:object];
        
        LookinConnectionResponseAttachment *attach = [LookinConnectionResponseAttachment new];
        attach.data = lookinObj;
        [[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag];
        
    } else if (requestType == LookinRequestTypeAllAttrGroups) {
        unsigned long oid = ((NSNumber *)object).unsignedLongValue;
        CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid];
        if (![layer isKindOfClass:[CALayer class]]) {
            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:LookinRequestTypeAllAttrGroups tag:tag];
            return;
        }
        
        NSArray<LookinAttributesGroup *> *list = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
        [self _submitResponseWithData:list requestType:LookinRequestTypeAllAttrGroups tag:tag];
        
    } else if (requestType == LookinRequestTypeAllSelectorNames) {
        if (![object isKindOfClass:[NSDictionary class]]) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        NSDictionary *params = object;
        Class targetClass = NSClassFromString(params[@"className"]);
        BOOL hasArg = [(NSNumber *)params[@"hasArg"] boolValue];
        if (!targetClass) {
            NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"Didn't find the class named \"%@\". Please input another class and try again."), object];
            [self _submitResponseWithError:LookinErrorMake(errorMsg, @"") requestType:requestType tag:tag];
            return;
        }
        
        NSArray<NSString *> *selNames = [self _methodNameListForClass:targetClass hasArg:hasArg];
        [self _submitResponseWithData:selNames requestType:requestType tag:tag];
        
    } else if (requestType == LookinRequestTypeInvokeMethod) {
        if (![object isKindOfClass:[NSDictionary class]]) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        NSDictionary *param = object;
        unsigned long oid = [param[@"oid"] unsignedLongValue];
        NSString *text = param[@"text"];
        if (!text.length) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        NSObject *targerObj = [NSObject lks_objectWithOid:oid];
        if (!targerObj) {
            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
            return;
        }
        
        SEL targetSelector = NSSelectorFromString(text);
        if (targetSelector && [targerObj respondsToSelector:targetSelector]) {
            NSString *resultDescription;
            NSObject *resultObject;
            NSError *error;
            [self _handleInvokeWithObject:targerObj selector:targetSelector resultDescription:&resultDescription resultObject:&resultObject error:&error];
            if (error) {
                [self _submitResponseWithError:error requestType:requestType tag:tag];
                return;
            }
            NSMutableDictionary *responseData = [NSMutableDictionary dictionaryWithCapacity:2];
            if (resultDescription) {
                responseData[@"description"] = resultDescription;
            }
            if (resultObject) {
                responseData[@"object"] = resultObject;
            }
            [self _submitResponseWithData:responseData requestType:requestType tag:tag];
        } else {
            NSString *errMsg = [NSString stringWithFormat:LKS_Localized(@"%@ doesn't have an instance method called \"%@\"."), NSStringFromClass(targerObj.class), text];
            [self _submitResponseWithError:LookinErrorMake(errMsg, @"") requestType:requestType tag:tag];
        }
        
    } else if (requestType == LookinPush_CanceHierarchyDetails) {
        [self.activeDetailHandlers enumerateObjectsUsingBlock:^(LKS_HierarchyDetailsHandler * _Nonnull handler, BOOL * _Nonnull stop) {
            [handler cancel];
        }];
        [self.activeDetailHandlers removeAllObjects];
        
    } else if (requestType == LookinRequestTypeFetchImageViewImage) {
        if (![object isKindOfClass:[NSNumber class]]) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        unsigned long imageViewOid = [(NSNumber *)object unsignedLongValue];
        UIImageView *imageView = (UIImageView *)[NSObject lks_objectWithOid:imageViewOid];
        if (!imageView) {
            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
            return;
        }
        if (![imageView isKindOfClass:[UIImageView class]]) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        UIImage *image = imageView.image;
        NSData *imageData = [image lookin_data];
        [self _submitResponseWithData:imageData requestType:requestType tag:tag];
    
    } else if (requestType == LookinRequestTypeModifyRecognizerEnable) {
        if (![object isKindOfClass:[NSDictionary class]]) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        NSDictionary<NSString *, NSNumber *> *params = object;
        unsigned long recognizerOid = ((NSNumber *)params[@"oid"]).unsignedLongValue;
        BOOL shouldBeEnabled = ((NSNumber *)params[@"enable"]).boolValue;
        
        UIGestureRecognizer *recognizer = (UIGestureRecognizer *)[NSObject lks_objectWithOid:recognizerOid];
        if (!recognizer) {
            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
            return;
        }
        if (![recognizer isKindOfClass:[UIGestureRecognizer class]]) {
            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
            return;
        }
        recognizer.enabled = shouldBeEnabled;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // dispatch 以确保拿到的 enabled 是比较新的
            [self _submitResponseWithData:@(recognizer.enabled) requestType:requestType tag:tag];
        });
    }
}

- (NSArray<NSString *> *)_methodNameListForClass:(Class)aClass hasArg:(BOOL)hasArg {
    NSSet<NSString *> *prefixesToVoid = [NSSet setWithObjects:@"_", @"CA_", @"cpl", @"mf_", @"vs_", @"pep_", @"isNS", @"avkit_", @"PG_", @"px_", @"pl_", @"nsli_", @"pu_", @"pxg_", nil];
    NSMutableArray<NSString *> *array = [NSMutableArray array];
    
    Class currentClass = aClass;
    while (currentClass) {
        NSString *className = NSStringFromClass(currentClass);
        BOOL isSystemClass = ([className hasPrefix:@"UI"] || [className hasPrefix:@"CA"] || [className hasPrefix:@"NS"]);
        
        unsigned int methodCount = 0;
        Method *methods = class_copyMethodList(currentClass, &methodCount);
        for (unsigned int i = 0; i < methodCount; i++) {
            NSString *selName = NSStringFromSelector(method_getName(methods[i]));
            
            if (!hasArg && [selName containsString:@":"]) {
                continue;
            }
            
            if (isSystemClass) {
                BOOL invalid = [prefixesToVoid lookin_any:^BOOL(NSString *prefix) {
                    return [selName hasPrefix:prefix];
                }];
                if (invalid) {
                    continue;
                }
            }
            if (selName.length && ![array containsObject:selName]) {
                [array addObject:selName];
            }
        }
        if (methods) free(methods);
        currentClass = [currentClass superclass];
    }

    return [array lookin_sortedArrayByStringLength];
}

- (void)_handleInvokeWithObject:(NSObject *)obj selector:(SEL)selector resultDescription:(NSString **)description resultObject:(LookinObject **)resultObject error:(NSError **)error {
    NSMethodSignature *signature = [obj methodSignatureForSelector:selector];
    if (signature.numberOfArguments > 2) {
        *error = LookinErrorMake(LKS_Localized(@"Lookin doesn't support invoking methods with arguments yet."), @"");
        return;
    }
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:obj];
    [invocation setSelector:selector];
    [invocation invoke];

    const char *returnType = [signature methodReturnType];
    
    
    if (strcmp(returnType, @encode(void)) == 0) {
        //void, do nothing
        *description = LookinStringFlag_VoidReturn;
        
    } else if (strcmp(returnType, @encode(char)) == 0) {
        char charValue;
        [invocation getReturnValue:&charValue];
        *description = [NSString stringWithFormat:@"%@", @(charValue)];
        
    } else if (strcmp(returnType, @encode(int)) == 0) {
        int intValue;
        [invocation getReturnValue:&intValue];
        if (intValue == INT_MAX) {
            *description = @"INT_MAX";
        } else if (intValue == INT_MIN) {
            *description = @"INT_MIN";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(intValue)];
        }
        
    } else if (strcmp(returnType, @encode(short)) == 0) {
        short shortValue;
        [invocation getReturnValue:&shortValue];
        if (shortValue == SHRT_MAX) {
            *description = @"SHRT_MAX";
        } else if (shortValue == SHRT_MIN) {
            *description = @"SHRT_MIN";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(shortValue)];
        }
        
    } else if (strcmp(returnType, @encode(long)) == 0) {
        long longValue;
        [invocation getReturnValue:&longValue];
        if (longValue == NSNotFound) {
            *description = @"NSNotFound";
        } else if (longValue == LONG_MAX) {
            *description = @"LONG_MAX";
        } else if (longValue == LONG_MIN) {
            *description = @"LONG_MAX";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(longValue)];
        }
        
    } else if (strcmp(returnType, @encode(long long)) == 0) {
        long long longLongValue;
        [invocation getReturnValue:&longLongValue];
        if (longLongValue == LLONG_MAX) {
            *description = @"LLONG_MAX";
        } else if (longLongValue == LLONG_MIN) {
            *description = @"LLONG_MIN";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(longLongValue)];
        }
        
    } else if (strcmp(returnType, @encode(unsigned char)) == 0) {
        unsigned char ucharValue;
        [invocation getReturnValue:&ucharValue];
        if (ucharValue == UCHAR_MAX) {
            *description = @"UCHAR_MAX";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(ucharValue)];
        }
        
    } else if (strcmp(returnType, @encode(unsigned int)) == 0) {
        unsigned int uintValue;
        [invocation getReturnValue:&uintValue];
        if (uintValue == UINT_MAX) {
            *description = @"UINT_MAX";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(uintValue)];
        }
        
    } else if (strcmp(returnType, @encode(unsigned short)) == 0) {
        unsigned short ushortValue;
        [invocation getReturnValue:&ushortValue];
        if (ushortValue == USHRT_MAX) {
            *description = @"USHRT_MAX";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(ushortValue)];
        }
        
    } else if (strcmp(returnType, @encode(unsigned long)) == 0) {
        unsigned long ulongValue;
        [invocation getReturnValue:&ulongValue];
        if (ulongValue == ULONG_MAX) {
            *description = @"ULONG_MAX";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(ulongValue)];
        }
        
    } else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
        unsigned long long ulongLongValue;
        [invocation getReturnValue:&ulongLongValue];
        if (ulongLongValue == ULONG_LONG_MAX) {
            *description = @"ULONG_LONG_MAX";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(ulongLongValue)];
        }
        
    } else if (strcmp(returnType, @encode(float)) == 0) {
        float floatValue;
        [invocation getReturnValue:&floatValue];
        if (floatValue == FLT_MAX) {
            *description = @"FLT_MAX";
        } else if (floatValue == FLT_MIN) {
            *description = @"FLT_MIN";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(floatValue)];
        }
        
    } else if (strcmp(returnType, @encode(double)) == 0) {
        double doubleValue;
        [invocation getReturnValue:&doubleValue];
        if (doubleValue == DBL_MAX) {
            *description = @"DBL_MAX";
        } else if (doubleValue == DBL_MIN) {
            *description = @"DBL_MIN";
        } else {
            *description = [NSString stringWithFormat:@"%@", @(doubleValue)];
        }
        
    } else if (strcmp(returnType, @encode(BOOL)) == 0) {
        BOOL boolValue;
        [invocation getReturnValue:&boolValue];
        *description = boolValue ? @"YES" : @"NO";
        
    } else if (strcmp(returnType, @encode(SEL)) == 0) {
        SEL selValue;
        [invocation getReturnValue:&selValue];
        *description = [NSString stringWithFormat:@"SEL(%@)", NSStringFromSelector(selValue)];
        
    } else if (strcmp(returnType, @encode(Class)) == 0) {
        Class classValue;
        [invocation getReturnValue:&classValue];
        *description = [NSString stringWithFormat:@"<%@>", NSStringFromClass(classValue)];
        
    } else if (strcmp(returnType, @encode(CGPoint)) == 0) {
        CGPoint targetValue;
        [invocation getReturnValue:&targetValue];
        *description = NSStringFromCGPoint(targetValue);

    } else if (strcmp(returnType, @encode(CGVector)) == 0) {
        CGVector targetValue;
        [invocation getReturnValue:&targetValue];
        *description = NSStringFromCGVector(targetValue);

    } else if (strcmp(returnType, @encode(CGSize)) == 0) {
        CGSize targetValue;
        [invocation getReturnValue:&targetValue];
        *description = NSStringFromCGSize(targetValue);

    } else if (strcmp(returnType, @encode(CGRect)) == 0) {
        CGRect rectValue;
        [invocation getReturnValue:&rectValue];
        *description = NSStringFromCGRect(rectValue);
        
    } else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) {
        CGAffineTransform rectValue;
        [invocation getReturnValue:&rectValue];
        *description = NSStringFromCGAffineTransform(rectValue);
        
    } else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) {
        UIEdgeInsets targetValue;
        [invocation getReturnValue:&targetValue];
        *description = NSStringFromUIEdgeInsets(targetValue);
        
    } else if (strcmp(returnType, @encode(UIOffset)) == 0) {
        UIOffset targetValue;
        [invocation getReturnValue:&targetValue];
        *description = NSStringFromUIOffset(targetValue);
        
    } else {
        if (@available(iOS 11.0, tvOS 11.0, *)) {
            if (strcmp(returnType, @encode(NSDirectionalEdgeInsets)) == 0) {
                NSDirectionalEdgeInsets targetValue;
                [invocation getReturnValue:&targetValue];
                *description = NSStringFromDirectionalEdgeInsets(targetValue);
                return;
            }
        }
        
        NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType];
        if ([argType_string hasPrefix:@"@"] || [argType_string hasPrefix:@"^{"]) {
            __unsafe_unretained id returnObjValue;
            [invocation getReturnValue:&returnObjValue];
            
            if (returnObjValue) {
                *description = [NSString stringWithFormat:@"%@", returnObjValue];
                
                LookinObject *parsedLookinObj = [LookinObject instanceWithObject:returnObjValue];
                *resultObject = parsedLookinObj;
            } else {
                *description = @"nil";
            }
        } else {
            *description = [NSString stringWithFormat:LKS_Localized(@"%@ was invoked successfully, but Lookin can't parse the return value:%@"), NSStringFromSelector(selector), argType_string];
        }
    }
}

- (void)_submitResponseWithError:(NSError *)error requestType:(uint32_t)requestType tag:(uint32_t)tag {
    LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
    attachment.error = error;
    [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}

- (void)_submitResponseWithData:(NSObject *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
    LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
    attachment.data = data;
    [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}

@end

#endif /* SHOULD_COMPILE_LOOKIN_SERVER */
