
/*******************************************************
 * Copyright (C) 2018 XMind Ltd. - All Rights Reserved
 *******************************************************/

#import <StoreKit/StoreKit.h>

#import <nan.h>
#import <string>
#import <vector>
#import <uv.h>

static NSMutableSet* _productCache = NULL;
static NSMutableSet* getProductCache() {
  if (_productCache == NULL) {
    _productCache = [[NSMutableSet alloc] init];
  }
  return _productCache;
}

void onGetProductsInMainThread(uv_async_t* handle);

@interface VanaInAppProduct : NSObject <SKProductsRequestDelegate> {
  @private
    Nan::Callback* callback_;
    uv_async_t async; // keep this instance around for as long as we might need to do the periodic callback
    NSArray* products;
    NSString* errorMessage;
}

- (id)initWithCallback:(Nan::Callback*) callback;
@end

@implementation VanaInAppProduct

/**
 * Init with a callback.
 *
 * @param callback - The callback that will be called to return the products.
 */
- (id)initWithCallback: (Nan::Callback*)callback {
  if ((self = [super init])) {
    callback_ = callback;
    products = nil;

    // Add the product into cache, otherwise, it will be ARC-ed.
    NSMutableSet* cache = getProductCache();
    @synchronized(cache) {
      [cache addObject:self];
    }

    uv_async_init(uv_default_loop(), &async, onGetProductsInMainThread);
    async.data = (__bridge void*) self;
  }

  return self;
}

- (void) onGetProducts {

  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();

  Nan::HandleScope scope;

  if (products == nil) {
    v8::Local<v8::Value> argv[] = {
      Nan::New<v8::Array>(0),
      Nan::New([errorMessage UTF8String]).ToLocalChecked()
    };
    callback_->Call(2, argv);
  } else {
    v8::Local<v8::Array> convertedProducts = Nan::New<v8::Array>([products count]);
    uint32_t counter = 0;

    for (SKProduct * product in products) {
      v8::Local<v8::Object> productObj = Nan::New<v8::Object>();
      Nan::Set(convertedProducts, counter, productObj);
      counter += 1;

      // Product Identifier
      if (product.productIdentifier != nil) {
        productObj->Set(
          context,
          Nan::New("productIdentifier").ToLocalChecked(),
          Nan::New(product.productIdentifier.UTF8String).ToLocalChecked()
        );
      }

      // Product Attributes
      if (product.localizedDescription != nil) {
        productObj->Set(
          context,
          Nan::New("localizedDescription").ToLocalChecked(),
          Nan::New(product.localizedDescription.UTF8String).ToLocalChecked()
        );
      }
      if (product.localizedTitle != nil) {
        productObj->Set(
          context,
          Nan::New("localizedTitle").ToLocalChecked(),
          Nan::New(product.localizedTitle.UTF8String).ToLocalChecked()
        );
      }
      if (product.contentVersion != nil) {
        productObj->Set(
          context,
          Nan::New("contentVersion").ToLocalChecked(),
          Nan::New(product.contentVersion.UTF8String).ToLocalChecked()
        );
      }
      if (product.contentLengths != nil) {
        v8::Local<v8::Array> contentLengths = Nan::New<v8::Array>([product.contentLengths count]);
        uint32_t contentLengthCounter = 0;
        for (NSNumber* contentLength in product.contentLengths) {
          Nan::Set(contentLengths, contentLengthCounter, Nan::New(contentLength.intValue));
          contentLengthCounter += 1;
        }
        productObj->Set(
          context,
          Nan::New("contentLengths").ToLocalChecked(),
          contentLengths
        );
      }

      // Pricing Information
      if (product.price != nil) {
        productObj->Set(
          context,
          Nan::New("price").ToLocalChecked(),
          Nan::New([product.price doubleValue])
        );

        if (product.priceLocale != nil) {
          NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
          [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
          [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
          [numberFormatter setLocale:product.priceLocale];

          productObj->Set(
            context,
            Nan::New("formattedPrice").ToLocalChecked(),
            Nan::New(
              [numberFormatter stringFromNumber:product.price].UTF8String
            ).ToLocalChecked()
          );
        }
      }

      // Downloadable Content Information
      if (product.downloadable == true) {
        productObj->Set(
          context,
          Nan::New("downloadable").ToLocalChecked(),
          Nan::New(true)
        );
      }
    }

    v8::Local<v8::Value> argv[] = { convertedProducts };
    callback_->Call(1, argv);
  }


  uv_close((uv_handle_t*) &async, NULL);

  delete callback_;
  callback_ = NULL;
  async.data = NULL;
  products = nil;

  NSMutableSet* cache = getProductCache();
  @synchronized(cache) {
    [cache removeObject:self];
  }
}

/**
 * Return products.
 *
 * @param productIDs - The products' id to fetch.
 */
- (void)getProducts:(NSSet*)productIDs {
  SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
  productsRequest.delegate = self;
  [productsRequest start];
}

/**
 * @see SKProductsRequestDelegate
 */
- (void)productsRequest:(SKProductsRequest*)request
     didReceiveResponse:(SKProductsResponse*)response {
  // Get the products.
  products = response.products;
  uv_async_send(&async);
}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
  errorMessage = [error localizedDescription];
  uv_async_send(&async);
}
@end

void onGetProductsInMainThread(uv_async_t* handle) {
  [(__bridge id) handle->data onGetProducts];
}
