//
//  LocalyticsPlugin.m
//  LLLocalytics
//
//  Created by Anand Bashyam on 2/23/18.
//  Copyright © 2018 Facebook. All rights reserved.
//

@import CoreLocation;

#import "LocalyticsPlugin.h"
@import Localytics;


@interface LLInboxCampaign (LocalyticsPlugin)
- (nonnull NSDictionary *)toDictionary;
@end

@interface LLInAppCampaign (LocalyticsPlugin)
- (nonnull NSDictionary<NSString *, NSObject *> *)toDictionary;
@end

@interface LLGeofence (LocalyticsPlugin)
- (nonnull NSDictionary<NSString *, NSObject *> *)toDictionary;
@end

@interface LLPlacesCampaign (LocalyticsPlugin)
- (nonnull NSDictionary<NSString *, NSObject *> *)toDictionary;
@end

@interface LLInAppConfiguration (LocalyticsPlugin)
- (void)update:(nonnull NSDictionary *)inAppConfig;
@end

typedef void (^_Nullable Emitter)(NSString*_Nonnull, id _Nonnull);

@interface LocalyticsPlugin () <LLAnalyticsDelegate, LLLocationDelegate>
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, LLInboxCampaign *> *inboxCampaignCache;
@property (nonatomic) dispatch_queue_t inboxCacheSerialQueue;
+ (void)updateInboxCache:(nonnull NSArray<LLInboxCampaign *> *)  campaigns;
+ (void)replaceInboxCache:(nonnull NSArray<LLInboxCampaign *> *) campaigns;
+ (nonnull NSString*)stringWithButtonLocation:(LLInAppMessageDismissButtonLocation) location;
+ (nonnull NSArray<NSDictionary<NSString *, NSObject *> *> *)dictionaryArrayFromInboxCampaigns:(nonnull NSArray<LLInboxCampaign *> *)campaigns;

+ (LLInAppMessageDismissButtonLocation)locationFrom:(nullable NSString *)location;

@property (nonatomic, strong) Emitter analyticsEmitter;
@property (nonatomic, strong) Emitter locationEmitter;
@end

#define EVTNAME_LOCALYTICS_SESSION_WILLOPEN @"localyticsSessionWillOpen"
#define EVTNAME_LOCALYTICS_SESSION_DIDOPEN @"localyticsSessionDidOpen"
#define EVTNAME_LOCALYTICS_SESSION_DIDTAGEVENT @"localyticsDidTagEvent"
#define EVTNAME_LOCALYTICS_SESSION_WILLCLOSE @"localyticsSessionWillClose"

@implementation LocalyticsPlugin
LocalyticsPlugin* shared;

+ (NSArray<NSString *> *)analyticsEvents {
    return @[EVTNAME_LOCALYTICS_SESSION_WILLOPEN,
             EVTNAME_LOCALYTICS_SESSION_DIDOPEN,
             EVTNAME_LOCALYTICS_SESSION_DIDTAGEVENT,
             EVTNAME_LOCALYTICS_SESSION_WILLCLOSE];
}

#pragma mark LLAnalyticsDelegate
- (void)localyticsSessionWillOpen:(BOOL)isFirst isUpgrade:(BOOL)isUpgrade isResume:(BOOL)isResume {
    NSDictionary *body = @{@"isFirst": @(isFirst), @"isUpgrade": @(isUpgrade), @"isResume": @(isResume)};
    Emitter emitter = shared.analyticsEmitter;
    if (emitter) {
        emitter(@"localyticsSessionWillOpen", body);
    }
}

- (void)localyticsSessionDidOpen:(BOOL)isFirst isUpgrade:(BOOL)isUpgrade isResume:(BOOL)isResume {
    NSDictionary *body = @{@"isFirst": @(isFirst), @"isUpgrade": @(isUpgrade), @"isResume": @(isResume)};
    Emitter emitter = shared.analyticsEmitter;
    if (emitter) {
        emitter(@"localyticsSessionDidOpen", body);
    }
}

- (void)localyticsDidTagEvent:(nonnull NSString *)eventName attributes:(nullable NSDictionary<NSString *,NSString *> *)attributes customerValueIncrease:(nullable NSNumber *)customerValueIncrease {
    NSDictionary *body = @{@"name": eventName, @"attributes": attributes ?: [NSNull null], @"customerValueIncrease": customerValueIncrease ?: [NSNull null]};
    Emitter emitter = shared.analyticsEmitter;
    if (emitter) {
        emitter(@"localyticsDidTagEvent", body);
    }
}

