/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */

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

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

XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                  "resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Console",
                                  "resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Integration",
                                  "resource://gre/modules/Integration.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PermissionUI",
                                  "resource:///modules/PermissionUI.jsm");

XPCOMUtils.defineLazyGetter(this, "gFlyWebBundle", function() {
  const tns = {
    "flyweb-button.label": "FlyWeb",
    "flyweb-button.tooltiptext": "Discover nearby FlyWeb services",
    "flyweb-items-empty": "There are no FlyWeb services currently nearby"
  };
  return {
    GetStringFromName(name) {
      return tns[name];
    }
  };
});

const FLYWEB_ENABLED_PREF = "dom.flyweb.enabled";

function install(aData, aReason) {}

function uninstall(aData, aReason) {}

function startup(aData, aReason) {
  // Observe pref changes and enable/disable as necessary.
  Services.prefs.addObserver(FLYWEB_ENABLED_PREF, prefObserver);

  // Only initialize if pref is enabled.
  let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
  if (enabled) {
    FlyWebView.init();
  }
}

function shutdown(aData, aReason) {
  Services.prefs.removeObserver(FLYWEB_ENABLED_PREF, prefObserver);

  let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
  if (enabled) {
    FlyWebView.uninit();
  }
}

// use enabled pref as a way for tests (e.g. test_contextmenu.html) to disable
// the addon when running.
function prefObserver(aSubject, aTopic, aData) {
  let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
  if (enabled) {
    FlyWebView.init();
  } else {
    FlyWebView.uninit();
  }
}

let gDiscoveryManagerInstance;

class DiscoveryManager {
  constructor(aWindow) {
    this._discoveryManager = new aWindow.FlyWebDiscoveryManager();
  }

  destroy() {
    if (this._id) {
      this.stop();
    }

    this._discoveryManager = null;
  }

  start(callback) {
    if (!this._id) {
      this._id = this._discoveryManager.startDiscovery(this);
    }

    this._callback = callback;
  }

  stop() {
    this._discoveryManager.stopDiscovery(this._id);

    this._id = null;
  }

  pairWith(serviceId, callback) {
    this._discoveryManager.pairWithService(serviceId, {
      pairingSucceeded(service) {
        callback(service);
      },

      pairingFailed(error) {
        console.error("FlyWeb failed to pair with service " + serviceId, error);
      }
    });
  }

  onDiscoveredServicesChanged(services) {
    if (!this._id || !this._callback) {
      return;
    }

    this._callback(services);
  }
}

const FlyWebPermissionPromptIntegration = (base) => ({
  __proto__: base,
  createPermissionPrompt(type, request) {
    if (type != "flyweb-publish-server") {
      return super.createPermissionPrompt(...arguments);
    }

    return {
      __proto__: PermissionUI.PermissionPromptForRequestPrototype,
      get request() {
        return request;
      },
      get permissionKey() {
        return "flyweb-publish-server";
      },
      get popupOptions() {
        return {
          learnMoreURL: "https://flyweb.github.io",
          popupIconURL: "chrome://flyweb-shared/skin/icon.svg",
        };
      },
      get notificationID() {
        return "flyweb-publish-server";
      },
      get anchorID() {
        const kAnchorID = "flyweb-publish-server-notification-icon";
        let chromeDoc = this.browser.ownerDocument;
        let anchor = chromeDoc.getElementById(kAnchorID);
        if (!anchor) {
          let notificationPopupBox =
            chromeDoc.getElementById("notification-popup-box");
          let notificationIcon = chromeDoc.createElement("image");
          notificationIcon.id = kAnchorID;
          notificationIcon.setAttribute("src",
                                        "chrome://flyweb-shared/skin/icon.svg");
          notificationIcon.classList.add("notification-anchor-icon");
          notificationIcon.setAttribute("role", "button");
          notificationIcon.setAttribute("aria-label",
                                        "View the publish-server request");
          notificationIcon.style.mozContextProperties = "fill";
          notificationIcon.style.fill = "currentcolor";
          notificationIcon.style.opacity = "0.4";
          notificationPopupBox.appendChild(notificationIcon);
        }

        return kAnchorID;
      },
      get message() {
        return "Would you like to let this site start a server accessible " +
               "to nearby devices and people?";
      },
      get promptActions() {
        return [{
          label: "Allow Server",
          accessKey: "A",
          action: Ci.nsIPermissionManager.ALLOW_ACTION,
          expireType:  Ci.nsIPermissionManager.EXPIRE_SESSION,
        }, {
          label: "Block Server",
          accessKey: "B",
          action: Ci.nsIPermissionManager.DENY_ACTION,
          expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
        }];
      },
    };
  },
});

