//
//  BSUInterface.m
//  BlueStack
//
//  Created by Nagib Bin Azad on 11/21/22.
//

/// Returns an NSString copying the characters from |bytes|, a C array of UTF8-encoded bytes.
/// Returns nil if |bytes| is NULL.
#import <BlueStackSDK/BlueStackSDK-Swift.h>
#import <CoreLocation/CoreLocation.h>
#import "BSUInterface.h"
#import "BSUSettings.h"
#import "BSUPluginUtil.h"
#import "BSUInitializer.h"
#import "BSURewardedVideoAd.h"
#import "BSUInterstitialAd.h"
#import "BSUBanner.h"
#import "BSUNativeAd.h"
#import "BSURequestOptions.h"

static NSString *BSUStringFromUTF8String(const char *bytes) { return bytes ? @(bytes) : nil; }

/// Returns a C string from a C array of UTF8-encoded bytes.
static const char *cStringCopy(const char *string) {
    if (!string) {
        return NULL;
    }
    char *res = (char *)malloc(strlen(string) + 1);
    strcpy(res, string);
    return res;
}

/// Returns a C string from a C array of UTF8-encoded bytes.
static const char **cStringArrayCopy(NSArray *array) {
    if (array == nil) {
        return nil;
    }
    
    const char **stringArray;
    
    stringArray = calloc(array.count, sizeof(char *));
    for (int i = 0; i < array.count; i++) {
        stringArray[i] = cStringCopy([array[i] UTF8String]);
    }
    return stringArray;
}

/// Removes an object from the cache.
void BSURelease(BSUTypeRef ref) {
    if (ref) {
        BSUObjectCache *cache = [BSUObjectCache sharedInstance];
        [cache removeObjectForKey:[(__bridge NSObject *)ref BSU_referenceKey]];
    }
}

void BSUInitializeWithCallback(BSUTypeMobileAdsClientRef *mobileAdsClientRef, const char *appId, BSUTypeSettingsRef settingsRef, BSUSDKInitializationCompleteCallback sdkInitCallback, BSUAdaptersInitializationCompleteCallback adaptersInitCallback) {
    NSString *appIdString = BSUStringFromUTF8String(appId);
    BSUSettings *settings = (__bridge BSUSettings *)settingsRef;
    NSLog(@"BSUInitializeWithCallback called for %@",appIdString);
    // set the client as CrossPlatform
    [[NSUserDefaults standardUserDefaults] setBool: YES forKey:@"IsCrossPlatformNativeAd"];

    [[BSUInitializer sharedInstance] prepareBlueStackSDKWithAppId:appIdString withSettings:settings
    withSDKCompletionHandler:^(BOOL success, NSError *error) {
        NSLog(@"BSUInitializeWithCallback withCompletionHandler");
        sdkInitCallback(mobileAdsClientRef, success, (__bridge BSUTypeErrorRef)error);
    }
    withAdaptersInitCompletionHandler:^(InitializationStatus *bsInitStatus) {
        NSLog(@"BSUInitializeWithCallback withAdaptersInitCompletionHandler");
        if(adaptersInitCallback != nil){
            BSUObjectCache *cache = [BSUObjectCache sharedInstance];
            cache[bsInitStatus.BSU_referenceKey] = bsInitStatus;
            adaptersInitCallback(mobileAdsClientRef, (__bridge BSUTypeInitializationStatusRef)bsInitStatus);
        }
    }];    
}

int BSUGetInitState(BSUTypeInitializationStatusRef statusRef, const char *className) {
    InitializationStatus *initStatus = (__bridge InitializationStatus *)statusRef;
    NSString *classNameString = BSUStringFromUTF8String(className);
    AdapterState state = initStatus.adapterStatuses[classNameString].state;

    if (state == AdapterStateReady) {
        return 1;
    } else if (state == AdapterStateNotReady){
        return 0;
    } else {
        return 2;
    }
}

const char **BSUGetInitAdapterClasses(BSUTypeInitializationStatusRef statusRef) {
    // NSArray<NSString *> *classes = [NSArray arrayWithObject:@"BlueStack"];
    InitializationStatus *initStatus = (__bridge InitializationStatus *)statusRef;
    NSArray *classes = [initStatus.adapterStatuses allKeys];
    return cStringArrayCopy(classes);
}

int BSUGetInitNumberOfAdapterClasses(BSUTypeInitializationStatusRef statusRef) {
    InitializationStatus *initStatus = (__bridge InitializationStatus *)statusRef;
    return [initStatus.adapterStatuses count];
    // return 1;
}

