#import "BarcodeDetectorManagerMlkit.h"
#import <React/RCTConvert.h>
#import "RNFileSystem.h"
#if __has_include(<FirebaseMLVision/FirebaseMLVision.h>)

@interface BarcodeDetectorManagerMlkit ()
@property(nonatomic, strong) FIRVisionBarcodeDetector *barcodeRecognizer;
@property(nonatomic, strong) FIRVision *vision;
@property(nonatomic, assign) FIRVisionBarcodeFormat setOption;
@property(nonatomic, assign) NSInteger detectionMode;
@property(nonatomic, assign) float scaleX;
@property(nonatomic, assign) float scaleY;
@end

@implementation BarcodeDetectorManagerMlkit

- (instancetype)init
{
  if (self = [super init]) {
    self.vision = [FIRVision vision];
    self.barcodeRecognizer = [_vision barcodeDetector];
  }
  return self;
}

- (BOOL)isRealDetector
{
  return true;
}

-(NSInteger)fetchDetectionMode
{
    return self.detectionMode;
}

+ (NSDictionary *)constants
{
    return @{
                @"CODE_128" : @(FIRVisionBarcodeFormatCode128),
                @"CODE_39" : @(FIRVisionBarcodeFormatCode39),
                @"CODE_93" : @(FIRVisionBarcodeFormatCode93),
                @"CODABAR" : @(FIRVisionBarcodeFormatCodaBar),
                @"EAN_13" : @(FIRVisionBarcodeFormatEAN13),
                @"EAN_8" : @(FIRVisionBarcodeFormatEAN8),
                @"ITF" : @(FIRVisionBarcodeFormatITF),
                @"UPC_A" : @(FIRVisionBarcodeFormatUPCA),
                @"UPC_E" : @(FIRVisionBarcodeFormatUPCE),
                @"QR_CODE" : @(FIRVisionBarcodeFormatQRCode),
                @"PDF417" : @(FIRVisionBarcodeFormatPDF417),
                @"AZTEC" : @(FIRVisionBarcodeFormatAztec),
                @"DATA_MATRIX" : @(FIRVisionBarcodeFormatDataMatrix),
                @"ALL" : @(FIRVisionBarcodeFormatAll),
            };
}

- (void)setType:(id)json queue:(dispatch_queue_t)sessionQueue
{
  NSInteger requestedValue = [RCTConvert NSInteger:json];
  if (self.setOption != requestedValue) {
      if (sessionQueue) {
          dispatch_async(sessionQueue, ^{
              self.setOption = requestedValue;
              FIRVisionBarcodeDetectorOptions *options =
              [[FIRVisionBarcodeDetectorOptions alloc]
              initWithFormats: requestedValue];
              self.barcodeRecognizer =
              [self.vision barcodeDetectorWithOptions:options];
          });
      }
  }
}

-(void)setMode:(id)json queue:(dispatch_queue_t)sessionQueue
{
    NSInteger requestedValue = [RCTConvert NSInteger:json];
    self.detectionMode = requestedValue;
}

- (void)findBarcodesInFrame:(UIImage *)uiImage
                  scaleX:(float)scaleX
                  scaleY:(float)scaleY
               completed:(void (^)(NSArray *result))completed
{
    self.scaleX = scaleX;
    self.scaleY = scaleY;
    FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];
    NSMutableArray *emptyResult = [[NSMutableArray alloc] init];
    [_barcodeRecognizer detectInImage:image
        completion:^(NSArray<FIRVisionBarcode *> *barcodes, NSError *error) {
            if (error != nil || barcodes == nil) {
                completed(emptyResult);
            } else {
                completed([self processBarcodes:barcodes imageContainingBarcodes:uiImage]);
            }
        }];
}