- (void)localyticsSessionWillClose {
    Emitter emitter = shared.analyticsEmitter;
    if (emitter) {
        emitter(@"localyticsSessionWillClose", @{});
    }
}

#pragma mark LLLocationDelegate
- (void)localyticsDidUpdateLocation:(nonnull CLLocation *)location {
    NSDictionary *locationDict = @{
                                   @"latitude": @(location.coordinate.latitude),
                                   @"longitude": @(location.coordinate.longitude),
                                   @"altitude": @(location.altitude),
                                   @"time": @(location.timestamp.timeIntervalSince1970),
                                   @"horizontalAccuracy": @(location.horizontalAccuracy),
                                   @"verticalAccuracy": @(location.verticalAccuracy)
                                   };
    NSDictionary *body = @{@"location": locationDict};
    Emitter emitter = shared.locationEmitter;
    if (emitter) {
        emitter(@"localyticsDidUpdateLocation", body);
    }
}

- (void)localyticsDidUpdateMonitoredRegions:(nonnull NSArray<LLRegion *> *)addedRegions removeRegions:(nonnull NSArray<LLRegion *> *)removedRegions {
    NSDictionary *body = @{
                           @"added": [LocalyticsPlugin dictionaryArrayFromRegions:addedRegions],
                           @"removed": [LocalyticsPlugin dictionaryArrayFromRegions:removedRegions],
                           };
    Emitter emitter = shared.locationEmitter;
    if (emitter) {
        emitter(@"localyticsDidUpdateMonitoredGeofences", body);
    }
}

- (void)localyticsDidTriggerRegions:(nonnull NSArray<LLRegion *> *)regions withEvent:(LLRegionEvent)event {
    NSDictionary *body = @{
                           @"regions": [LocalyticsPlugin dictionaryArrayFromRegions:regions],
                           @"event": event == LLRegionEventEnter ? @"enter": @"exit"
                           };
    Emitter emitter = shared.locationEmitter;
    if (emitter) {
        emitter(@"localyticsDidTriggerRegions", body);
    }
}

#pragma mark Delegate Registerations

+ (void)registerAnalyticsDelegate:(void (^_Nullable)(NSString*_Nonnull, id _Nonnull))eventEmitter {
    if (eventEmitter == nil) {
        [Localytics setAnalyticsDelegate:nil];
        shared.analyticsEmitter = nil;
    } else {
        shared.analyticsEmitter = eventEmitter;
        [Localytics setAnalyticsDelegate:[LocalyticsPlugin sharedInstance]];
    }
}

+ (void)registerLocationDelegate:(void (^_Nullable)(NSString*_Nonnull, id _Nonnull))eventEmitter {
    if (eventEmitter == nil) {
        [Localytics setLocationDelegate:nil];
        shared.locationEmitter = nil;
    } else {
        shared.locationEmitter = eventEmitter;
        [Localytics setLocationDelegate:[LocalyticsPlugin sharedInstance]];
    }
}

- (nullable LLInboxCampaign*) findInboxCampaign:(NSInteger)campaignId {
    NSArray<LLInboxCampaign *> *campaignList = [Localytics allInboxCampaigns];
    for (LLInboxCampaign *campaign in campaignList) {
        if (campaign.campaignId == campaignId) {
            return campaign;
        }
    }
    return nil;
}

- (instancetype)init {
    if (self = [super init]) {
        _inboxCampaignCache = [NSMutableDictionary new];
        _inboxCacheSerialQueue = dispatch_queue_create("com.localytics.LLShouldDeepLink", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

+ (LocalyticsPlugin*) sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[LocalyticsPlugin alloc]init];
    });
    return shared;
}

#pragma mark API's

+ (void)tagImpressionForInboxCampaignId:(NSInteger)campaignId
                       withActionName:(nonnull NSString*)action {
    LLInboxCampaign *campaign = LocalyticsPlugin.sharedInstance.inboxCampaignCache[@(campaignId)];
    if (campaign) {
        if ([@"click" isEqualToString:action]) {
            [Localytics tagImpressionForInboxCampaign:campaign withType:LLImpressionTypeClick];
        } else if ([@"dismiss" isEqualToString:action]) {
            [Localytics tagImpressionForInboxCampaign:campaign withType:LLImpressionTypeDismiss];
        } else if (action.length == 0){
            [Localytics tagImpressionForInboxCampaign:campaign withCustomAction:action];
        } else {
            NSLog(@"Unable to find action %@", action);
            return;
        }
    } else {
        NSLog(@"Unable to find campaign %ld", (long)campaignId);
        return;
    }
}