const char *BSUGetInitDescription(BSUTypeInitializationStatusRef statusRef,
                                  const char *className) {
    InitializationStatus *initStatus = (__bridge InitializationStatus *)statusRef;
    NSString *classNameString = BSUStringFromUTF8String(className);
    const char *description = [initStatus.adapterStatuses[classNameString].statusDescription UTF8String];
    return cStringCopy(description);
    // return cStringCopy(@"BlueStack".UTF8String);
}

const char *BSUGetInitProviderName(BSUTypeInitializationStatusRef statusRef,
                                   const char *className) {
    InitializationStatus *initStatus = (__bridge InitializationStatus *)statusRef;
    NSString *classNameString = BSUStringFromUTF8String(className);
    const char *providerName = [initStatus.adapterStatuses[classNameString].name UTF8String];
    return cStringCopy(providerName);
    // return cStringCopy(@"BlueStack".UTF8String);
}

#pragma mark Settings

BSUTypeSettingsRef BSUCreateSettings() {
    BSUSettings *settings = [[BSUSettings alloc] init];
    BSUObjectCache *cache = [BSUObjectCache sharedInstance];
    cache[settings.BSU_referenceKey] = settings;
    return (__bridge BSUTypeSettingsRef)(settings);
}

void BSUSetDebugModeEnabled(BSUTypeSettingsRef settingsRef, BOOL debugModeEnabled) {
    BSUSettings *settings = (__bridge BSUSettings *)settingsRef;
    settings.isDebugModeEnabled = debugModeEnabled;
}

#pragma mark RequestOptions

BSUTypeRequestOptionsRef BSUCreateRequestOptions() {
    BSURequestOptions *options = [[BSURequestOptions alloc] init];
    BSUObjectCache *cache = [BSUObjectCache sharedInstance];
    cache[options.BSU_referenceKey] = options;
    return (__bridge BSUTypeRequestOptionsRef)(options);
}

void BSUSetGender(BSUTypeRequestOptionsRef requestOptionsRef, int gender) {
    BSURequestOptions *options = (__bridge BSURequestOptions *)requestOptionsRef;
    options.gender = (Gender)gender;
}

void BSUSetAge(BSUTypeRequestOptionsRef requestOptionsRef, int age) {
    BSURequestOptions *options = (__bridge BSURequestOptions *)requestOptionsRef;
    options.age = @(age);
}

void BSUSetLocation(BSUTypeRequestOptionsRef requestOptionsRef, double latitude, double longitude, int consentFlag) {
    BSURequestOptions *options = (__bridge BSURequestOptions *)requestOptionsRef;
    options.location = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
    options.consentFlag = consentFlag;
}

void BSULanguage(BSUTypeRequestOptionsRef requestOptionsRef, const char *language) {
    // Not supported in BlueStackSDK.RequestOptions
}

void BSUSetKeyword(BSUTypeRequestOptionsRef requestOptionsRef, const char *keyword) {
    BSURequestOptions *options = (__bridge BSURequestOptions *)requestOptionsRef;
    options.keyword = BSUStringFromUTF8String(keyword);
}

void BSUSetContentUrl(BSUTypeRequestOptionsRef requestOptionsRef, const char *contentUrl) {
    BSURequestOptions *options = (__bridge BSURequestOptions *)requestOptionsRef;
    options.contentUrl = BSUStringFromUTF8String(contentUrl);
}

void BSUDestroyRequestOptions(BSUTypeRequestOptionsRef requestOptionsRef) {
    BSURelease(requestOptionsRef);
}

#pragma mark Error

int BSUGetAdErrorCode(BSUTypeErrorRef error) {
    NSError *internalError = (__bridge NSError *)error;
    return (int)internalError.code;
}

const char *BSUGetAdErrorDomain(BSUTypeErrorRef error) {
    NSError *internalError = (__bridge NSError *)error;
    return cStringCopy(internalError.domain.UTF8String);
}

const char *BSUGetAdErrorMessage(BSUTypeErrorRef error) {
    NSError *internalError = (__bridge NSError *)error;
    return cStringCopy(internalError.localizedDescription.UTF8String);
}

#pragma mark Rewarded Video Ad