- (NSArray *)processBarcodes:(NSArray *)barcodes imageContainingBarcodes:(UIImage *)imageContainingBarcodes
{
    NSMutableArray *result = [[NSMutableArray alloc] init];
    for (FIRVisionBarcode *barcode in barcodes) {
        NSMutableDictionary *resultDict =
        [[NSMutableDictionary alloc] initWithCapacity:20];
        // Boundaries of a barcode in image
        NSDictionary *bounds = [self processBounds:barcode.frame];
        [resultDict setObject:bounds forKey:@"bounds"];

        // TODO send points to javascript - implement on android at the same time
        // Point[] corners = barcode.getCornerPoints();

        NSString *rawValue = barcode.rawValue;
        NSString *displayValue = barcode.displayValue;
        [resultDict setObject:rawValue forKey:@"dataRaw"];
        [resultDict setObject:displayValue forKey:@"data"];

        // Store the image to app cache and return the uri
        NSString *path = [RNFileSystem generatePathInDirectory:[[RNFileSystem cacheDirectoryPath] stringByAppendingPathComponent:@"Camera"] withExtension:@".jpg"];
        [UIImageJPEGRepresentation(imageContainingBarcodes, 1.0) writeToFile:path atomically:YES];
        [resultDict setObject:path forKey:@"uri"];

        FIRVisionBarcodeValueType valueType = barcode.valueType;
        [resultDict setObject:[self getType:barcode.valueType] forKey:@"type"];

        switch (valueType) {
            case FIRVisionBarcodeValueTypeWiFi:
                if(barcode.wifi.ssid) {[resultDict setObject:barcode.wifi.ssid forKey:@"ssid"]; }
                if(barcode.wifi.password) {[resultDict setObject:barcode.wifi.password forKey:@"password"]; }
                if(barcode.wifi.type) {
                    NSString *encryptionTypeString = @"UNKNOWN";
                    int type = barcode.wifi.type;
                    switch (type) {
                        case FIRVisionBarcodeWiFiEncryptionTypeWEP:
                            encryptionTypeString = @"WEP";
                            break;
                        case FIRVisionBarcodeWiFiEncryptionTypeWPA:
                            encryptionTypeString = @"WPA";
                            break;
                        case FIRVisionBarcodeWiFiEncryptionTypeOpen:
                            encryptionTypeString = @"Open";
                            break;
                        default:
                            break;
                    }
                    [resultDict setObject:encryptionTypeString forKey:@"encryptionType"];
                }
                break;
            case FIRVisionBarcodeValueTypeURL:
                if(barcode.URL.url) { [resultDict setObject:barcode.URL.url forKey:@"url"]; }
                if(barcode.URL.title) { [resultDict setObject:barcode.URL.title forKey:@"title"]; }
                break;
            case FIRVisionBarcodeValueTypeContactInfo:
                if(barcode.contactInfo.addresses) {
                    NSMutableArray *addresses = [[NSMutableArray alloc] init];
                    for (FIRVisionBarcodeAddress *address in barcode.contactInfo.addresses) {
                        [addresses addObject:[self processAddress:address]];
                    }
                    [resultDict setObject:addresses forKey:@"addresses"];
                }
                if(barcode.contactInfo.emails) {
                    NSMutableArray *emails = [[NSMutableArray alloc] init];
                    for (FIRVisionBarcodeEmail *email in barcode.contactInfo.emails) {
                        [emails addObject:[self processEmail:email]];
                    }
                    [resultDict setObject:emails forKey:@"emails"];
                }
                if(barcode.contactInfo.name) {
                    FIRVisionBarcodePersonName *name = barcode.contactInfo.name;
                    NSObject *nameObject = @{
                                             @"formattedName" : name.formattedName ? name.formattedName : @"",
                                             @"firstName" : name.first ? name.first : @"",
                                             @"middleName" : name.middle ? name.middle : @"",
                                             @"lastName" : name.last ? name.last : @"",
                                             @"prefix" : name.prefix ? name.prefix : @"",
                                             @"pronounciation" : name.pronounciation ? name.pronounciation : @"",
                                             @"suffix" : name.suffix ? name.suffix : @"",
                                             };
                    [resultDict setObject:nameObject forKey:@"name"];
                }
                if(barcode.contactInfo.phones) {
                    NSMutableArray *phones = [[NSMutableArray alloc] init];
                    for (FIRVisionBarcodePhone *phone in barcode.contactInfo.phones) {
                        [phones addObject:[self processPhone:phone]];
                    }
                    [resultDict setObject:phones forKey:@"phones"];
                }
                if(barcode.contactInfo.urls) {[resultDict setObject:barcode.contactInfo.urls forKey:@"urls"]; }
                if(barcode.contactInfo.organization) {[resultDict setObject:barcode.contactInfo.organization forKey:@"organization"]; }
                break;
            case FIRVisionBarcodeValueTypeSMS:
                if(barcode.sms.message) {[resultDict setObject:barcode.sms.message forKey:@"message"]; }
                if(barcode.sms.phoneNumber) {[resultDict setObject:barcode.sms.phoneNumber forKey:@"phoneNumber"]; }
                break;
            case FIRVisionBarcodeValueTypeGeographicCoordinates:
                if(barcode.geoPoint.latitude) {[resultDict setObject:@(barcode.geoPoint.latitude) forKey:@"latitude"]; }
                if(barcode.geoPoint.longitude) {[resultDict setObject:@(barcode.geoPoint.longitude) forKey:@"longitude"]; }
                break;
            case FIRVisionBarcodeValueTypeDriversLicense:
                if(barcode.driverLicense.firstName) {[resultDict setObject:barcode.driverLicense.firstName forKey:@"firstName"]; }
                if(barcode.driverLicense.middleName) {[resultDict setObject:barcode.driverLicense.middleName forKey:@"middleName"]; }
                if(barcode.driverLicense.lastName) {[resultDict setObject:barcode.driverLicense.lastName forKey:@"lastName"]; }
                if(barcode.driverLicense.gender) {[resultDict setObject:barcode.driverLicense.gender forKey:@"gender"]; }
                if(barcode.driverLicense.addressCity) {[resultDict setObject:barcode.driverLicense.addressCity forKey:@"addressCity"]; }
                if(barcode.driverLicense.addressState) {[resultDict setObject:barcode.driverLicense.addressState forKey:@"addressState"]; }
                if(barcode.driverLicense.addressStreet) {[resultDict setObject:barcode.driverLicense.addressStreet forKey:@"addressStreet"]; }
                if(barcode.driverLicense.addressZip) {[resultDict setObject:barcode.driverLicense.addressZip forKey:@"addressZip"]; }
                if(barcode.driverLicense.birthDate) {[resultDict setObject:barcode.driverLicense.birthDate forKey:@"birthDate"]; }
                if(barcode.driverLicense.documentType) {[resultDict setObject:barcode.driverLicense.documentType forKey:@"documentType"]; }
                if(barcode.driverLicense.licenseNumber) {[resultDict setObject:barcode.driverLicense.licenseNumber forKey:@"licenseNumber"]; }
                if(barcode.driverLicense.expiryDate) {[resultDict setObject:barcode.driverLicense.expiryDate forKey:@"expiryDate"]; }
                if(barcode.driverLicense.issuingDate) {[resultDict setObject:barcode.driverLicense.issuingDate forKey:@"issuingDate"]; }
                if(barcode.driverLicense.issuingCountry) {[resultDict setObject:barcode.driverLicense.issuingCountry forKey:@"issuingCountry"]; }
                break;
            case FIRVisionBarcodeValueTypeCalendarEvent:
                if(barcode.calendarEvent.eventDescription) {[resultDict setObject:barcode.calendarEvent.eventDescription forKey:@"eventDescription"]; }
                if(barcode.calendarEvent.location) {[resultDict setObject:barcode.calendarEvent.location forKey:@"location"]; }
                if(barcode.calendarEvent.organizer) {[resultDict setObject:barcode.calendarEvent.organizer forKey:@"organizer"]; }
                if(barcode.calendarEvent.status) {[resultDict setObject:barcode.calendarEvent.status forKey:@"status"]; }
                if(barcode.calendarEvent.summary) {[resultDict setObject:barcode.calendarEvent.summary forKey:@"summary"]; }
                if(barcode.calendarEvent.start) {
                    [resultDict setObject:[self processDate:barcode.calendarEvent.start] forKey:@"start"];
                }
                if(barcode.calendarEvent.end) {
                    [resultDict setObject:[self processDate:barcode.calendarEvent.end] forKey:@"end"];
                }
                break;
            case FIRVisionBarcodeValueTypePhone:
                if(barcode.phone.number) {[resultDict setObject:barcode.phone.number forKey:@"number"]; }
                if(barcode.phone.type) {
                    [resultDict setObject:[self getPhoneType:barcode.phone.type] forKey:@"phoneType"];
                }
                break;
            case FIRVisionBarcodeValueTypeEmail:
                if(barcode.email.address) {[resultDict setObject:barcode.email.address forKey:@"address"]; }
                if(barcode.email.body) {[resultDict setObject:barcode.email.body forKey:@"body"]; }
                if(barcode.email.subject) {[resultDict setObject:barcode.email.subject forKey:@"subject"]; }
                if(barcode.email.type) {[resultDict setObject:[self getEmailType:barcode.email.type] forKey:@"emailType"]; }
                break;
            default:
                break;
        }
        [result addObject:resultDict];
    }
    return result;
}

