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

#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import <iostream>

#import "./iap.h"

NSString* ToNSStringNoCopy(const Nan::Utf8String& value) {
  const char* cstr = *value ? *value : "<string conversion failed>";
  return [[NSString alloc] initWithBytesNoCopy:(void*)cstr
                                        length:value.length()
                                      encoding:NSUTF8StringEncoding
                                  freeWhenDone:false];
}

NSString* ToNSString(const Nan::Utf8String& value) {
  const char* cstr = *value ? *value : "<string conversion failed>";
  return [[NSString alloc] initWithBytes:(void*)cstr
                                  length:value.length()
                                encoding:NSUTF8StringEncoding];
}

#import "./iap_product.mm" // Direct include, so we don't need to modify binding.gyp to compile this file.
#import "./iap_purchase.mm"
#import "./iap_transcatioinObverser.mm"

/*
 * Make the file activated, so that it can be handoff to iOS.
 * arguments:
 *   productID String - The identifiers of the product to purchase. (The identifier of com.example.app.product1 is product1).
 *   quantity Integer (optional) - The number of items the user wants to purchase.
 *   callback Function (optional) - The callback called when the payment is added to the PaymentQueue.
 *     isProductValid Boolean - Determine if the product is valid and added to the payment queue.
 *
 * You should listen for the transactions-updated event as soon as possible and certainly before you call purchaseProduct.
 */
NAN_METHOD(IapPurchaseProduct) {
  if (info.Length() < 1 || !info[0]->IsString()) {
    Nan::ThrowTypeError("Invalid arguments");
    return;
  }

  Nan::HandleScope scope;

  @autoreleasepool {
    Nan::Utf8String arg0(info[0]);
    NSString* productId = ToNSString(arg0);

    int quantity = 1;

    Nan::Callback* callback = NULL;

    if (info.Length() > 1) {
      if (info[1]->IsFunction()) {
        callback = new Nan::Callback(v8::Local<v8::Function>::Cast(info[1]));
      }
      if (info[1]->IsInt32()) {
        quantity = v8::Local<v8::Int32>::Cast(info[1])->Value();
      }
      if (info.Length() > 2 && info[2]->IsFunction()) {
        callback = new Nan::Callback(v8::Local<v8::Function>::Cast(info[2]));
      }
    }

    VanaInAppPurchase* purchase = [[VanaInAppPurchase alloc] initWithCallback:callback quantity:quantity];
    [purchase purchaseProduct:productId];
  }
}

/*
 * Retrieves the product descriptions.
 * arguments:
 *   productIDs String[] - The identifiers of the products to get.
 *   callback Function - The callback called with the products or an empty array if the products don't exist.
 *      products Product[] - Array of Product objects or String to indicate an error occurs.
 */
NAN_METHOD(IapGetProducts) {
  if (info.Length() != 2 || !info[0]->IsArray() || !info[1]->IsFunction()) {
    Nan::ThrowTypeError("Invalid arguments");
    return;
  }

  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();

  Nan::HandleScope scope;

  @autoreleasepool {
    auto productIdArg = v8::Local<v8::Array>::Cast(info[0]);
    auto idCount = productIdArg->Length();

    NSMutableSet* set = [NSMutableSet setWithCapacity:idCount];
    for (uint32_t i = 0; i < idCount; ++i) {
      Nan::Utf8String productId(productIdArg->Get(context,i).ToLocalChecked());
      [set addObject:ToNSString(productId)];
    }

    auto cb = v8::Local<v8::Function>::Cast(info[1]);
    VanaInAppProduct* product = [[VanaInAppProduct alloc] initWithCallback:new Nan::Callback(cb)];
    [product getProducts: set];
  }
}

/*
 * Returns Boolean, whether a user can make a payment.
 */
NAN_METHOD(IapCanMakePayments) {
  Nan::HandleScope scope;

  @autoreleasepool {
    info.GetReturnValue().Set(Nan::New((bool) [SKPaymentQueue canMakePayments]));
  }

}

/*
 * Returns String, the path to the receipt.
 */
NAN_METHOD(IapGetReceiptURL) {
  @autoreleasepool {
    NSURL* url = [[NSBundle mainBundle] appStoreReceiptURL];

    if (url != nil) {
      info.GetReturnValue().Set(Nan::New(url.fileSystemRepresentation).ToLocalChecked());
    } else {
      info.GetReturnValue().Set(Nan::New("").ToLocalChecked());
    }
  }
}

/*
 * Completes all pending transactions.
 */
NAN_METHOD(IapFinishAllTransactions) {
  Nan::HandleScope scope;

  @autoreleasepool {
    for (SKPaymentTransaction* transaction in SKPaymentQueue.defaultQueue.transactions) {
      [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    }
  }
}

/*
 * Completes the pending transactions corresponding to the date.
 * arguments:
 *   date String - The ISO formatted date of the transaction to finish.
 */
NAN_METHOD(IapFinishTransactionByDate) {
  Nan::HandleScope scope;

  @autoreleasepool {
    // Create the date formatter.
    NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
    NSLocale* enUSPOSIXLocale =
        [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
    [dateFormatter setLocale:enUSPOSIXLocale];
    [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];

    // Remove the transaction.
    Nan::Utf8String arg0(info[0]);
    NSString* transactionDate = ToNSStringNoCopy(arg0);

    for (SKPaymentTransaction* transaction in SKPaymentQueue.defaultQueue.transactions) {
      if ([transactionDate isEqualToString:[dateFormatter stringFromDate:transaction.transactionDate]]) {
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
      }
    }
  }
}

NAN_METHOD(IapRestoreCompletedTransactions) {
  Nan::HandleScope scope;
  if (info.Length() < 1 || !info[0]->IsFunction()) {
    Nan::ThrowTypeError("Invalid arguments");
    return;
  }

  @autoreleasepool {
    Nan::Callback* callback = new Nan::Callback(v8::Local<v8::Function>::Cast(info[0]));

    VanaPaymentHandler * handler = [[VanaPaymentHandler alloc] initWithCallback:callback];
    [handler restoreCompletedTransactions];
  }
}

NAN_METHOD(IapRefreshReceipt) {
  Nan::HandleScope scope;
  if (info.Length() < 1 || !info[0]->IsFunction()) {
    Nan::ThrowTypeError("Invalid arguments");
    return;
  }

  @autoreleasepool {
    Nan::Callback* callback = new Nan::Callback(v8::Local<v8::Function>::Cast(info[0]));

    VanaPaymentHandler * handler = [[VanaPaymentHandler alloc] initWithCallback:callback];
    [handler refreshReceipt];
  }
}
