/* 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/. */

"use strict";

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// JS shim that contains the callback functions to be triggered from the
// payment provider's code in order to fire DOMRequest events.
const kPaymentShimFile = "chrome://browser/content/payment.js";

// Type of MozChromEvents to handle payment dialogs.
const kOpenPaymentConfirmationEvent = "open-payment-confirmation-dialog";
const kOpenPaymentFlowEvent = "open-payment-flow-dialog";

XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                   "@mozilla.org/uuid-generator;1",
                                   "nsIUUIDGenerator");

function debug (s) {
  //dump("-*- PaymentGlue: " + s + "\n");
};

function PaymentUI() {
}

PaymentUI.prototype = {

  confirmPaymentRequest: function confirmPaymentRequest(aRequestId,
                                                        aRequests,
                                                        aSuccessCb,
                                                        aErrorCb) {
    let _error = function _error(errorMsg) {
      if (aErrorCb) {
        aErrorCb.onresult(aRequestId, errorMsg);
      }
    };

    let browser = Services.wm.getMostRecentWindow("navigator:browser");
    let content = browser.getContentWindow();
    if (!content) {
      _error("NO_CONTENT_WINDOW");
      return;
    }

    // The UI should listen for mozChromeEvent 'open-payment-confirmation-dialog'
    // type in order to create and show the payment request confirmation frame
    // embeded within a trusted dialog.
    let id = kOpenPaymentConfirmationEvent + "-" + this.getRandomId();
    let detail = {
      type: kOpenPaymentConfirmationEvent,
      id: id,
      paymentRequests: aRequests
    };

    // Once the user confirm the payment request and makes his choice, we get
    // back to the DOM part to get the appropriate payment flow information
    // based on the selected payment provider.
    content.addEventListener("mozContentEvent", function handleSelection(evt) {
      let msg = evt.detail;
      if (msg.id != id) {
        return;
      }

      if (msg.userSelection && aSuccessCb) {
        aSuccessCb.onresult(aRequestId, msg.userSelection);
      } else if (msg.errorMsg) {
        _error(msg.errorMsg);
      }

      content.removeEventListener("mozContentEvent", handleSelection);
    });

    browser.shell.sendChromeEvent(detail);
  },

  showPaymentFlow: function showPaymentFlow(aRequestId,
                                            aPaymentFlowInfo,
                                            aErrorCb) {
    let _error = function _error(errorMsg) {
      if (aErrorCb) {
        aErrorCb.onresult(aRequestId, errorMsg);
      }
    };

    // We ask the UI to browse to the selected payment flow.
    let browser = Services.wm.getMostRecentWindow("navigator:browser");
    let content = browser.getContentWindow();
    if (!content) {
      _error("NO_CONTENT_WINDOW");
      return;
    }

    let id = kOpenPaymentFlowEvent + "-" + this.getRandomId();
    let detail = {
      type: kOpenPaymentFlowEvent,
      id: id,
      uri: aPaymentFlowInfo.uri,
      method: aPaymentFlowInfo.requestMethod,
      jwt: aPaymentFlowInfo.jwt
    };

    // At some point the UI would send the created iframe back so the
    // callbacks for firing DOMRequest events can be loaded on its
    // content.
    content.addEventListener("mozContentEvent", (function loadPaymentShim(evt) {
      if (evt.detail.id != id) {
        content.removeEventListener("mozContentEvent", loadPaymentShim);
        return;
      }

      // Try to load the payment shim file containing the payment callbacks
      // in the content script.
      if (!evt.detail.frame && !evt.detail.errorMsg) {
        _error("ERROR_LOADING_PAYMENT_SHIM");
        return;
      }
      let frame = evt.detail.frame;
      let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
                        .frameLoader;
      let mm = frameLoader.messageManager;
      try {
        mm.loadFrameScript(kPaymentShimFile, true);
        mm.sendAsyncMessage("Payment:LoadShim", { requestId: aRequestId });
      } catch (e) {
        debug("Error loading " + kPaymentShimFile + " as a frame script: " + e);
        _error("ERROR_LOADING_PAYMENT_SHIM");
      } finally {
        content.removeEventListener("mozContentEvent", loadPaymentShim);
      }
    }).bind(this));

    // We also listen for UI notifications about a closed payment flow. The UI
    // should provide the reason of the closure within the 'errorMsg' parameter
    this._notifyPayFlowClosed = function _notifyPayFlowClosed (evt) {
      if (evt.detail.id != id) {
        return;
      }
      if (evt.detail.errorMsg) {
        _error(evt.detail.errorMsg);
        content.removeEventListener("mozContentEvent",
                                    this._notifyPayFlowClosed);
        return;
      }
    };
    content.addEventListener("mozContentEvent",
                             this._notifyPayFlowClosed.bind(this));

    browser.shell.sendChromeEvent(detail);
  },

  cleanup: function cleanup() {
    let browser = Services.wm.getMostRecentWindow("navigator:browser");
    let content = browser.getContentWindow();
    if (!content) {
      return;
    }
    content.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
  },

  getRandomId: function getRandomId() {
    return uuidgen.generateUUID().toString();
  },

  classID: Components.ID("{8b83eabc-7929-47f4-8b48-4dea8d887e4b}"),

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue])
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentUI]);
