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

// the "exported" symbols
var SocialUI,
    SocialShare,
    SocialActivationListener;

(function() {

XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
  let tmp = {};
  Cu.import("resource:///modules/Social.jsm", tmp);
  return tmp.OpenGraphBuilder;
});

XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
  let tmp = {};
  Cu.import("resource:///modules/Social.jsm", tmp);
  return tmp.DynamicResizeWatcher;
});

SocialUI = {
  _initialized: false,

  // Called on delayed startup to initialize the UI
  init: function SocialUI_init() {
    if (this._initialized) {
      return;
    }
    let mm = window.getGroupMessageManager("social");
    mm.loadFrameScript("chrome://browser/content/content.js", true);
    mm.loadFrameScript("chrome://browser/content/social-content.js", true);

    Services.obs.addObserver(this, "social:providers-changed", false);

    CustomizableUI.addListener(this);
    SocialActivationListener.init();

    Social.init().then((update) => {
      if (update)
        this._providersChanged();
    });

    this._initialized = true;
  },

  // Called on window unload
  uninit: function SocialUI_uninit() {
    if (!this._initialized) {
      return;
    }
    Services.obs.removeObserver(this, "social:providers-changed");

    CustomizableUI.removeListener(this);
    SocialActivationListener.uninit();

    this._initialized = false;
  },

  observe: function SocialUI_observe(subject, topic, data) {
    switch (topic) {
      case "social:providers-changed":
        this._providersChanged();
        break;
    }
  },

  _providersChanged: function() {
    SocialShare.populateProviderMenu();
  },

  showLearnMore: function() {
    let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
    openUILinkIn(url, "tab");
  },

  closeSocialPanelForLinkTraversal: function (target, linkNode) {
    // No need to close the panel if this traversal was not retargeted
    if (target == "" || target == "_self")
      return;

    // Check to see whether this link traversal was in a social panel
    let win = linkNode.ownerGlobal;
    let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIWebNavigation)
                                  .QueryInterface(Ci.nsIDocShell)
                                  .chromeEventHandler;
    let containerParent = container.parentNode;
    if (containerParent.classList.contains("social-panel") &&
        containerParent instanceof Ci.nsIDOMXULPopupElement) {
      // allow the link traversal to finish before closing the panel
      setTimeout(() => {
        containerParent.hidePopup();
      }, 0);
    }
  },

  get _chromeless() {
    // Is this a popup window that doesn't want chrome shown?
    let docElem = document.documentElement;
    // extrachrome is not restored during session restore, so we need
    // to check for the toolbar as well.
    let chromeless = docElem.getAttribute("chromehidden").includes("extrachrome") ||
                     docElem.getAttribute('chromehidden').includes("toolbar");
    // This property is "fixed" for a window, so avoid doing the check above
    // multiple times...
    delete this._chromeless;
    this._chromeless = chromeless;
    return chromeless;
  },

  get enabled() {
    // Returns whether social is enabled *for this window*.
    if (this._chromeless)
      return false;
    return Social.providers.length > 0;
  },

  canSharePage: function(aURI) {
    return (aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')));
  },

  onCustomizeEnd: function(aWindow) {
    if (aWindow != window)
      return;
    // customization mode gets buttons out of sync with command updating, fix
    // the disabled state
    let canShare = this.canSharePage(gBrowser.currentURI);
    let shareButton = SocialShare.shareButton;
    if (shareButton) {
      if (canShare) {
        shareButton.removeAttribute("disabled")
      } else {
        shareButton.setAttribute("disabled", "true")
      }
    }
  },

  // called on tab/urlbar/location changes and after customization. Update
  // anything that is tab specific.
  updateState: function() {
    goSetCommandEnabled("Social:PageShareable", this.canSharePage(gBrowser.currentURI));
  }
}

// message manager handlers
SocialActivationListener = {
  init: function() {
    messageManager.addMessageListener("Social:Activation", this);
  },
  uninit: function() {
    messageManager.removeMessageListener("Social:Activation", this);
  },
  receiveMessage: function(aMessage) {
    let data = aMessage.json;
    let browser = aMessage.target;
    data.window = window;
    // if the source if the message is the share panel, we do a one-click
    // installation. The source of activations is controlled by the
    // social.directories preference
    let options;
    if (browser == SocialShare.iframe && Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
      options = { bypassContentCheck: true, bypassInstallPanel: true };
    }

    Social.installProvider(data, function(manifest) {
      Social.activateFromOrigin(manifest.origin, function(provider) {
        if (provider.shareURL) {
          // Ensure that the share button is somewhere usable.
          // SocialShare.shareButton may return null if it is in the menu-panel
          // and has never been visible, so we check the widget directly. If
          // there is no area for the widget we move it into the toolbar.
          let widget = CustomizableUI.getWidget("social-share-button");
          // If the panel is already open, we can be sure that the provider can
          // already be accessed, possibly anchored to another toolbar button.
          // In that case we don't move the widget.
          if (!widget.areaType && SocialShare.panel.state != "open") {
            CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
            // Ensure correct state.
            SocialUI.onCustomizeEnd(window);
          }

          // make this new provider the selected provider. If the panel hasn't
          // been opened, we need to make the frame first.
          SocialShare._createFrame();
          SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
          SocialShare.iframe.setAttribute('origin', provider.origin);
          // get the right button selected
          SocialShare.populateProviderMenu();
          if (SocialShare.panel.state == "open") {
            SocialShare.sharePage(provider.origin);
          }
        }
        if (provider.postActivationURL) {
          // if activated from an open share panel, we load the landing page in
          // a background tab
          gBrowser.loadOneTab(provider.postActivationURL, {inBackground: SocialShare.panel.state == "open"});
        }
      });
    }, options);
  }
}

