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

// Note: the file browser-tabsintitlebar-stub.js is used instead of
// this one on platforms which don't have CAN_DRAW_IN_TITLEBAR defined.

var TabsInTitlebar = {
  init: function () {
    if (this._initialized) {
      return;
    }
    this._readPref();
    Services.prefs.addObserver(this._prefName, this, false);

    // We need to update the appearance of the titlebar when the menu changes
    // from the active to the inactive state. We can't, however, rely on
    // DOMMenuBarInactive, because the menu fires this event and then removes
    // the inactive attribute after an event-loop spin.
    //
    // Because updating the appearance involves sampling the heights and margins
    // of various elements, it's important that the layout be more or less
    // settled before updating the titlebar. So instead of listening to
    // DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to
    // watch the "invalid" attribute directly.
    let menu = document.getElementById("toolbar-menubar");
    this._menuObserver = new MutationObserver(this._onMenuMutate);
    this._menuObserver.observe(menu, {attributes: true});

    this.onAreaReset = function(aArea) {
      if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
        this._update(true);
    };
    this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) {
      if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
        this._update(true);
    };
    CustomizableUI.addListener(this);

    addEventListener("resolutionchange", this, false);

    this._initialized = true;
    if (this._updateOnInit) {
      // We don't need to call this with 'true', even if original calls
      // (before init()) did, because this will be the first call and so
      // we will update anyway.
      this._update();
    }
  },

  allowedBy: function (condition, allow) {
    if (allow) {
      if (condition in this._disallowed) {
        delete this._disallowed[condition];
        this._update(true);
      }
    } else {
      if (!(condition in this._disallowed)) {
        this._disallowed[condition] = null;
        this._update(true);
      }
    }
  },

  updateAppearance: function updateAppearance(aForce) {
    this._update(aForce);
  },

  get enabled() {
    return document.documentElement.getAttribute("tabsintitlebar") == "true";
  },

  observe: function (subject, topic, data) {
    if (topic == "nsPref:changed")
      this._readPref();
  },

  handleEvent: function (aEvent) {
    if (aEvent.type == "resolutionchange" && aEvent.target == window) {
      this._update(true);
    }
  },

  _onMenuMutate: function (aMutations) {
    for (let mutation of aMutations) {
      if (mutation.attributeName == "inactive" ||
          mutation.attributeName == "autohide") {
        TabsInTitlebar._update(true);
        return;
      }
    }
  },

  _initialized: false,
  _updateOnInit: false,
  _disallowed: {},
  _prefName: "browser.tabs.drawInTitlebar",
  _lastSizeMode: null,

  _readPref: function () {
    this.allowedBy("pref",
                   Services.prefs.getBoolPref(this._prefName));
  },

  _update: function (aForce=false) {
    let $ = id => document.getElementById(id);
    let rect = ele => ele.getBoundingClientRect();
    let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);

    if (window.fullScreen)
      return;

    // In some edgecases it is possible for this to fire before we've initialized.
    // Don't run now, but don't forget to run it when we do initialize.
    if (!this._initialized) {
      this._updateOnInit = true;
      return;
    }

    let allowed = true;

    if (!aForce) {
      // _update is called on resize events, because the window is not ready
      // after sizemode events. However, we only care about the event when the
      // sizemode is different from the last time we updated the appearance of
      // the tabs in the titlebar.
      let sizemode = document.documentElement.getAttribute("sizemode");
      if (this._lastSizeMode == sizemode) {
        return;
      }
      let oldSizeMode = this._lastSizeMode;
      this._lastSizeMode = sizemode;
      // Don't update right now if we are leaving fullscreen, since the UI is
      // still changing in the consequent "fullscreen" event. Code there will
      // call this function again when everything is ready.
      // See browser-fullScreen.js: FullScreen.toggle and bug 1173768.
      if (oldSizeMode == "fullscreen") {
        return;
      }
    }

    for (let something in this._disallowed) {
      allowed = false;
      break;
    }

    let titlebar = $("titlebar");
    let titlebarContent = $("titlebar-content");
    let menubar = $("toolbar-menubar");

    if (allowed) {
      // We set the tabsintitlebar attribute first so that our CSS for
      // tabsintitlebar manifests before we do our measurements.
      document.documentElement.setAttribute("tabsintitlebar", "true");
      updateTitlebarDisplay();

      // Try to avoid reflows in this code by calculating dimensions first and
      // then later set the properties affecting layout together in a batch.

      // Get the full height of the tabs toolbar:
      let tabsToolbar = $("TabsToolbar");
      let tabsStyles = window.getComputedStyle(tabsToolbar);
      let fullTabsHeight = rect(tabsToolbar).height + verticalMargins(tabsStyles);
      // Buttons first:
      let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;

      let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles;
      if (AppConstants.platform == "macosx") {
        secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
        // No need to look up the menubar stuff on OS X:
        menuHeight = 0;
        fullMenuHeight = 0;
      } else {
        // Otherwise, get the height and margins separately for the menubar
        menuHeight = rect(menubar).height;
        menuStyles = window.getComputedStyle(menubar);
        fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
      }

      // And get the height of what's in the titlebar:
      let titlebarContentHeight = rect(titlebarContent).height;

      // Begin setting CSS properties which will cause a reflow

      // If the menubar is around (menuHeight is non-zero), try to adjust
      // its full height (i.e. including margins) to match the titlebar,
      // by changing the menubar's bottom padding
      if (menuHeight) {
        // Calculate the difference between the titlebar's height and that of the menubar
        let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
        let paddingBottom;
        // The titlebar is bigger:
        if (menuTitlebarDelta > 0) {
          fullMenuHeight += menuTitlebarDelta;
          // If there is already padding on the menubar, we need to add that
          // to the difference so the total padding is correct:
          if ((paddingBottom = menuStyles.paddingBottom)) {
            menuTitlebarDelta += parseFloat(paddingBottom);
          }
          menubar.style.paddingBottom = menuTitlebarDelta + "px";
        // The menubar is bigger, but has bottom padding we can remove:
        } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) {
          let existingPadding = parseFloat(paddingBottom);
          // menuTitlebarDelta is negative; work out what's left, but don't set negative padding:
          let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta);
          menubar.style.paddingBottom = desiredPadding + "px";
          // We've changed the menu height now:
          fullMenuHeight += desiredPadding - existingPadding;
        }
      }

      // Next, we calculate how much we need to stretch the titlebar down to
      // go all the way to the bottom of the tab strip, if necessary.
      let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;

      if (tabAndMenuHeight > titlebarContentHeight) {
        // We need to increase the titlebar content's outer height (ie including margins)
        // to match the tab and menu height:
        let extraMargin = tabAndMenuHeight - titlebarContentHeight;
        if (AppConstants.platform != "macosx") {
          titlebarContent.style.marginBottom = extraMargin + "px";
        }

        titlebarContentHeight += extraMargin;
      } else {
        titlebarContent.style.removeProperty("margin-bottom");
      }

      // Then add a negative margin to the titlebar, so that the following elements
      // will overlap it by the lesser of the titlebar height or the tabstrip+menu.
      let minTitlebarOrTabsHeight = Math.min(titlebarContentHeight, tabAndMenuHeight);
      titlebar.style.marginBottom = "-" + minTitlebarOrTabsHeight + "px";

      // Finally, size the placeholders:
      if (AppConstants.platform == "macosx") {
        this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
      }
      this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);

    } else {
      document.documentElement.removeAttribute("tabsintitlebar");
      updateTitlebarDisplay();

      if (AppConstants.platform == "macosx") {
        let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
        this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
      }

      // Reset the margins and padding that might have been modified:
      titlebarContent.style.marginTop = "";
      titlebarContent.style.marginBottom = "";
      titlebar.style.marginBottom = "";
      menubar.style.paddingBottom = "";
    }

    ToolbarIconColor.inferFromText();
    if (CustomizationHandler.isCustomizing()) {
      gCustomizeMode.updateLWTStyling();
    }
  },

  _sizePlaceholder: function (type, width) {
    Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
                  function (node) { node.width = width; });
  },

  uninit: function () {
    this._initialized = false;
    removeEventListener("resolutionchange", this);
    Services.prefs.removeObserver(this._prefName, this);
    this._menuObserver.disconnect();
    CustomizableUI.removeListener(this);
  }
};