void BSUSetRewardedVideoAdCallbacks(BSUTypeRewardedVideoAdRef rewardedVideoAd,
                                    BSUOnRewardedVideoLoadedCallback
                                    onLoadAdCallback,
                                    BSUOnRewardedVideoErrorCallback
                                    onErrorCallback,
                                    BSUOnRewardedVideoClickedCallback
                                    onClickedCallback,
                                    BSUOnRewardedVideoClosedCallback
                                    onAdClosedCallback,
                                    BSUOnRewardedVideoAppearedCallback
                                    onAdAppearedCallback,
                                    BSUOnUserRewardEarnedCallback
                                    onRewardEarnedCallback) {
    BSURewardedVideoAd *internalRewardedVideoAd = (__bridge BSURewardedVideoAd *)rewardedVideoAd;
    internalRewardedVideoAd.onLoadedCallback = onLoadAdCallback;
    internalRewardedVideoAd.onErrorCallback = onErrorCallback;
    internalRewardedVideoAd.onClickedCallback = onClickedCallback;
    internalRewardedVideoAd.onAdClosedCallback = onAdClosedCallback;
    internalRewardedVideoAd.onAdAppearedCallback = onAdAppearedCallback;
    internalRewardedVideoAd.onRewardEarnedCallback = onRewardEarnedCallback;
}

BSUTypeRewardedVideoAdRef BSUCreateRewardedVideoAd(BSUTypeRewardedVideoAdClientRef *rewardedVideoAdClient, const char *placementId) {
    NSString *placementIdStr = BSUStringFromUTF8String(placementId);
    BSURewardedVideoAd *internalRewardedVideoAd = [[BSURewardedVideoAd alloc] initWithRewardedClientReference:rewardedVideoAdClient placementId:placementIdStr];
    BSUObjectCache *cache = [BSUObjectCache sharedInstance];
    cache[internalRewardedVideoAd.BSU_referenceKey] = internalRewardedVideoAd;
    return (__bridge BSUTypeRewardedVideoAdRef)internalRewardedVideoAd;
}

void BSULoadRewardedVideoAd(BSUTypeRewardedVideoAdRef rewardedVideoAd) {
    BSURewardedVideoAd *internalRewardedVideoAd = (__bridge BSURewardedVideoAd *)rewardedVideoAd;
    [internalRewardedVideoAd loadAd];
}

void BSULoadRewardedVideoAdWithRequestOptions(BSUTypeRewardedVideoAdRef rewardedVideoAd, BSUTypeRequestOptionsRef requestOptionsRef) {
    BSURewardedVideoAd *internalRewardedVideoAd = (__bridge BSURewardedVideoAd *)rewardedVideoAd;
    [internalRewardedVideoAd loadAdWithRequestOptions:requestOptionsRef];
}

void BSUShowRewardedVideoAd(BSUTypeRewardedVideoAdRef rewardedVideoAd) {
    BSURewardedVideoAd *internalRewardedVideoAd = (__bridge BSURewardedVideoAd *)rewardedVideoAd;
    [internalRewardedVideoAd show];
}

void BSUDestroyRewardedVideoAd(BSUTypeRewardedVideoAdRef rewardedVideoAd) {
    BSURewardedVideoAd *internalRewardedVideoAd = (__bridge BSURewardedVideoAd *)rewardedVideoAd;
    [internalRewardedVideoAd destroy];
}

#pragma mark Interstitial Ad

void BSUSetInterstitialAdCallbacks(BSUTypeInterstitialRef interstitial,
                                   BSUOnInterstitialDidLoadedCallback
                                   onDidLoadedCallback,
                                   BSUOnInterstitialDidFailCallback
                                   onDidFailCallback,
                                   BSUOnInterstitialClickedCallback
                                   onClickedCallback,
                                   BSUOnInterstitialDisappearCallback
                                   onDisappearCallback,
                                   BSUOnInterstitialDidShownCallback
                                   onDidShownCallback) {
    BSUInterstitialAd *internalInterstitialAd = (__bridge BSUInterstitialAd *)interstitial;
    internalInterstitialAd.onDidLoadedCallback = onDidLoadedCallback;
    internalInterstitialAd.onDidFailCallback = onDidFailCallback;
    internalInterstitialAd.onClickedCallback = onClickedCallback;
    internalInterstitialAd.onDisappearCallback = onDisappearCallback;
    internalInterstitialAd.onDidShownCallback = onDidShownCallback;
}

