/**
 * Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * For the applicable distribution terms see the license.txt -file, included in
 * the distribution.
 */

#include <iapclient.h>
#include <QDebug>
#include <QApplication>
#include <QFile>
#include <QDir>

#include "purchasableitembase.h"
#include "declarativepurchaselistmodel.h"

#define TICKETFOLDERNAME "tickets"

// For possible status  values please see
// https://projects.developer.nokia.com/dav/iap/doc/GUID-532F510B-9EE3-4069-AD8D-3E225C47A446/class_i_a_p_client.html
// The purchase was successful
#define REQUEST_STATUS_OK "OK"
// The purchase failed but the product is restorable
// It is recognized by OVI Store that current user has been already purchased the product
// on other device or in the previous application installation
#define REQUEST_STATUS_RESTORABLE "RestorableProduct"

// local function declaration
bool isTheSameProduct(IAPClient::ProductDataHash& left, IAPClient::ProductData& right);

//QT metatype declaration to be able data transfer this type with Qt::QueuedConnection
Q_DECLARE_METATYPE(IAPClient::ProductDataList)

PurchasableItemBase::PurchasableItemBase(DeclarativePurchaseListModel &model,
                                         const QString &productID,
                                         const QString &productUrl) :
    QObject(&model), //parent
    m_model(&model),
    m_isBusy(false),
    m_isKnown(false),
    m_unlocked_icon(NULL),
    m_buy_icon(NULL),
    m_notready_icon(NULL),
    m_productUrl(productUrl)
{
    m_productMetadata.productId = productID;

    // connect IAP API's signals to app's slots
    IAPClient *client = &m_model->client();
    qRegisterMetaType<IAPClient::ProductDataHash>("IAPClient::ProductDataHash");

    connect(client, SIGNAL(productDataReceived( int, QString, IAPClient::ProductDataHash)),
            this, SLOT(productDataReceived(int, QString, IAPClient::ProductDataHash)), Qt::QueuedConnection);
    connect(client, SIGNAL(purchaseCompleted( int , QString, QString)),
            this, SLOT(purchaseCompleted( int , QString, QString)),Qt::QueuedConnection);
    connect(client, SIGNAL(purchaseFlowFinished(int)),
            this, SLOT(purchaseFlowFinished(int)), Qt::QueuedConnection);
    connect(client, SIGNAL(restorationCompleted (int , QString , QString)),
            this, SLOT(restorationCompleted (int , QString , QString)), Qt::QueuedConnection);

    //initialize data
    m_productMetadata.dataRequestResult = "Pending";
    m_productMetadata.drmProtection = IAPClient::NokiaDrm;

    fetchMetadata();
}

PurchasableItemBase::~PurchasableItemBase()
{
    delete m_unlocked_icon;
    m_unlocked_icon = NULL;

    delete m_buy_icon;
    m_buy_icon = NULL;

    delete m_notready_icon;
    m_notready_icon = NULL;
}

QVariant PurchasableItemBase::metadata(int role) const
{
    switch(role){
        case ProductIdRole:
            return m_productMetadata.productId;
        case DataReqResultRole:
            return m_productMetadata.dataRequestResult;
        case TitleRole:
            return m_productMetadata.title;
        case ShortDescriptionRole:
            return m_productMetadata.shortDescription;
        case LongDescriptionRole:
            return m_productMetadata.longDescription;
        case PriceRole:
            return m_productMetadata.price;
        case DrmProtectionRole:
            return ((m_productMetadata.drmProtection == IAPClient::NokiaDrm) ? true : false);
        case IsMetadataKnown:
            return m_isKnown;
        case IsBusy:
            return m_isBusy;
        case IsActivated:
            return isActivated();
        default:
            break;
    }
    return QVariant();
}

bool PurchasableItemBase::setMetadata(const QVariant &value, int role)
{
    bool result = true;
    switch(role){
        case ProductIdRole:
            m_productMetadata.productId = value.toString();
            break;
        case DataReqResultRole:
            m_productMetadata.dataRequestResult = value.toString();
            break;
        case TitleRole:
            m_productMetadata.title = value.toString();
            break;
        case ShortDescriptionRole:
            m_productMetadata.shortDescription = value.toString();
            break;
        case LongDescriptionRole:
            m_productMetadata.longDescription = value.toString();
            break;
        case PriceRole:
            m_productMetadata.price = value.toString();
            break;
        case DrmProtectionRole:
            m_productMetadata.drmProtection = (value.toBool()) ?
                                        IAPClient::NokiaDrm : IAPClient::OtherDrm;
            break;
        case IsMetadataKnown: //read-only properties
        case IsBusy:
            m_isBusy = value.toBool();
            break;
        case IsActivated:
        default:
            result = false;
            break;
    }

    return result;
}

bool PurchasableItemBase::isMetaDataKnown() const
{
    return m_isKnown;
}

bool PurchasableItemBase::isBusy()
{
    return m_isBusy;
}

bool PurchasableItemBase::isActivated() const
{
    return (m_unlocked_icon != NULL);
}

void PurchasableItemBase::fetchMetadata()
{
    qDebug(__PRETTY_FUNCTION__);
    if (setBusy()){
        m_requestId = m_model->client().getProductData(m_productMetadata.productId);
    }
}