function updateTitlebarDisplay() {
  if (AppConstants.platform == "macosx") {
    // OS X and the other platforms differ enough to necessitate this kind of
    // special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR,
    // we draw in the OS X titlebar when putting the tabs up there. However, OS X
    // also draws in the titlebar when a lightweight theme is applied, regardless
    // of whether or not the tabs are drawn in the titlebar.
    if (TabsInTitlebar.enabled) {
      document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1");
      document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
      document.documentElement.removeAttribute("drawtitle");
    } else {
      // We set chromemargin-nonlwtheme to "" instead of removing it as a way of
      // making sure that LightweightThemeConsumer doesn't take it upon itself to
      // detect this value again if and when we do a lwtheme state change.
      document.documentElement.setAttribute("chromemargin-nonlwtheme", "");
      let isCustomizing = document.documentElement.hasAttribute("customizing");
      let hasLWTheme = document.documentElement.hasAttribute("lwtheme");
      let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
      if ((!hasLWTheme || isCustomizing) && !isPrivate) {
        document.documentElement.removeAttribute("chromemargin");
      }
      document.documentElement.setAttribute("drawtitle", "true");
    }
  } else { // not OS X
    if (TabsInTitlebar.enabled)
      document.documentElement.setAttribute("chromemargin", "0,2,2,2");
    else
      document.documentElement.removeAttribute("chromemargin");
  }
}

function onTitlebarMaxClick() {
  if (window.windowState == window.STATE_MAXIMIZED)
    window.restore();
  else
    window.maximize();
}