let FlyWebView = {
  init() {
    // Create widget and add it to the menu panel.
    CustomizableUI.createWidget({
      id: "flyweb-button",
      type: "view",
      viewId: "flyweb-panel",
      label: gFlyWebBundle.GetStringFromName("flyweb-button.label"),
      tooltiptext: gFlyWebBundle.GetStringFromName("flyweb-button.tooltiptext"),

      onBeforeCreated(aDocument) {
        let panel = aDocument.createElement("panelview");
        panel.id = "flyweb-panel";
        panel.setAttribute("class", "PanelUI-subView");
        panel.setAttribute("flex", "1");

        let empty = aDocument.createElement("description");
        empty.id = "flyweb-items-empty";
        empty.setAttribute("mousethrough", "always");
        empty.textContent = gFlyWebBundle.GetStringFromName("flyweb-items-empty");

        let items = aDocument.createElement("vbox");
        items.id = "flyweb-items";
        items.setAttribute("class", "panel-subview-body");

        panel.appendChild(empty);
        panel.appendChild(items);

        panel.addEventListener("command", this);

        aDocument.getElementById("PanelUI-multiView").appendChild(panel);

        this._sheetURI = Services.io.newURI("chrome://flyweb/skin/flyweb.css");
        aDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).
            getInterface(Ci.nsIDOMWindowUtils).loadSheet(this._sheetURI, 1);
      },

      onDestroyed(aDocument) {
        aDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).
            getInterface(Ci.nsIDOMWindowUtils).removeSheet(this._sheetURI, 1);
      },

      onViewShowing(aEvent) {
        let doc = aEvent.target.ownerDocument;

        let items = doc.getElementById("flyweb-items");
        let empty = doc.getElementById("flyweb-items-empty");

        if (!gDiscoveryManagerInstance) {
          gDiscoveryManagerInstance = new DiscoveryManager(doc.defaultView);
        }

        gDiscoveryManagerInstance.start((services) => {
          while (items.firstChild) {
            items.firstChild.remove();
          }

          let fragment = doc.createDocumentFragment();

          for (let service of services) {
            let button = doc.createElement("toolbarbutton");
            button.setAttribute("class", "subviewbutton cui-withicon");
            button.setAttribute("label", service.displayName);
            button.setAttribute("data-service-id", service.serviceId);
            fragment.appendChild(button);
          }

          items.appendChild(fragment);

          empty.hidden = services.length > 0;
        });
      },

      onViewHiding(aEvent) {
        gDiscoveryManagerInstance.stop();
      },

      handleEvent(aEvent) {
        if (aEvent.type === "command") {
          let serviceId = aEvent.target.getAttribute("data-service-id");
          gDiscoveryManagerInstance.pairWith(serviceId, (service) => {
            aEvent.view.openUILinkIn(service.uiUrl, "tab");
          });
        }
      }
    });

    Integration.contentPermission
               .register(FlyWebPermissionPromptIntegration);
  },

  uninit() {
    CustomizableUI.destroyWidget("flyweb-button");

    if (gDiscoveryManagerInstance) {
      gDiscoveryManagerInstance.destroy();
      gDiscoveryManagerInstance = null;
    }

    Integration.contentPermission
               .unregister(FlyWebPermissionPromptIntegration);
  }
};