+ (void)updateInboxCache:(nonnull NSArray<LLInboxCampaign *> *)  campaigns {
    dispatch_sync(LocalyticsPlugin.sharedInstance.inboxCacheSerialQueue, ^{
        // Cache campaigns - Dont clear out here. This is not all campaigns
        for (LLInboxCampaign *campaign in campaigns) {
            [LocalyticsPlugin.sharedInstance.inboxCampaignCache setObject:campaign forKey:@(campaign.campaignId)];
        }
    });
}

+ (void)replaceInboxCache:(nonnull NSArray<LLInboxCampaign *> *) campaigns {
    // Do an atomic update of campaign cache to avoid issues when accessing from multiple queues.
    NSMutableDictionary* newCache = [NSMutableDictionary new];
    for (LLInboxCampaign *campaign in campaigns) {
        [newCache setObject:campaign forKey:@(campaign.campaignId)];
    }
    dispatch_sync(LocalyticsPlugin.sharedInstance.inboxCacheSerialQueue, ^{
        LocalyticsPlugin.sharedInstance.inboxCampaignCache = newCache;
    });
}

+ (void)tagImpressionForPushToInboxCampaign:(NSInteger)campaignId success:(BOOL)success {
    dispatch_sync(LocalyticsPlugin.sharedInstance.inboxCacheSerialQueue, ^{
        LLInboxCampaign* campaign = [LocalyticsPlugin.sharedInstance findInboxCampaign:campaignId];
        if (campaign) {
            [Localytics tagImpressionForPushToInboxCampaign:campaign success:success];
        }
    });
}

+ (NSInteger)inboxUnreadCount {
    __block NSInteger count;
    dispatch_sync(LocalyticsPlugin.sharedInstance.inboxCacheSerialQueue, ^{
        count = [Localytics inboxCampaignsUnreadCount];
    });
    return count;
}

+ (void)markInboxCampaign:(NSInteger)campaignId asRead:(BOOL)read {
    dispatch_sync(LocalyticsPlugin.sharedInstance.inboxCacheSerialQueue, ^{
        LLInboxCampaign* campaign = [LocalyticsPlugin.sharedInstance findInboxCampaign:campaignId];
        if (campaign==nil) {
            NSLog(@"No campign found for id :%ld", (long)campaignId);
        }
        [Localytics setInboxCampaign:campaign asRead:read];
    });
}

/**
 Set Plugin Version. Has effect only first time.

 @param version version to set for Plugin.
 */
+ (void)setPluginVersion:(nonnull NSString*)version {
    [Localytics setOptions:@{@"plugin_library":version}];
}

+ (NSString*)stringWithButtonLocation:(LLInAppMessageDismissButtonLocation) location {
    switch (location) {
        case LLInAppMessageDismissButtonLocationLeft:
            return @"left";
            break;
        case LLInAppMessageDismissButtonLocationRight:
            return @"right";
    }
}

+ (NSArray<NSDictionary<NSString *, NSObject *> *> *)dictionaryArrayFromInboxCampaigns:(NSArray<LLInboxCampaign *> *)campaigns {
    NSMutableArray *array = [NSMutableArray new];
    for (LLInboxCampaign *campaign in campaigns) {
        [array addObject:[campaign toDictionary]];
    }
    
    return array;
}

+ (NSArray<NSDictionary<NSString *, NSObject *> *> *)dictionaryArrayFromRegions:(NSArray<LLRegion *> *)regions {
    NSMutableArray *array = [NSMutableArray new];
    for (LLRegion *region in regions) {
        if ([region isKindOfClass:[LLGeofence class]]) {
            LLGeofence *geofence = (LLGeofence*)region;
            [array addObject:[geofence toDictionary]];
        }
    }
    
    return [array copy];
}

+ (NSArray<CLRegion *> *)regionsFromDictionaryArray:(NSArray<NSDictionary *> *)dictArray {
    NSMutableArray *array = [NSMutableArray new];
    for (NSDictionary *dict in dictArray) {
        [array addObject:[LocalyticsPlugin regionFromDictionary:dict]];
    }
    
    return [array copy];
}

+ (nonnull NSArray<NSDictionary *> *)getAllInboxCampaigns {
    NSArray<LLInboxCampaign *> *inboxCampaigns = [Localytics allInboxCampaigns];
    [LocalyticsPlugin replaceInboxCache:inboxCampaigns];
    return [LocalyticsPlugin dictionaryArrayFromInboxCampaigns:inboxCampaigns];
}