- (NSString *)getType:(int)type
{
    NSString *barcodeType = @"UNKNOWN";
    switch (type) {
        case FIRVisionBarcodeValueTypeEmail:
            barcodeType = @"EMAIL";
            break;
        case FIRVisionBarcodeValueTypePhone:
            barcodeType = @"PHONE";
            break;
        case FIRVisionBarcodeValueTypeCalendarEvent:
            barcodeType = @"CALENDAR_EVENT";
            break;
        case FIRVisionBarcodeValueTypeDriversLicense:
            barcodeType = @"DRIVER_LICENSE";
            break;
        case FIRVisionBarcodeValueTypeGeographicCoordinates:
            barcodeType = @"GEO";
            break;
        case FIRVisionBarcodeValueTypeSMS:
            barcodeType = @"SMS";
            break;
        case FIRVisionBarcodeValueTypeContactInfo:
            barcodeType = @"CONTACT_INFO";
            break;
        case FIRVisionBarcodeValueTypeWiFi:
            barcodeType = @"WIFI";
            break;
        case FIRVisionBarcodeValueTypeText:
            barcodeType = @"TEXT";
            break;
        case FIRVisionBarcodeValueTypeISBN:
            barcodeType = @"ISBN";
            break;
        case FIRVisionBarcodeValueTypeProduct:
            barcodeType = @"PRODUCT";
            break;
        default:
            break;
    }
    return barcodeType;
}

