/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/ipc/InputStreamUtils.h"
#include "nsArrayUtils.h"
#include "nsCOMPtr.h"
#include "nsIMutableArray.h"
#include "nsIPaymentRequestService.h"
#include "nsISupportsPrimitives.h"
#include "nsServiceManagerUtils.h"
#include "PaymentRequestData.h"
#include "PaymentRequestParent.h"

namespace mozilla {
namespace dom {

NS_IMPL_ISUPPORTS(PaymentRequestParent, nsIPaymentActionCallback)

PaymentRequestParent::PaymentRequestParent(uint64_t aTabId)
  : mActorAlived(true)
  , mTabId(aTabId)
{
}

mozilla::ipc::IPCResult
PaymentRequestParent::RecvRequestPayment(const IPCPaymentActionRequest& aRequest)
{
  if (!mActorAlived) {
    return IPC_FAIL_NO_REASON(this);
  }
  nsCOMPtr<nsIPaymentActionRequest> action;
  nsCOMPtr<nsIPaymentActionCallback> callback = do_QueryInterface(this);
  MOZ_ASSERT(callback);
  nsresult rv;
  switch (aRequest.type()) {
    case IPCPaymentActionRequest::TIPCPaymentCreateActionRequest: {
      const IPCPaymentCreateActionRequest& request = aRequest;

      nsCOMPtr<nsIMutableArray> methodData = do_CreateInstance(NS_ARRAY_CONTRACTID);
      MOZ_ASSERT(methodData);
      for (IPCPaymentMethodData data : request.methodData()) {
        nsCOMPtr<nsIPaymentMethodData> method;
        rv = payments::PaymentMethodData::Create(data, getter_AddRefs(method));
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return IPC_FAIL_NO_REASON(this);
        }
        rv = methodData->AppendElement(method, false);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return IPC_FAIL_NO_REASON(this);
        }
      }

      nsCOMPtr<nsIPaymentDetails> details;
      rv = payments::PaymentDetails::Create(request.details(), getter_AddRefs(details));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }

      nsCOMPtr<nsIPaymentOptions> options;
      rv = payments::PaymentOptions::Create(request.options(), getter_AddRefs(options));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }

      nsCOMPtr<nsIPaymentCreateActionRequest> createAction =
        do_CreateInstance(NS_PAYMENT_CREATE_ACTION_REQUEST_CONTRACT_ID);
      if (NS_WARN_IF(!createAction)) {
        return IPC_FAIL_NO_REASON(this);
      }
      rv = createAction->InitRequest(request.requestId(),
                                     callback,
                                     mTabId,
                                     request.topLevelPrincipal(),
                                     methodData,
                                     details,
                                     options);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }

      action = do_QueryInterface(createAction);
      MOZ_ASSERT(action);
      break;
    }
    case IPCPaymentActionRequest::TIPCPaymentCanMakeActionRequest: {
      const IPCPaymentCanMakeActionRequest& request = aRequest;
      rv = CreateActionRequest(request.requestId(),
                               nsIPaymentActionRequest::CANMAKE_ACTION,
                               getter_AddRefs(action));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }
      break;
    }
    case IPCPaymentActionRequest::TIPCPaymentShowActionRequest: {
      const IPCPaymentShowActionRequest& request = aRequest;
      rv = CreateActionRequest(request.requestId(),
                               nsIPaymentActionRequest::SHOW_ACTION,
                               getter_AddRefs(action));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }
      break;
    }
    case IPCPaymentActionRequest::TIPCPaymentAbortActionRequest: {
      const IPCPaymentAbortActionRequest& request = aRequest;
      rv = CreateActionRequest(request.requestId(),
                               nsIPaymentActionRequest::ABORT_ACTION,
                               getter_AddRefs(action));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }
      break;
    }
    case IPCPaymentActionRequest::TIPCPaymentCompleteActionRequest: {
      const IPCPaymentCompleteActionRequest& request = aRequest;
      nsCOMPtr<nsIPaymentCompleteActionRequest> completeAction =
        do_CreateInstance(NS_PAYMENT_COMPLETE_ACTION_REQUEST_CONTRACT_ID);
      rv = completeAction->InitRequest(request.requestId(),
                                       callback,
                                       request.completeStatus());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }
      action = do_QueryInterface(completeAction);
      MOZ_ASSERT(action);
      break;
    }
    case IPCPaymentActionRequest::TIPCPaymentUpdateActionRequest: {
      const IPCPaymentUpdateActionRequest& request = aRequest;

      nsCOMPtr<nsIPaymentDetails> details;
      rv = payments::PaymentDetails::Create(request.details(), getter_AddRefs(details));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return IPC_FAIL_NO_REASON(this);
      }

      nsCOMPtr<nsIPaymentUpdateActionRequest> updateAction =
        do_CreateInstance(NS_PAYMENT_UPDATE_ACTION_REQUEST_CONTRACT_ID);
      rv = updateAction->InitRequest(request.requestId(),
                                     callback,
                                     details);
      action = do_QueryInterface(updateAction);
      MOZ_ASSERT(action);
      break;
    }
    default: {
      return IPC_FAIL(this, "Unexpected request type");
    }
  }
  nsCOMPtr<nsIPaymentRequestService> service =
    do_GetService(NS_PAYMENT_REQUEST_SERVICE_CONTRACT_ID);
  MOZ_ASSERT(service);
  rv = service->RequestPayment(action);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return IPC_FAIL_NO_REASON(this);
  }
  return IPC_OK();
}

