/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Purchasing module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3-COMM$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qmacinapppurchasebackend_p.h"
#include "qmacinapppurchaseproduct_p.h"
#include "qmacinapppurchasetransaction_p.h"

#include <QtCore/QString>

#import <StoreKit/StoreKit.h>

@interface QT_MANGLE_NAMESPACE(InAppPurchaseManager) : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
{
    QMacInAppPurchaseBackend *backend;
    NSMutableArray<SKPaymentTransaction *> *pendingTransactions;
}

-(void)requestProductData:(NSString *)identifier;
-(void)processPendingTransactions;

@end

@implementation QT_MANGLE_NAMESPACE(InAppPurchaseManager)

-(id)initWithBackend:(QMacInAppPurchaseBackend *)iapBackend {
    if (self = [super init]) {
        backend = iapBackend;
        pendingTransactions = [[NSMutableArray<SKPaymentTransaction *> alloc] init];
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        qRegisterMetaType<QMacInAppPurchaseProduct*>("QMacInAppPurchaseProduct*");
        qRegisterMetaType<QMacInAppPurchaseTransaction*>("QMacInAppPurchaseTransaction*");
    }
    return self;
}

-(void)dealloc
{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    [pendingTransactions release];
    [super dealloc];
}

-(void)requestProductData:(NSString *)identifier
{
    NSSet<NSString *> *productId = [NSSet<NSString *> setWithObject:identifier];
    SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productId];
    productsRequest.delegate = self;
    [productsRequest start];
}

-(void)processPendingTransactions
{
    NSMutableArray<SKPaymentTransaction *> *registeredTransactions = [NSMutableArray<SKPaymentTransaction *> array];

    for (SKPaymentTransaction *transaction in pendingTransactions) {
        QInAppTransaction::TransactionStatus status = [QT_MANGLE_NAMESPACE(InAppPurchaseManager) statusFromTransaction:transaction];

        QMacInAppPurchaseProduct *product = backend->registeredProductForProductId(QString::fromNSString(transaction.payment.productIdentifier));

        if (product) {
            //It is possible that the product doesn't exist yet (because of previous restores).
            QMacInAppPurchaseTransaction *qtTransaction = new QMacInAppPurchaseTransaction(transaction, status, product);
            if (qtTransaction->thread() != backend->thread()) {
                qtTransaction->moveToThread(backend->thread());
                QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, qtTransaction));
            }
            [registeredTransactions addObject:transaction];
            QMetaObject::invokeMethod(backend, "registerTransaction", Qt::AutoConnection, Q_ARG(QMacInAppPurchaseTransaction*, qtTransaction));
        }
    }

    //Remove registeredTransactions from pendingTransactions
    [pendingTransactions removeObjectsInArray:registeredTransactions];
}


//SKProductsRequestDelegate
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSArray<SKProduct *> *products = response.products;
    SKProduct *product = [products count] == 1 ? [[products firstObject] retain] : nil;

    if (product == nil) {
        //Invalid product ID
        NSString *invalidId = [response.invalidProductIdentifiers firstObject];
        QMetaObject::invokeMethod(backend, "registerQueryFailure", Qt::AutoConnection, Q_ARG(QString, QString::fromNSString(invalidId)));
    } else {
        //Valid product query
        //Create a QMacInAppPurchaseProduct
        QMacInAppPurchaseProduct *validProduct = new QMacInAppPurchaseProduct(product, backend->productTypeForProductId(QString::fromNSString([product productIdentifier])));
        if (validProduct->thread() != backend->thread()) {
            validProduct->moveToThread(backend->thread());
            QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, validProduct));
        }
        QMetaObject::invokeMethod(backend, "registerProduct", Qt::AutoConnection, Q_ARG(QMacInAppPurchaseProduct*, validProduct));
    }

    [request release];
}

+(QInAppTransaction::TransactionStatus)statusFromTransaction:(SKPaymentTransaction *)transaction
{
    QInAppTransaction::TransactionStatus status;
    switch (transaction.transactionState) {
        case SKPaymentTransactionStatePurchasing:
            //Ignore the purchasing state as it's not really a transaction
            //And its important that it doesn't need to be finalized as
            //Calling finishTransaction: on a transaction that is
            //in the SKPaymentTransactionStatePurchasing state throws an exception
            status = QInAppTransaction::Unknown;
            break;
        case SKPaymentTransactionStatePurchased:
            status = QInAppTransaction::PurchaseApproved;
            break;
        case SKPaymentTransactionStateFailed:
            status = QInAppTransaction::PurchaseFailed;
            break;
        case SKPaymentTransactionStateRestored:
            status = QInAppTransaction::PurchaseRestored;
            break;
        default:
            status = QInAppTransaction::Unknown;
            break;
    }
    return status;
}

//SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
    Q_UNUSED(queue);
    for (SKPaymentTransaction *transaction in transactions) {
        //Create QMacInAppPurchaseTransaction
        QInAppTransaction::TransactionStatus status = [QT_MANGLE_NAMESPACE(InAppPurchaseManager) statusFromTransaction:transaction];

        if (status == QInAppTransaction::Unknown)
            continue;

        QMacInAppPurchaseProduct *product = backend->registeredProductForProductId(QString::fromNSString(transaction.payment.productIdentifier));

        if (product) {
            //It is possible that the product doesn't exist yet (because of previous restores).
            QMacInAppPurchaseTransaction *qtTransaction = new QMacInAppPurchaseTransaction(transaction, status, product);
            if (qtTransaction->thread() != backend->thread()) {
                qtTransaction->moveToThread(backend->thread());
                QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, qtTransaction));
            }
            QMetaObject::invokeMethod(backend, "registerTransaction", Qt::AutoConnection, Q_ARG(QMacInAppPurchaseTransaction*, qtTransaction));
        } else {
            //Add the transaction to the pending transactions list
            [pendingTransactions addObject:transaction];
        }
    }
}

@end


QT_BEGIN_NAMESPACE

QMacInAppPurchaseBackend::QMacInAppPurchaseBackend(QObject *parent)
    : QInAppPurchaseBackend(parent)
    , m_iapManager(0)
{
}

QMacInAppPurchaseBackend::~QMacInAppPurchaseBackend()
{
    [m_iapManager release];
}

void QMacInAppPurchaseBackend::initialize()
{
    m_iapManager = [[QT_MANGLE_NAMESPACE(InAppPurchaseManager) alloc] initWithBackend:this];
    emit QInAppPurchaseBackend::ready();
}

bool QMacInAppPurchaseBackend::isReady() const
{
    if (m_iapManager)
        return true;
    return false;
}

void QMacInAppPurchaseBackend::queryProduct(QInAppProduct::ProductType productType, const QString &identifier)
{
    Q_UNUSED(productType)

    if (m_productTypeForPendingId.contains(identifier)) {
        qWarning("Product query already pending for %s", qPrintable(identifier));
        return;
    }

    m_productTypeForPendingId[identifier] = productType;

    [m_iapManager requestProductData:(identifier.toNSString())];
}

void QMacInAppPurchaseBackend::restorePurchases()
{
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

void QMacInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
{
    Q_UNUSED(propertyName);
    Q_UNUSED(value);
}

void QMacInAppPurchaseBackend::registerProduct(QMacInAppPurchaseProduct *product)
{
    QHash<QString, QInAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(product->identifier());
    Q_ASSERT(it != m_productTypeForPendingId.end());

    m_registeredProductForId[product->identifier()] = product;
    emit productQueryDone(product);
    m_productTypeForPendingId.erase(it);
    [m_iapManager processPendingTransactions];
}

void QMacInAppPurchaseBackend::registerQueryFailure(const QString &productId)
{
    QHash<QString, QInAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(productId);
    Q_ASSERT(it != m_productTypeForPendingId.end());

    emit QInAppPurchaseBackend::productQueryFailed(it.value(), it.key());
    m_productTypeForPendingId.erase(it);
}

void QMacInAppPurchaseBackend::registerTransaction(QMacInAppPurchaseTransaction *transaction)
{
    emit QInAppPurchaseBackend::transactionReady(transaction);
}

QInAppProduct::ProductType QMacInAppPurchaseBackend::productTypeForProductId(const QString &productId)
{
    return m_productTypeForPendingId[productId];
}

QMacInAppPurchaseProduct *QMacInAppPurchaseBackend::registeredProductForProductId(const QString &productId)
{
    return m_registeredProductForId[productId];
}

void QMacInAppPurchaseBackend::setParentToBackend(QObject *object)
{
    object->setParent(this);
}

QT_END_NAMESPACE

#include "moc_qmacinapppurchasebackend_p.cpp"