- (NSString *)getPhoneType:(int)type
{
    NSString *typeString = @"UNKNOWN";
    switch (type) {
        case FIRVisionBarcodePhoneTypeFax:
            typeString = @"Fax";
            break;
        case FIRVisionBarcodePhoneTypeHome:
            typeString = @"Home";
        case FIRVisionBarcodePhoneTypeWork:
            typeString = @"Work";
        case FIRVisionBarcodePhoneTypeMobile:
            typeString = @"Mobile";
        default:
            break;
    }
    return typeString;
}

- (NSString *)getEmailType:(int)type
{
    NSString *typeString = @"UNKNOWN";
    switch (type) {
        case FIRVisionBarcodeEmailTypeWork:
            typeString = @"Work";
            break;
        case FIRVisionBarcodeEmailTypeHome:
            typeString = @"Home";
        default:
            break;
    }
    return typeString;
}

- (NSDictionary *)processPhone:(FIRVisionBarcodePhone *)phone
{
    NSString *number = @"";
    NSString *typeString = @"UNKNOWN";
    if (phone) {
        typeString = [self getPhoneType:phone.type];
        number = phone.number;
    }
    return @{@"number" : number, @"phoneType" : typeString};
}

- (NSDictionary *)processAddress:(FIRVisionBarcodeAddress *)address
{
    NSArray *addressLines = [[NSArray alloc] init];
    NSString *typeString = @"UNKNOWN";
    if (address) {
        int type = address.type;
        NSString *typeString = @"UNKNOWN";
        switch (type) {
            case FIRVisionBarcodeAddressTypeWork:
                typeString = @"Work";
                break;
            case FIRVisionBarcodeAddressTypeHome:
                typeString = @"Home";
            default:
                break;
        }
        addressLines = address.addressLines;
    }
    return @{@"addressLines" : addressLines, @"addressType" : typeString};
}