BSUTypeInterstitialRef BSUCreateInterstitialAd(BSUTypeInterstitialClientRef *interstitialClient, const char *placementId) {
    NSString *placementIdStr = BSUStringFromUTF8String(placementId);
    BSUInterstitialAd *internalInterstitialAd = [[BSUInterstitialAd alloc] initWithInterstitialClientReference:interstitialClient placementId:placementIdStr];
    BSUObjectCache *cache = [BSUObjectCache sharedInstance];
    cache[internalInterstitialAd.BSU_referenceKey] = internalInterstitialAd;
    return (__bridge BSUTypeInterstitialRef)internalInterstitialAd;
}

void BSULoadInterstitialAd(BSUTypeInterstitialRef interstitial) {
    BSUInterstitialAd *internalInterstitial = (__bridge BSUInterstitialAd *)interstitial;
    [internalInterstitial loadAd];
}

void BSULoadInterstitialAdWithRequestOptions(BSUTypeInterstitialRef interstitial, BSUTypeRequestOptionsRef requestOptionsRef) {
    BSUInterstitialAd *internalInterstitial = (__bridge BSUInterstitialAd *)interstitial;
    [internalInterstitial loadAdWithRequestOptions:requestOptionsRef];
}

void BSUShowInterstitialAd(BSUTypeInterstitialRef interstitial) {
    BSUInterstitialAd *internalInterstitial = (__bridge BSUInterstitialAd *)interstitial;
    [internalInterstitial show];
}

void BSUDestroyInterstitialAd(BSUTypeInterstitialRef interstitial) {
    BSUInterstitialAd *internalInterstitial = (__bridge BSUInterstitialAd *)interstitial;
    [internalInterstitial destroy];
}

#pragma mark Banner Ad

BSUTypeBannerRef BSUCreateBannerAd(BSUTypeBannerClientRef *bannerClient, const char *placementId, BSUAdPosition adPosition) {
    BSUBanner *banner =
    [[BSUBanner alloc] initWithBannerClientReference:bannerClient
                                         placementId:BSUStringFromUTF8String(placementId)
                                         adPosition:adPosition];
    BSUObjectCache *cache = [BSUObjectCache sharedInstance];
    cache[banner.BSU_referenceKey] = banner;
    return (__bridge BSUTypeBannerRef)banner;
}

void BSUSetBannerAdCallbacks(BSUTypeBannerRef banner,
                             BSUOnBannerDidLoadCallback onBannerDidLoadCallback,
                             BSUOnBannerDidFailedCallback onBannerDidFailedCallback,
                             BSUOnAdClickedCallback onAdClickedCallback,
                             BSUOnBannerDidRefreshCallback onBannerDidRefreshCallback,
                             BSUOnBannerDidFailToRefreshCallback onBannerDidFailToRefreshCallback,
                             BSUOnBannerHideCallback onBannerHideCallback,
                             BSUOnBannerDisplayCallback onBannerDisplayCallback) {
    BSUBanner *internalBanner = (__bridge BSUBanner *)banner;
    internalBanner.onBannerDidLoadCallback = onBannerDidLoadCallback;
    internalBanner.onBannerDidFailedCallback = onBannerDidFailedCallback;
    internalBanner.onAdClickedCallback = onAdClickedCallback;
    internalBanner.onBannerDidRefreshCallback = onBannerDidRefreshCallback;
    internalBanner.onBannerDidFailToRefreshCallback = onBannerDidFailToRefreshCallback;
    internalBanner.onBannerHideCallback = onBannerHideCallback;
    internalBanner.onBannerDisplayCallback = onBannerDisplayCallback;
}

void BSULoadBannerAd(BSUTypeBannerRef banner, const char *adSize) {
    BSUBanner *internalBanner = (__bridge BSUBanner *)banner;
    [internalBanner loadAdWithAdSize:BSUStringFromUTF8String(adSize)];
}

void BSULoadBannerAdWithRequestOptions(BSUTypeBannerRef banner, const char *adSize, BSUTypeRequestOptionsRef requestOptionsRef) {
    BSUBanner *internalBanner = (__bridge BSUBanner *)banner;
    [internalBanner loadAdWithAdSize:BSUStringFromUTF8String(adSize) requestOptions:requestOptionsRef];
}

void BSUHideBannerAd(BSUTypeBannerRef banner) {
    BSUBanner *internalBanner = (__bridge BSUBanner *)banner;
    [internalBanner hide];
}

void BSUShowBannerAd(BSUTypeBannerRef banner) {
    BSUBanner *internalBanner = (__bridge BSUBanner *)banner;
    [internalBanner show];
}