SocialShare = {
  get _dynamicResizer() {
    delete this._dynamicResizer;
    this._dynamicResizer = new DynamicResizeWatcher();
    return this._dynamicResizer;
  },

  // Share panel may be attached to the overflow or menu button depending on
  // customization, we need to manage open state of the anchor.
  get anchor() {
    let widget = CustomizableUI.getWidget("social-share-button");
    return widget.forWindow(window).anchor;
  },
  // Holds the anchor node in use whilst the panel is open, because it may vary.
  _currentAnchor: null,

  get panel() {
    return document.getElementById("social-share-panel");
  },

  get iframe() {
    // panel.firstChild is our toolbar hbox, panel.lastChild is the iframe
    // container hbox used for an interstitial "loading" graphic
    return this.panel.lastChild.firstChild;
  },

  uninit: function () {
    if (this.iframe) {
      let mm = this.messageManager;
      mm.removeMessageListener("PageVisibility:Show", this);
      mm.removeMessageListener("PageVisibility:Hide", this);
      mm.removeMessageListener("Social:DOMWindowClose", this);
      this.iframe.removeEventListener("load", this);
      this.iframe.remove();
    }
  },

  _createFrame: function() {
    let panel = this.panel;
    if (this.iframe)
      return;
    this.panel.hidden = false;
    // create and initialize the panel for this window
    let iframe = document.createElement("browser");
    iframe.setAttribute("type", "content");
    iframe.setAttribute("class", "social-share-frame");
    iframe.setAttribute("context", "contentAreaContextMenu");
    iframe.setAttribute("tooltip", "aHTMLTooltip");
    iframe.setAttribute("disableglobalhistory", "true");
    iframe.setAttribute("flex", "1");
    iframe.setAttribute("message", "true");
    iframe.setAttribute("messagemanagergroup", "social");
    panel.lastChild.appendChild(iframe);
    let mm = this.messageManager;
    mm.addMessageListener("PageVisibility:Show", this);
    mm.addMessageListener("PageVisibility:Hide", this);
    mm.sendAsyncMessage("Social:SetErrorURL",
                        { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
    iframe.addEventListener("load", this, true);
    mm.addMessageListener("Social:DOMWindowClose", this);

    this.populateProviderMenu();
  },

  get messageManager() {
    // The xbl bindings for the iframe may not exist yet, so we can't
    // access iframe.messageManager directly - but can get at it with this dance.
    return this.iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
  },

  receiveMessage: function(aMessage) {
    let iframe = this.iframe;
    switch(aMessage.name) {
      case "PageVisibility:Show":
        SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
        break;
      case "PageVisibility:Hide":
        SocialShare._dynamicResizer.stop();
        break;
      case "Social:DOMWindowClose":
        this.panel.hidePopup();
        break;
    }
  },

  handleEvent: function(event) {
    switch (event.type) {
      case "load": {
        this.iframe.parentNode.removeAttribute("loading");
        if (this.currentShare)
          SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
      }
    }
  },

  getSelectedProvider: function() {
    let provider;
    let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
    if (lastProviderOrigin) {
      provider = Social._getProviderFromOrigin(lastProviderOrigin);
    }
    return provider;
  },

  createTooltip: function(event) {
    let tt = event.target;
    let provider = Social._getProviderFromOrigin(tt.triggerNode.getAttribute("origin"));
    tt.firstChild.setAttribute("value", provider.name);
    tt.lastChild.setAttribute("value", provider.origin);
  },

  populateProviderMenu: function() {
    if (!this.iframe)
      return;
    let providers = Social.providers.filter(p => p.shareURL);
    let hbox = document.getElementById("social-share-provider-buttons");
    // remove everything before the add-share-provider button (which should also
    // be lastChild if any share providers were added)
    let addButton = document.getElementById("add-share-provider");
    while (hbox.lastChild != addButton) {
      hbox.removeChild(hbox.lastChild);
    }
    let selectedProvider = this.getSelectedProvider();
    for (let provider of providers) {
      let button = document.createElement("toolbarbutton");
      button.setAttribute("class", "toolbarbutton-1 share-provider-button");
      button.setAttribute("type", "radio");
      button.setAttribute("group", "share-providers");
      button.setAttribute("image", provider.iconURL);
      button.setAttribute("tooltip", "share-button-tooltip");
      button.setAttribute("origin", provider.origin);
      button.setAttribute("label", provider.name);
      button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin'));");
      if (provider == selectedProvider) {
        this.defaultButton = button;
      }
      hbox.appendChild(button);
    }
    if (!this.defaultButton) {
      this.defaultButton = addButton;
    }
    this.defaultButton.setAttribute("checked", "true");
  },

  get shareButton() {
    // web-panels (bookmark/sidebar) don't include customizableui, so
    // nsContextMenu fails when accessing shareButton, breaking
    // browser_bug409481.js.
    if (!window.CustomizableUI)
      return null;
    let widget = CustomizableUI.getWidget("social-share-button");
    if (!widget || !widget.areaType)
      return null;
    return widget.forWindow(window).node;
  },

  _onclick: function() {
    Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(0);
  },

  onShowing: function() {
    (this._currentAnchor || this.anchor).setAttribute("open", "true");
    this.iframe.addEventListener("click", this._onclick, true);
  },

  onHidden: function() {
    (this._currentAnchor || this.anchor).removeAttribute("open");
    this._currentAnchor = null;
    this.iframe.docShellIsActive = false;
    this.iframe.removeEventListener("click", this._onclick, true);
    this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
    // make sure that the frame is unloaded after it is hidden
    this.messageManager.sendAsyncMessage("Social:ClearFrame");
    this.currentShare = null;
    // share panel use is over, purge any history
    this.iframe.purgeSessionHistory();
  },

  sharePage: function(providerOrigin, graphData, target, anchor) {
    // if providerOrigin is undefined, we use the last-used provider, or the
    // current/default provider.  The provider selection in the share panel
    // will call sharePage with an origin for us to switch to.
    this._createFrame();
    let iframe = this.iframe;

    // graphData is an optional param that either defines the full set of data
    // to be shared, or partial data about the current page. It is set by a call
    // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
    // define at least url. If it is undefined, we're sharing the current url in
    // the browser tab.
    let pageData = graphData ? graphData : this.currentShare;
    let sharedURI = pageData ? Services.io.newURI(pageData.url, null, null) :
                                gBrowser.currentURI;
    if (!SocialUI.canSharePage(sharedURI))
      return;

    let browserMM = gBrowser.selectedBrowser.messageManager;

    // the point of this action type is that we can use existing share
    // endpoints (e.g. oexchange) that do not support additional
    // socialapi functionality.  One tweak is that we shoot an event
    // containing the open graph data.
    let _dataFn;
    if (!pageData || sharedURI == gBrowser.currentURI) {
      browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
        browserMM.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
        let pageData = msg.json;
        if (graphData) {
          // overwrite data retreived from page with data given to us as a param
          for (let p in graphData) {
            pageData[p] = graphData[p];
          }
        }
        this.sharePage(providerOrigin, pageData, target, anchor);
      });
      browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
      return;
    }
    // if this is a share of a selected item, get any microformats
    if (!pageData.microformats && target) {
      browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
        browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
        pageData.microformats = msg.data;
        this.sharePage(providerOrigin, pageData, target, anchor);
      });
      browserMM.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
      return;
    }
    this.currentShare = pageData;

    let provider;
    if (providerOrigin)
      provider = Social._getProviderFromOrigin(providerOrigin);
    else
      provider = this.getSelectedProvider();
    if (!provider || !provider.shareURL) {
      this.showDirectory(anchor);
      return;
    }
    // check the menu button
    let hbox = document.getElementById("social-share-provider-buttons");
    let btn = hbox.querySelector("[origin='" + provider.origin + "']");
    if (btn)
      btn.checked = true;

    let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);

    this._dynamicResizer.stop();
    let size = provider.getPageSize("share");
    if (size) {
      // let the css on the share panel define width, but height
      // calculations dont work on all sites, so we allow that to be
      // defined.
      delete size.width;
    }

    // if we've already loaded this provider/page share endpoint, we don't want
    // to add another load event listener.
    let endpointMatch = shareEndpoint == iframe.getAttribute("src");
    if (endpointMatch) {
      this._dynamicResizer.start(iframe.parentNode, iframe, size);
      iframe.docShellIsActive = true;
      SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
    } else {
      iframe.parentNode.setAttribute("loading", "true");
    }
    // if the user switched between share providers we do not want that history
    // available.
    iframe.purgeSessionHistory();

    // always ensure that origin belongs to the endpoint
    let uri = Services.io.newURI(shareEndpoint, null, null);
    iframe.setAttribute("origin", provider.origin);
    iframe.setAttribute("src", shareEndpoint);
    this._openPanel(anchor);
  },

  showDirectory: function(anchor) {
    this._createFrame();
    let iframe = this.iframe;
    if (iframe.getAttribute("src") == "about:providerdirectory")
      return;
    iframe.removeAttribute("origin");
    iframe.parentNode.setAttribute("loading", "true");

    iframe.setAttribute("src", "about:providerdirectory");
    this._openPanel(anchor);
  },

  _openPanel: function(anchor) {
    this._currentAnchor = anchor || this.anchor;
    anchor = document.getAnonymousElementByAttribute(this._currentAnchor, "class", "toolbarbutton-icon");
    this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
    Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(0);
  }
};

})();
