/*
 * Copyright (C) 2015, 2016 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebPaymentCoordinatorProxy.h"

#if ENABLE(APPLE_PAY)

#include "WebPageProxy.h"
#include "WebPaymentCoordinatorMessages.h"
#include "WebPaymentCoordinatorProxyMessages.h"
#include "WebProcessProxy.h"
#include <WebCore/PaymentAuthorizationStatus.h>

namespace WebKit {

static WebPaymentCoordinatorProxy* activePaymentCoordinatorProxy;

WebPaymentCoordinatorProxy::WebPaymentCoordinatorProxy(WebPageProxy& webPageProxy)
    : m_webPageProxy(webPageProxy)
    , m_weakPtrFactory(this)
    , m_state(State::Idle)
    , m_merchantValidationState(MerchantValidationState::Idle)
{
    m_webPageProxy.process().addMessageReceiver(Messages::WebPaymentCoordinatorProxy::messageReceiverName(), m_webPageProxy.pageID(), *this);
}

WebPaymentCoordinatorProxy::~WebPaymentCoordinatorProxy()
{
    if (activePaymentCoordinatorProxy == this)
        activePaymentCoordinatorProxy = nullptr;

    if (m_state != State::Idle)
        hidePaymentUI();

    m_webPageProxy.process().removeMessageReceiver(Messages::WebPaymentCoordinatorProxy::messageReceiverName(), m_webPageProxy.pageID());
}

void WebPaymentCoordinatorProxy::canMakePayments(bool& reply)
{
    reply = platformCanMakePayments();
}

void WebPaymentCoordinatorProxy::canMakePaymentsWithActiveCard(const String& merchantIdentifier, const String& domainName, uint64_t requestID)
{
    auto weakThis = m_weakPtrFactory.createWeakPtr();
    platformCanMakePaymentsWithActiveCard(merchantIdentifier, domainName, [weakThis, requestID](bool canMakePayments) {
        auto paymentCoordinatorProxy = weakThis.get();
        if (!paymentCoordinatorProxy)
            return;

        paymentCoordinatorProxy->m_webPageProxy.send(Messages::WebPaymentCoordinator::CanMakePaymentsWithActiveCardReply(requestID, canMakePayments));
    });
}

void WebPaymentCoordinatorProxy::openPaymentSetup(const String& merchantIdentifier, const String& domainName, uint64_t requestID)
{
    auto weakThis = m_weakPtrFactory.createWeakPtr();
    platformOpenPaymentSetup(merchantIdentifier, domainName, [weakThis, requestID](bool result) {
        auto paymentCoordinatorProxy = weakThis.get();
        if (!paymentCoordinatorProxy)
            return;

        paymentCoordinatorProxy->m_webPageProxy.send(Messages::WebPaymentCoordinator::OpenPaymentSetupReply(requestID, result));
    });
}

void WebPaymentCoordinatorProxy::showPaymentUI(const String& originatingURLString, const Vector<String>& linkIconURLStrings, const WebCore::PaymentRequest& paymentRequest, bool& result)
{
    // FIXME: Make this a message check.
    ASSERT(canBegin());

    if (activePaymentCoordinatorProxy) {
        activePaymentCoordinatorProxy->hidePaymentUI();
        activePaymentCoordinatorProxy->didCancelPayment();
    }

    activePaymentCoordinatorProxy = this;

    m_state = State::Activating;

    WebCore::URL originatingURL(WebCore::URL(), originatingURLString);

    Vector<WebCore::URL> linkIconURLs;
    for (const auto& linkIconURLString : linkIconURLStrings)
        linkIconURLs.append(WebCore::URL(WebCore::URL(), linkIconURLString));

    platformShowPaymentUI(originatingURL, linkIconURLs, paymentRequest, [this](bool result) {
        ASSERT(m_state == State::Activating);
        if (!result) {
            didCancelPayment();
            return;
        }

        m_state = State::Active;
    });

    result = true;
}

static bool isValidEnum(WebCore::PaymentAuthorizationStatus status)
{
    switch (status) {
    case WebCore::PaymentAuthorizationStatus::Success:
    case WebCore::PaymentAuthorizationStatus::Failure:
    case WebCore::PaymentAuthorizationStatus::InvalidBillingPostalAddress:
    case WebCore::PaymentAuthorizationStatus::InvalidShippingPostalAddress:
    case WebCore::PaymentAuthorizationStatus::InvalidShippingContact:
    case WebCore::PaymentAuthorizationStatus::PINRequired:
    case WebCore::PaymentAuthorizationStatus::PINIncorrect:
    case WebCore::PaymentAuthorizationStatus::PINLockout:
        return true;
    }

    return false;
}

void WebPaymentCoordinatorProxy::completeMerchantValidation(const WebCore::PaymentMerchantSession& paymentMerchantSession)
{
    // It's possible that the payment has been canceled already.
    if (m_state == State::Idle)
        return;

    // FIXME: This should be a MESSAGE_CHECK.
    ASSERT(m_merchantValidationState == MerchantValidationState::Validating);

    platformCompleteMerchantValidation(paymentMerchantSession);
    m_merchantValidationState = MerchantValidationState::ValidationComplete;
}

void WebPaymentCoordinatorProxy::completeShippingMethodSelection(uint32_t opaqueStatus, const std::optional<WebCore::PaymentRequest::TotalAndLineItems>& newTotalAndLineItems)
{
    // It's possible that the payment has been canceled already.
    if (m_state == State::Idle)
        return;

    // FIXME: This should be a MESSAGE_CHECK.
    ASSERT(m_state == State::ShippingMethodSelected);

    auto status = static_cast<WebCore::PaymentAuthorizationStatus>(opaqueStatus);

    // FIXME: Make this a message check.
    RELEASE_ASSERT(isValidEnum(status));

    platformCompleteShippingMethodSelection(status, newTotalAndLineItems);
    m_state = State::Active;
}

void WebPaymentCoordinatorProxy::completeShippingContactSelection(uint32_t opaqueStatus, const Vector<WebCore::PaymentRequest::ShippingMethod>& newShippingMethods, const std::optional<WebCore::PaymentRequest::TotalAndLineItems>& newTotalAndLineItems)
{
    // It's possible that the payment has been canceled already.
    if (m_state == State::Idle)
        return;

    // FIXME: This should be a MESSAGE_CHECK.
    ASSERT(m_state == State::ShippingContactSelected);

    auto status = static_cast<WebCore::PaymentAuthorizationStatus>(opaqueStatus);

    // FIXME: Make this a message check.
    RELEASE_ASSERT(isValidEnum(status));

    platformCompleteShippingContactSelection(status, newShippingMethods, newTotalAndLineItems);
    m_state = State::Active;
}

void WebPaymentCoordinatorProxy::completePaymentMethodSelection(const std::optional<WebCore::PaymentRequest::TotalAndLineItems>& newTotalAndLineItems)
{
    // It's possible that the payment has been canceled already.
    if (m_state == State::Idle)
        return;

    // FIXME: This should be a MESSAGE_CHECK.
    ASSERT(m_state == State::PaymentMethodSelected);

    platformCompletePaymentMethodSelection(newTotalAndLineItems);
    m_state = State::Active;
}

void WebPaymentCoordinatorProxy::completePaymentSession(uint32_t opaqueStatus)
{
    // It's possible that the payment has been canceled already.
    if (!canCompletePayment())
        return;

    auto status = static_cast<WebCore::PaymentAuthorizationStatus>(opaqueStatus);

    // FIXME: Make this a message check.
    RELEASE_ASSERT(isValidEnum(status));

    platformCompletePaymentSession(status);

    if (!WebCore::isFinalStateStatus(status)) {
        m_state = State::Active;
        return;
    }

    didReachFinalState();
}

void WebPaymentCoordinatorProxy::abortPaymentSession()
{
    // It's possible that the payment has been canceled already.
    if (!canAbort())
        return;

    hidePaymentUI();

    didReachFinalState();
}

void WebPaymentCoordinatorProxy::didCancelPayment()
{
    ASSERT(canCancel());

    m_webPageProxy.send(Messages::WebPaymentCoordinator::DidCancelPayment());

    didReachFinalState();
}

void WebPaymentCoordinatorProxy::validateMerchant(const WebCore::URL& url)
{
    ASSERT(m_merchantValidationState == MerchantValidationState::Idle);

    m_merchantValidationState = MerchantValidationState::Validating;
    m_webPageProxy.send(Messages::WebPaymentCoordinator::ValidateMerchant(url.string()));
}

void WebPaymentCoordinatorProxy::didAuthorizePayment(const WebCore::Payment& payment)
{
    m_state = State::Authorized;
    m_webPageProxy.send(Messages::WebPaymentCoordinator::DidAuthorizePayment(payment));
}

void WebPaymentCoordinatorProxy::didSelectShippingMethod(const WebCore::PaymentRequest::ShippingMethod& shippingMethod)
{
    ASSERT(m_state == State::Active);

    m_state = State::ShippingMethodSelected;
    m_webPageProxy.send(Messages::WebPaymentCoordinator::DidSelectShippingMethod(shippingMethod));
}

void WebPaymentCoordinatorProxy::didSelectShippingContact(const WebCore::PaymentContact& shippingContact)
{
    ASSERT(m_state == State::Active);

    m_state = State::ShippingContactSelected;
    m_webPageProxy.send(Messages::WebPaymentCoordinator::DidSelectShippingContact(shippingContact));
}

void WebPaymentCoordinatorProxy::didSelectPaymentMethod(const WebCore::PaymentMethod& paymentMethod)
{
    ASSERT(m_state == State::Active);

    m_state = State::PaymentMethodSelected;
    m_webPageProxy.send(Messages::WebPaymentCoordinator::DidSelectPaymentMethod(paymentMethod));
}

bool WebPaymentCoordinatorProxy::canBegin() const
{
    switch (m_state) {
    case State::Idle:
        return true;

    case State::Activating:
    case State::Active:
    case State::Authorized:
    case State::ShippingMethodSelected:
    case State::ShippingContactSelected:
    case State::PaymentMethodSelected:
        return false;
    }
}

bool WebPaymentCoordinatorProxy::canCancel() const
{
    switch (m_state) {
    case State::Activating:
    case State::Active:
    case State::Authorized:
    case State::ShippingMethodSelected:
    case State::ShippingContactSelected:
    case State::PaymentMethodSelected:
        return true;

    case State::Idle:
        return false;
    }
}

bool WebPaymentCoordinatorProxy::canCompletePayment() const
{
    switch (m_state) {
    case State::Authorized:
        return true;

    case State::Idle:
    case State::Activating:
    case State::Active:
    case State::ShippingMethodSelected:
    case State::ShippingContactSelected:
    case State::PaymentMethodSelected:
        return false;
    }
}

bool WebPaymentCoordinatorProxy::canAbort() const
{
    switch (m_state) {
    case State::Activating:
    case State::Active:
    case State::Authorized:
    case State::ShippingMethodSelected:
    case State::ShippingContactSelected:
    case State::PaymentMethodSelected:
        return true;

    case State::Idle:
        return false;
    }
}

void WebPaymentCoordinatorProxy::didReachFinalState()
{
    m_state = State::Idle;
    m_merchantValidationState = MerchantValidationState::Idle;

    ASSERT(activePaymentCoordinatorProxy == this);
    activePaymentCoordinatorProxy = nullptr;
}

}

#endif
