//
//  RNFaceDetectorModule.m
//  RCTCamera
//
//  Created by Joao Guilherme Daros Fidelis on 21/01/18.
//

#if __has_include(<GoogleMobileVision/GoogleMobileVision.h>)
#import "RNFaceDetectorModule.h"
#import "RNFaceEncoder.h"
#import "RNFileSystem.h"
#import "RNFaceDetectorUtils.h"

static const NSString *kModeOptionName = @"mode";
static const NSString *kDetectLandmarksOptionName = @"detectLandmarks";
static const NSString *kRunClassificationsOptionName = @"runClassifications";

@implementation RNFaceDetectorModule

static NSFileManager *fileManager = nil;
static NSDictionary *defaultDetectorOptions = nil;

- (instancetype)init
{
    self = [super init];
    if (self) {
        fileManager = [NSFileManager defaultManager];
    }
    return self;
}

RCT_EXPORT_MODULE(RNFaceDetector);

@synthesize bridge = _bridge;

- (void)setBridge:(RCTBridge *)bridge
{
    _bridge = bridge;
}

+ (BOOL)requiresMainQueueSetup
{
    return NO;
}

- (NSDictionary *)constantsToExport
{
    return [RNFaceDetectorUtils constantsToExport];
}

RCT_EXPORT_METHOD(detectFaces:(nonnull NSDictionary *)options
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSString *uri = options[@"uri"];
    if (uri == nil) {
        reject(@"E_FACE_DETECTION_FAILED", @"You must define a URI.", nil);
        return;
    }
    
    NSURL *url = [NSURL URLWithString:uri];
    NSString *path = [url.path stringByStandardizingPath];
//    if (!([self.bridge.scopedModules.fileSystem permissionsForURI:url] & EXFileSystemPermissionRead)) {
//        reject(@"E_FILESYSTEM_PERMISSIONS", [NSString stringWithFormat:@"File '%@' isn't readable.", uri], nil);
//        return;
//    }
    
    @try {
        GMVDetector *detector = [[self class] detectorForOptions:options];
        
        if (![fileManager fileExistsAtPath:path]) {
            reject(@"E_FACE_DETECTION_FAILED", [NSString stringWithFormat:@"The file does not exist. Given path: `%@`.", path], nil);
            return;
        }
        
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
        
        NSDictionary *detectionOptions = [[self class] detectionOptionsForImage:image];
        NSArray<GMVFaceFeature *> *faces = [detector featuresInImage:image options:detectionOptions];
        RNFaceEncoder *faceEncoder = [[RNFaceEncoder alloc] init];
        NSMutableArray<NSDictionary *> *encodedFaces = [NSMutableArray arrayWithCapacity:[faces count]];
        [faces enumerateObjectsUsingBlock:^(GMVFaceFeature * _Nonnull face, NSUInteger _idx, BOOL * _Nonnull _stop) {
            [encodedFaces addObject:[faceEncoder encode:face]];
        }];
        
        resolve(@{
                  @"faces" : encodedFaces,
                  @"image" : @{
                          @"uri" : options[@"uri"],
                          @"width" : @(image.size.width),
                          @"height" : @(image.size.height),
                          @"orientation" : @([RNFaceDetectorModule exifOrientationFor:image.imageOrientation])
                          }
                  });
    } @catch (NSException *exception) {
        reject(@"E_FACE_DETECTION_FAILED", [exception description], nil);
    }
}

+ (GMVDetector *)detectorForOptions:(NSDictionary *)options
{
    NSMutableDictionary *parsedOptions = [[NSMutableDictionary alloc] initWithDictionary:[self getDefaultDetectorOptions]];
    
    if (options[kDetectLandmarksOptionName]) {
        [parsedOptions setObject:options[kDetectLandmarksOptionName] forKey:GMVDetectorFaceLandmarkType];
    }
    
    if (options[kModeOptionName]) {
        [parsedOptions setObject:options[kModeOptionName] forKey:GMVDetectorFaceMode];
    }
    
    if (options[kRunClassificationsOptionName]) {
        [parsedOptions setObject:options[kRunClassificationsOptionName] forKey:GMVDetectorFaceClassificationType];
    }
    
    return [GMVDetector detectorOfType:GMVDetectorTypeFace options:parsedOptions];
}

# pragma mark: - Detector default options getter and initializer

+ (NSDictionary *)getDefaultDetectorOptions
{
    if (defaultDetectorOptions == nil) {
        [self initDefaultDetectorOptions];
    }
    
    return defaultDetectorOptions;
}

+ (void)initDefaultDetectorOptions
{
    defaultDetectorOptions = @{
                               GMVDetectorFaceMode : @(GMVDetectorFaceAccurateMode),
                               GMVDetectorFaceLandmarkType : @(GMVDetectorFaceLandmarkAll),
                               GMVDetectorFaceClassificationType : @(GMVDetectorFaceClassificationAll)
                               };
}

# pragma mark: - Utility methods

+ (NSDictionary *)detectionOptionsForImage:(UIImage *)image
{
    return @{
             GMVDetectorImageOrientation : @([[self class] gmvImageOrientationFor:image.imageOrientation]),
             };
}

// As the documentation (http://cocoadocs.org/docsets/GoogleMobileVision/1.0.2/Constants/GMVImageOrientation.html) suggests
// the value of GMVImageOrientation is the same as the value defined by EXIF specifications, so we can adapt
// https://gist.github.com/steipete/4666527 to our needs.
+ (GMVImageOrientation)gmvImageOrientationFor:(UIImageOrientation)orientation
{
    switch (orientation) {
        case UIImageOrientationUp:
            return GMVImageOrientationTopLeft;
        case UIImageOrientationDown:
            return GMVImageOrientationBottomRight;
        case UIImageOrientationLeft:
            return GMVImageOrientationLeftBottom;
        case UIImageOrientationRight:
            return GMVImageOrientationRightTop;
        case UIImageOrientationUpMirrored:
            return GMVImageOrientationTopRight;
        case UIImageOrientationDownMirrored:
            return GMVImageOrientationBottomLeft;
        case UIImageOrientationLeftMirrored:
            return GMVImageOrientationLeftTop;
        case UIImageOrientationRightMirrored:
            return GMVImageOrientationRightBottom;
    }
}

// https://gist.github.com/steipete/4666527
+ (int)exifOrientationFor:(UIImageOrientation)orientation
{
    switch (orientation) {
        case UIImageOrientationUp:
            return 1;
        case UIImageOrientationDown:
            return 3;
        case UIImageOrientationLeft:
            return 8;
        case UIImageOrientationRight:
            return 6;
        case UIImageOrientationUpMirrored:
            return 2;
        case UIImageOrientationDownMirrored:
            return 4;
        case UIImageOrientationLeftMirrored:
            return 5;
        case UIImageOrientationRightMirrored:
            return 7;
    }
}

@end
#endif