+ (nonnull NSArray<NSDictionary *> *)getInboxCampaigns {
    NSArray<LLInboxCampaign *> *inboxCampaigns = [Localytics inboxCampaigns];
    [LocalyticsPlugin updateInboxCache:inboxCampaigns];
    return [LocalyticsPlugin dictionaryArrayFromInboxCampaigns:inboxCampaigns];
}

+ (void)refreshAllInboxCampaigns:(nonnull void (^)(NSArray<NSDictionary *> * _Nullable inboxCampaigns))completionBlock {
    [Localytics refreshAllInboxCampaigns:^(NSArray<LLInboxCampaign *> *allCampaigns) {
        [LocalyticsPlugin replaceInboxCache:allCampaigns];
        completionBlock([LocalyticsPlugin dictionaryArrayFromInboxCampaigns:allCampaigns]);
    }];
}

+ (void)refreshInboxCampaigns:(nonnull void (^)(NSArray<NSDictionary *> * _Nullable inboxCampaigns))completionBlock {
    [Localytics refreshInboxCampaigns:^(NSArray<LLInboxCampaign *> *inboxCampaigns) {
        [LocalyticsPlugin updateInboxCache:inboxCampaigns];
        
        completionBlock( [LocalyticsPlugin dictionaryArrayFromInboxCampaigns:inboxCampaigns]);
    }];
}

+ (nonnull NSString*)inAppMessageDismissButtonLocation {
    LLInAppMessageDismissButtonLocation location = [Localytics inAppMessageDismissButtonLocation];
    return [LocalyticsPlugin stringWithButtonLocation:location];
}

+ (void)setInAppMessageDismissButtonLocation:(nonnull NSString*)location {
    [Localytics setInAppMessageDismissButtonLocation:[LocalyticsPlugin locationFrom:location]];
}

+ (void)updateInAppConfig:(LLInAppConfiguration*)configuration from:(NSDictionary*)dict {
    [configuration update:dict];
}

+ (LLInAppMessageDismissButtonLocation)locationFrom:(nullable NSString *)location {
    // TODO FIX ME Case insensitive compare.
    if ([@"left" isEqualToString:location]) {
        return LLInAppMessageDismissButtonLocationLeft;
    } else if ([@"right" isEqualToString:location]) {
        return LLInAppMessageDismissButtonLocationRight;
    } else {
        return LLInAppMessageDismissButtonLocationLeft;
    }
}