NS_IMETHODIMP
PaymentRequestParent::RespondPayment(nsIPaymentActionResponse* aResponse)
{
  if (!NS_IsMainThread()) {
    nsCOMPtr<nsIPaymentActionCallback> self = do_QueryInterface(this);
    MOZ_ASSERT(self);
    nsCOMPtr<nsIPaymentActionResponse> response = aResponse;
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("PaymentRequestParent::RespondPayment",
                                                     [self, response] ()
    {
      self->RespondPayment(response);
    });
    return NS_DispatchToMainThread(r);
  }

  if (!mActorAlived) {
    return NS_ERROR_FAILURE;
  }
  uint32_t type;
  nsresult rv = aResponse->GetType(&type);
  NS_ENSURE_SUCCESS(rv, rv);
  nsAutoString requestId;
  rv = aResponse->GetRequestId(requestId);
  NS_ENSURE_SUCCESS(rv, rv);
  switch (type) {
    case nsIPaymentActionResponse::CANMAKE_ACTION: {
      nsCOMPtr<nsIPaymentCanMakeActionResponse> response = do_QueryInterface(aResponse);
      MOZ_ASSERT(response);
      bool result;
      rv = response->GetResult(&result);
      NS_ENSURE_SUCCESS(rv, rv);
      IPCPaymentCanMakeActionResponse actionResponse(requestId, result);
      if (!SendRespondPayment(actionResponse)) {
        return NS_ERROR_FAILURE;
      }
      break;
    }
    case nsIPaymentActionResponse::SHOW_ACTION: {
      nsCOMPtr<nsIPaymentShowActionResponse> response =
        do_QueryInterface(aResponse);
      MOZ_ASSERT(response);
      uint32_t acceptStatus;
      NS_ENSURE_SUCCESS(response->GetAcceptStatus(&acceptStatus), NS_ERROR_FAILURE);
      nsAutoString methodName;
      NS_ENSURE_SUCCESS(response->GetMethodName(methodName), NS_ERROR_FAILURE);
      nsAutoString data;
      NS_ENSURE_SUCCESS(response->GetData(data), NS_ERROR_FAILURE);
      nsAutoString payerName;
      NS_ENSURE_SUCCESS(response->GetPayerName(payerName), NS_ERROR_FAILURE);
      nsAutoString payerEmail;
      NS_ENSURE_SUCCESS(response->GetPayerEmail(payerEmail), NS_ERROR_FAILURE);
      nsAutoString payerPhone;
      NS_ENSURE_SUCCESS(response->GetPayerPhone(payerPhone), NS_ERROR_FAILURE);
      IPCPaymentShowActionResponse actionResponse(requestId,
                                                  acceptStatus,
                                                  methodName,
                                                  data,
                                                  payerName,
                                                  payerEmail,
                                                  payerPhone);
      if (!SendRespondPayment(actionResponse)) {
        return NS_ERROR_FAILURE;
      }
      break;
    }
    case nsIPaymentActionResponse::ABORT_ACTION: {
      nsCOMPtr<nsIPaymentAbortActionResponse> response =
        do_QueryInterface(aResponse);
      MOZ_ASSERT(response);
      bool isSucceeded;
      rv = response->IsSucceeded(&isSucceeded);
      NS_ENSURE_SUCCESS(rv, rv);
      IPCPaymentAbortActionResponse actionResponse(requestId, isSucceeded);
      if (!SendRespondPayment(actionResponse)) {
        return NS_ERROR_FAILURE;
      }
      break;
    }
    case nsIPaymentActionResponse::COMPLETE_ACTION: {
      nsCOMPtr<nsIPaymentCompleteActionResponse> response =
        do_QueryInterface(aResponse);
      MOZ_ASSERT(response);
      bool isCompleted;
      rv = response->IsCompleted(&isCompleted);
      NS_ENSURE_SUCCESS(rv, rv);
      IPCPaymentCompleteActionResponse actionResponse(requestId, isCompleted);
      if (!SendRespondPayment(actionResponse)) {
        return NS_ERROR_FAILURE;
      }
      break;
    }
    default: {
      return NS_ERROR_FAILURE;
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
PaymentRequestParent::ChangeShippingAddress(const nsAString& aRequestId,
                                            nsIPaymentAddress* aAddress)
{
  if (!NS_IsMainThread()) {
    nsCOMPtr<nsIPaymentActionCallback> self = this;
    nsCOMPtr<nsIPaymentAddress> address = aAddress;
    nsAutoString requestId(aRequestId);
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("dom::PaymentRequestParent::ChangeShippingAddress",
                                                     [self, requestId, address] ()
    {
      self->ChangeShippingAddress(requestId, address);
    });
    return NS_DispatchToMainThread(r);
  }
  if (!mActorAlived) {
    return NS_ERROR_FAILURE;
  }
  nsAutoString country;
  nsresult rv = aAddress->GetCountry(country);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIArray> iaddressLine;
  rv = aAddress->GetAddressLine(getter_AddRefs(iaddressLine));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString region;
  rv = aAddress->GetRegion(region);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString city;
  rv = aAddress->GetCity(city);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString dependentLocality;
  rv = aAddress->GetDependentLocality(dependentLocality);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString postalCode;
  rv = aAddress->GetPostalCode(postalCode);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString sortingCode;
  rv = aAddress->GetSortingCode(sortingCode);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString languageCode;
  rv = aAddress->GetLanguageCode(languageCode);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString organization;
  rv = aAddress->GetOrganization(organization);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString recipient;
  rv = aAddress->GetRecipient(recipient);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString phone;
  rv = aAddress->GetPhone(phone);
  NS_ENSURE_SUCCESS(rv, rv);

  nsTArray<nsString> addressLine;
  uint32_t length;
  rv = iaddressLine->GetLength(&length);
  NS_ENSURE_SUCCESS(rv, rv);
  for (uint32_t index = 0; index < length; ++index) {
    nsCOMPtr<nsISupportsString> iaddress = do_QueryElementAt(iaddressLine, index);
    MOZ_ASSERT(iaddress);
    nsAutoString address;
    rv = iaddress->GetData(address);
    NS_ENSURE_SUCCESS(rv, rv);
    addressLine.AppendElement(address);
  }

  IPCPaymentAddress ipcAddress(country, addressLine, region, city,
                               dependentLocality, postalCode, sortingCode,
                               languageCode, organization, recipient, phone);

  nsAutoString requestId(aRequestId);
  if (!SendChangeShippingAddress(requestId, ipcAddress)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP
PaymentRequestParent::ChangeShippingOption(const nsAString& aRequestId,
                                           const nsAString& aOption)
{
  if (!NS_IsMainThread()) {
    nsCOMPtr<nsIPaymentActionCallback> self = this;
    nsAutoString requestId(aRequestId);
    nsAutoString option(aOption);
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("dom::PaymentRequestParent::ChangeShippingOption",
                                                     [self, requestId, option] ()
    {
      self->ChangeShippingOption(requestId, option);
    });
    return NS_DispatchToMainThread(r);
  }
  if (!mActorAlived) {
    return NS_ERROR_FAILURE;
  }
  nsAutoString requestId(aRequestId);
  nsAutoString option(aOption);
  if (!SendChangeShippingOption(requestId, option)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

mozilla::ipc::IPCResult
PaymentRequestParent::Recv__delete__()
{
  mActorAlived = false;
  return IPC_OK();
}

void
PaymentRequestParent::ActorDestroy(ActorDestroyReason aWhy)
{
  mActorAlived = false;
  nsCOMPtr<nsIPaymentRequestService> service =
    do_GetService(NS_PAYMENT_REQUEST_SERVICE_CONTRACT_ID);
  MOZ_ASSERT(service);
  nsCOMPtr<nsIPaymentActionCallback> callback = do_QueryInterface(this);
  MOZ_ASSERT(callback);
  nsresult rv = service->RemoveActionCallback(callback);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    MOZ_ASSERT(false);
  }
}

nsresult
PaymentRequestParent::CreateActionRequest(const nsAString& aRequestId,
                                          uint32_t aActionType,
                                          nsIPaymentActionRequest** aAction)
{
  NS_ENSURE_ARG_POINTER(aAction);
  nsCOMPtr<nsIPaymentActionRequest> action =
    do_CreateInstance(NS_PAYMENT_ACTION_REQUEST_CONTRACT_ID);
  MOZ_ASSERT(action);
  nsCOMPtr<nsIPaymentActionCallback> callback = do_QueryInterface(this);
  MOZ_ASSERT(callback);
  nsresult rv = action->Init(aRequestId, aActionType, callback);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  action.forget(aAction);
  return NS_OK;
}

} // end of namespace dom
} // end of namespace mozilla