void PurchasableItemBase::purchase()
{
    qDebug(__PRETTY_FUNCTION__);
    if (setBusy()){
        m_requestId = m_model->client().purchaseProduct(m_productMetadata.productId,
                                                        IAPClient::NoForcedRestoration);
        m_model->beginModelUpdate();
        m_isBusy = false;
        m_model->commitModelUpdate();
    }
}

void PurchasableItemBase::restore()
{
    qDebug(__PRETTY_FUNCTION__);
    if (setBusy()){
        m_requestId = m_model->client().restoreProduct(m_productMetadata.productId);
    }
}

void PurchasableItemBase::productDataReceived(int requestId, QString status,
                                              IAPClient::ProductDataHash productData)
{
    if(requestId != m_requestId)
        return;

    qDebug(__FUNCTION__);
    m_model->beginModelUpdate();
    m_isBusy = false;
    m_isKnown = true;
    IAPClient::DrmType type = m_productMetadata.drmProtection;
    QString productId = m_productMetadata.productId;

    m_productMetadata.title = productData.value("info").toString();
    m_productMetadata.shortDescription = productData.value("shortdescription").toString();
    m_productMetadata.longDescription = productData.value("description").toString();
    m_productMetadata.price = productData.value("price").toString();
    m_productMetadata.dataRequestResult = productData.value("result").toString();

    m_productMetadata.drmProtection = type; //preserve DRM type
    m_productMetadata.productId = productId; //preserve Product ID

    if(isPurchased())
        setUnlocked(NULL);

    m_model->commitModelUpdate();
}

void PurchasableItemBase::purchaseCompleted(int requestId, QString status, QString purchaseTicket)
{
    if (requestId != m_requestId)
        return;

    qDebug(__PRETTY_FUNCTION__);
    m_model->beginModelUpdate();
    m_isBusy = false;
    if ((status.compare(REQUEST_STATUS_OK, Qt::CaseSensitive) == 0) ||
            (status.compare(REQUEST_STATUS_RESTORABLE, Qt::CaseSensitive) == 0)){
        setUnlocked(&purchaseTicket);
    }
    m_model->commitModelUpdate();
}

void PurchasableItemBase::purchaseFlowFinished(int requestId)
{
    if (requestId != m_requestId)
        return;

    qDebug(__PRETTY_FUNCTION__);
    m_isBusy = false;

    m_model->purchaseFlowFinished(isActivated());
}

void PurchasableItemBase::restorationCompleted(int requestId,
                                               QString status,
                                               QString purchaseTicket)
{
    if (requestId != m_requestId)
        return;

    qDebug(__PRETTY_FUNCTION__);
    m_model->beginModelUpdate();
    m_isBusy = false;
    
    if (status.compare("OK", Qt::CaseSensitive) == 0){
        setUnlocked(&purchaseTicket);
    }
    
    m_model->commitModelUpdate();
}

void PurchasableItemBase::restorationFlowFinished(int requestId)
{
    if (requestId != m_requestId)
        return;

    qDebug(__PRETTY_FUNCTION__);
    m_isBusy = false;
}

QImage& PurchasableItemBase::stateIcon() const
{
    if (isActivated())
        return *m_unlocked_icon;
    else if (m_isKnown)
        return *m_buy_icon;
    else
        return *m_notready_icon;
}

bool PurchasableItemBase::setBusy()
{
    if (isActivated())
        return false;	// already unlocked, no remote operation possible
		
    if (m_isBusy){
        qDebug() << "item is busy. you can set only one request at time on the item";
        return false;
    }
    
    m_model->beginModelUpdate();
    m_isBusy = true;
    m_model->commitModelUpdate();
    return true;
}

void PurchasableItemBase::saveTicket(QString *purchaseTicket)
{
    if (!purchaseTicket)
        return;

    QString privatedir(getTicketDir());
    if (!QDir(privatedir).exists())
        QDir().mkdir(privatedir);

    QFile file(getTicketUri());
    if (file.open(QIODevice::WriteOnly)){
        QDataStream out(&file);
        out << *purchaseTicket;
        file.close();
    }
    else
    {
        qDebug() << "PurchasableItemBase::testModeSaveTicket >>> Cannot open file for writing: ";
    }
}

bool PurchasableItemBase::readTicket()
{
    return (QFile(getTicketUri()).exists());
}

QString PurchasableItemBase::getTicketUri()
{
    QString fname(QApplication::applicationDirPath());
    fname.append("/");
    fname.append(TICKETFOLDERNAME);
    fname.append("/");
    fname.append(m_productMetadata.productId);
    fname.append(".ticket");
    return fname;
}

QString PurchasableItemBase::getTicketDir()
{
    QString fname(QApplication::applicationDirPath());
    fname.append("/");
    fname.append(TICKETFOLDERNAME);
    return fname;
}

bool PurchasableItemBase::isIncluded(IAPClient::ProductDataList& list)
{
    foreach(IAPClient::ProductDataHash item, list)
    {
        if(isTheSameProduct(item, m_productMetadata))
            return true;
    }

    return false;
}

bool isTheSameProduct(IAPClient::ProductDataHash& left, IAPClient::ProductData& right)
{
    return (left.value("id").toString().compare(right.productId) == 0);
}