- (NSDictionary *)processEmail:(FIRVisionBarcodeEmail *)email
{
    NSString *subject = @"";
    NSString *address  =@"";
    NSString *body  =@"";
    NSString *typeString = @"UNKNOWN";
    if (email) {
        if (email.subject) { subject = email.subject; }
        if (email.address) { address = email.address; }
        if (email.body) { body = email.body; }
        typeString = [self getEmailType:email.type];
    }
    return @{@"subject" : subject, @"body" : body, @"address" : address, @"emailType" : typeString};
}

- (NSString *)processDate:(NSDate *)date
{
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"dd-MM-YYYY HH:mm:ss"];
    return [dateFormatter stringFromDate:date];
}

- (NSDictionary *)processBounds:(CGRect)bounds
{
    float width = bounds.size.width * _scaleX;
    float height = bounds.size.height * _scaleY;
    float originX = bounds.origin.x * _scaleX;
    float originY = bounds.origin.y * _scaleY;
    NSDictionary *boundsDict = @{
                                 @"size" : @{@"width" : @(width), @"height" : @(height)},
                                 @"origin" : @{@"x" : @(originX), @"y" : @(originY)}
                                 };
    return boundsDict;
}


- (NSDictionary *)processPoint:(FIRVisionPoint *)point
{
    float originX = [point.x floatValue] * _scaleX;
    float originY = [point.y floatValue] * _scaleY;
    NSDictionary *pointDict = @{
                                @"x" : @(originX),
                                @"y" : @(originY)
                                };
    return pointDict;
}

@end
#else

@interface BarcodeDetectorManagerMlkit ()
@end

@implementation BarcodeDetectorManagerMlkit

- (instancetype)init {
    self = [super init];
    return self;
}

- (BOOL)isRealDetector {
    return false;
}

- (void)findBarcodesInFrame:(UIImage *)image
                       scaleX:(float)scaleX
                       scaleY:(float)scaleY
                       completed:(void (^)(NSArray *result))completed;
{
    NSLog(@"BarcodeDetector not installed, stub used!");
    NSArray *barcodes = @[ @"Error, Barcode Detector not installed" ];
    completed(barcodes);
}

+ (NSDictionary *)constants
{
    return @{
             @"CODE_128" : @{},
             @"CODE_39" : @{},
             @"CODE_93" : @{},
             @"CODABAR" : @{},
             @"EAN_13" : @{},
             @"EAN_8" : @{},
             @"ITF" : @{},
             @"UPC_A" : @{},
             @"UPC_E" : @{},
             @"QR_CODE" : @{},
             @"PDF417" : @{},
             @"AZTEC" : @{},
             @"DATA_MATRIX" : @{},
             };
}

- (void)setType:(id)json queue:(dispatch_queue_t)sessionQueue
{
    return;
}

@end
#endif