#pragma mark Conversions
+ (CLRegion *)regionFromDictionary:(NSDictionary *)dict {
    return [[CLCircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(0.0, 0.0)
                                             radius:1
                                         identifier:dict[@"uniqueId"]];
}

+ (LLCustomer *)customerFrom:(NSDictionary *)dict {
    if (dict) {
        return [LLCustomer customerWithBlock:^(LLCustomerBuilder* builder) {
            builder.customerId = dict[@"customerId"];
            builder.firstName = dict[@"firstName"];
            builder.lastName = dict[@"lastName"];
            builder.fullName = dict[@"fullName"];
            builder.emailAddress = dict[@"emailAddress"];
        }];
    }
    return nil;
}

+ (int)profilescopeFrom:(NSString *)scope {
    if ([@"org" isEqualToString:scope]) {
        return LLProfileScopeOrganization;
    } else if ([@"app" isEqualToString:scope]) {
        return LLProfileScopeApplication;
    } else { // App is default.
        return LLProfileScopeApplication;
    }
}

+ (int)regionEventFrom:(NSString *)event {
    if ([@"enter" isEqualToString:event]) {
        return LLRegionEventEnter;
    } else if ([@"exit" isEqualToString:event]) {
        return LLRegionEventExit;
    } else { // Default Exit backward compatibility
        return LLRegionEventExit;
    }
}

+ (nonnull NSDictionary<NSString *, NSObject *> *)dictionaryFromInAppCampaign:(nonnull LLInAppCampaign *)campaign {
    return [campaign toDictionary];
}

+ (nonnull NSDictionary<NSString *, NSObject *> *)dictionaryFromPlacesCampaign:(nonnull LLPlacesCampaign *)campaign {
    return [campaign toDictionary];
}
@end

@implementation LLInboxCampaign (LocalyticsPlugin)
- (nonnull NSDictionary *)toDictionary {
    return @{
             // LLCampaignBase
             @"campaignId": @(self.campaignId),
             @"name": self.name,
             @"attributes": self.attributes ?: [NSNull null],
             
             // LLWebViewCampaign
             @"creativeFilePath": self.creativeFilePath ?: [NSNull null],
             
             // LLInboxCampaign
             @"read": @(self.isRead),
             @"title": self.titleText ?: [NSNull null],
             @"summary": self.summaryText ?: [NSNull null],
             @"thumbnailUrl": [self.thumbnailUrl absoluteString] ?: [NSNull null],
             @"hasCreative": @(self.hasCreative),
             @"sortOrder": @(self.sortOrder),
             @"receivedDate": @(self.receivedDate)
             };
}
@end

@implementation LLInAppCampaign (LocalyticsPlugin)
- (NSDictionary<NSString *, NSObject *> *)toDictionary {
    LLInAppCampaign *campaign =  self;
    NSString *typeString = @"";
    switch (campaign.type) {
        case LLInAppMessageTypeTop:
            typeString = @"top";
            break;
        case LLInAppMessageTypeBottom:
            typeString = @"bottom";
            break;
        case LLInAppMessageTypeCenter:
            typeString = @"center";
            break;
        case LLInAppMessageTypeFull:
            typeString = @"full";
            break;
    }
    NSString* buttonLocation = [LocalyticsPlugin stringWithButtonLocation:campaign.dismissButtonLocation];
    return @{
             // LLCampaignBase
             @"campaignId": @(campaign.campaignId),
             @"name": campaign.name,
             @"attributes": campaign.attributes ?: [NSNull null],
             
             // LLWebViewCampaign
             @"creativeFilePath": campaign.creativeFilePath ?: [NSNull null],
             
             // LLInAppCampaign
             @"type": typeString,
             @"isResponsive": @(campaign.isResponsive),
             @"aspectRatio": @(campaign.aspectRatio),
             @"offset": @(campaign.offset),
             @"backgroundAlpha": @(campaign.backgroundAlpha),
             @"dismissButtonHidden": @(campaign.isDismissButtonHidden),
             @"dismissButtonLocation": buttonLocation,
             @"eventName": campaign.eventName,
             @"eventAttributes": campaign.eventAttributes ?: [NSNull null]
             };
}
@end

@implementation LLGeofence (LocalyticsPlugin)
- (NSDictionary<NSString *, NSObject *> *)toDictionary {
    LLGeofence *geofence = self;
    return @{
             @"uniqueId": geofence.region.identifier,
             @"latitude": @(geofence.region.center.latitude),
             @"longitude": @(geofence.region.center.longitude),
             @"name": geofence.name ?: [NSNull null],
             @"attributes": geofence.attributes ?: [NSNull null]
             };
}
@end

@implementation LLPlacesCampaign (LocalyticsPlugin)
- (NSDictionary<NSString *, NSObject *> *)toDictionary {
    LLPlacesCampaign *campaign = self;
    return @{
             // LLCampaignBase
             @"campaignId": @(campaign.campaignId),
             @"name": campaign.name,
             @"attributes": campaign.attributes ?: [NSNull null],
             
             // LLPlacesCampaign
             @"message": campaign.message,
             @"soundFilename": campaign.soundFilename ?: [NSNull null],
             @"region": [((LLGeofence *)campaign.region) toDictionary],
             @"triggerEvent": campaign.event == LLRegionEventEnter ? @"enter" : @"exit"
             };
}
@end

@implementation LLInAppConfiguration (LocalyticsPlugin)
- (void)update:(nonnull NSDictionary *)inAppConfig {
    if (inAppConfig[@"dismissButtonLocation"]) {
        self.dismissButtonLocation = [LocalyticsPlugin locationFrom:inAppConfig[@"dismissButtonLocation"]];
    }
    if (inAppConfig[@"dismissButtonHidden"]) {
        self.dismissButtonHidden = [inAppConfig[@"dismissButtonHidden"] boolValue];
    }
    if (inAppConfig[@"dismissButtonImageName"]) {
        [self setDismissButtonImageWithName:inAppConfig[@"dismissButtonImageName"]];
    }
    if (inAppConfig[@"aspectRatio"]) {
        self.aspectRatio = [inAppConfig[@"aspectRatio"] floatValue];
    }
    if (inAppConfig[@"offset"]) {
        self.offset = [inAppConfig[@"offset"] floatValue];
    }
    if (inAppConfig[@"backgroundAlpha"]) {
        self.backgroundAlpha = [inAppConfig[@"backgroundAlpha"] floatValue];
    }
}
@end