void BSUSetBannerAdPosition(BSUTypeBannerRef banner, BSUAdPosition adPosition) {
    BSUBanner *internalBanner = (__bridge BSUBanner *)banner;
    [internalBanner setPosition:adPosition];
}

void BSUDestroyBannerAd(BSUTypeBannerRef banner) {
    BSUBanner *internalBanner = (__bridge BSUBanner *)banner;
    [internalBanner destroy];
}

#pragma mark Native Ad

void BSUSetNativeAdCallbacks(BSUTypeNativeAdRef nativeAd,
                                    BSUOnNativeAdDidLoadCallback
                                    onDidLoadCallback,
                                    BSUOnNativeAdDidFailCallback
                                    onDidFailCallback,
                                    BSUOnNativeAdDidRecordImpressionCallback
                                    onRecordImpressionCallback,
                                    BSUOnNativeAdDidPerformClickCallback
                                    onPerformClickCallback,
                                    BSUOnNativeAdCloseCallback
                                    onCloseCallback) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    internalNativeAd.onDidLoadCallback = onDidLoadCallback;
    internalNativeAd.onDidFailCallback = onDidFailCallback;
    internalNativeAd.onRecordImpressionCallback = onRecordImpressionCallback;
    internalNativeAd.onPerformClickCallback = onPerformClickCallback;
    internalNativeAd.onCloseCallback = onCloseCallback;
}

BSUTypeNativeAdRef BSUCreateNativeAd(BSUTypeNativeAdClientRef *nativeAdClient, const char *placementId) {
    NSString *placementIdStr = BSUStringFromUTF8String(placementId);
    BSUNativeAd *internalNativeAd = [[BSUNativeAd alloc] initWithNativeAdClientReference:nativeAdClient placementId:placementIdStr];
    BSUObjectCache *cache = [BSUObjectCache sharedInstance];
    cache[internalNativeAd.BSU_referenceKey] = internalNativeAd;
    return (__bridge BSUTypeNativeAdRef)internalNativeAd;
}

void BSULoadNativeAd(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    [internalNativeAd loadAd];
}

void BSULoadNativeAdWithRequestOptions(BSUTypeNativeAdRef nativeAd, BSUTypeRequestOptionsRef requestOptionsRef) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    [internalNativeAd loadAdWithRequestOptions:requestOptionsRef];
}

const char *BSUNativeAdGetTitle(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSLog(@"BSUNativeAdGetTitle %@",internalNativeAd.title);
    return cStringCopy(internalNativeAd.title.UTF8String);
}

const char *BSUNativeAdGetBodyText(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSLog(@"BSUNativeAdGetBodyText %@",internalNativeAd.body);
    return cStringCopy(internalNativeAd.body.UTF8String);
}

const char *BSUNativeAdGetCTAText(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSLog(@"BSUNativeAdGetCTAText %@",internalNativeAd.callToAction);
    return cStringCopy(internalNativeAd.callToAction.UTF8String);
}

const char *BSUNativeAdGetBadge(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSLog(@"BSUNativeAdGetBadge %@",internalNativeAd.badge);
    return cStringCopy(internalNativeAd.badge.UTF8String);
}

const char *BSUNativeAdGetIconUrl(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSLog(@"BSUNativeAdGetIconUrl %@",internalNativeAd.iconUrl);
    return cStringCopy(internalNativeAd.iconUrl.UTF8String);
}

const char *BSUNativeAdGetCoverImageUrl(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSLog(@"BSUNativeAdGetCoverImageUrl %@",internalNativeAd.coverImageUrl);
    return cStringCopy(internalNativeAd.coverImageUrl.UTF8String);
}

const char *BSUNativeAdGetClickUrl(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSLog(@"BSUNativeAdGetClickUrl %@",internalNativeAd.clickUrl);
    return cStringCopy(internalNativeAd.clickUrl.UTF8String);
}

void BSURecordImpression(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSDictionary *emptyDictionary = [NSDictionary dictionary]; // for later implementation
    [internalNativeAd recordImpression:emptyDictionary];
}

void BSUPerformClick(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    NSDictionary *emptyDictionary = [NSDictionary dictionary]; // for later implementation
    [internalNativeAd performClick:emptyDictionary];
}

void BSUDestroyNativeAd(BSUTypeNativeAdRef nativeAd) {
    BSUNativeAd *internalNativeAd = (__bridge BSUNativeAd *)nativeAd;
    [internalNativeAd destroy];
}
