/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 {Cc, Cu, Ci} = require("chrome");
const promise = require("sdk/core/promise");

let ToolDefinitions = require("main").Tools;

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

loader.lazyGetter(this, "gDevTools", () => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
loader.lazyGetter(this, "RuleView", () => require("devtools/styleinspector/rule-view"));
loader.lazyGetter(this, "ComputedView", () => require("devtools/styleinspector/computed-view"));
loader.lazyGetter(this, "_strings", () => Services.strings
  .createBundle("chrome://global/locale/devtools/styleinspector.properties"));

const { PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");

// This module doesn't currently export any symbols directly, it only
// registers inspector tools.

function RuleViewTool(aInspector, aWindow, aIFrame)
{
  this.inspector = aInspector;
  this.doc = aWindow.document;
  this.outerIFrame = aIFrame;

  this.view = new RuleView.CssRuleView(aInspector, this.doc);
  this.doc.documentElement.appendChild(this.view.element);

  this._changeHandler = () => {
    this.inspector.markDirty();
  };

  this.view.element.addEventListener("CssRuleViewChanged", this._changeHandler);

  this._refreshHandler = () => {
    this.inspector.emit("rule-view-refreshed");
  };

  this.view.element.addEventListener("CssRuleViewRefreshed", this._refreshHandler);

  this._cssLinkHandler = (aEvent) => {
    let rule = aEvent.detail.rule;
    let sheet = rule.parentStyleSheet;

    // Chrome stylesheets are not listed in the style editor, so show
    // these sheets in the view source window instead.
    if (!sheet || sheet.isSystem) {
      let contentDoc = this.inspector.selection.document;
      let viewSourceUtils = this.inspector.viewSourceUtils;
      let href = rule.nodeHref || rule.href;
      viewSourceUtils.viewSource(href, null, contentDoc, rule.line || 0);
      return;
    }

    let location = promise.resolve(rule.location);
    if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
      location = rule.getOriginalLocation();
    }
    location.then(({ href, line, column }) => {
      let target = this.inspector.target;
      if (ToolDefinitions.styleEditor.isTargetSupported(target)) {
        gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
          toolbox.getCurrentPanel().selectStyleSheet(href, line, column);
        });
      }
      return;
    })
  }

  this.view.element.addEventListener("CssRuleViewCSSLinkClicked",
                                     this._cssLinkHandler);

  this._onSelect = this.onSelect.bind(this);
  this.inspector.selection.on("detached", this._onSelect);
  this.inspector.selection.on("new-node-front", this._onSelect);
  this.refresh = this.refresh.bind(this);
  this.inspector.on("layout-change", this.refresh);

  this.inspector.selection.on("pseudoclass", this.refresh);

  this.onSelect();
}

exports.RuleViewTool = RuleViewTool;

RuleViewTool.prototype = {
  onSelect: function RVT_onSelect(aEvent) {
    if (!this.view) {
      // Skip the event if RuleViewTool has been destroyed.
      return;
    }
    this.view.setPageStyle(this.inspector.pageStyle);

    if (!this.inspector.selection.isConnected() ||
        !this.inspector.selection.isElementNode()) {
      this.view.highlight(null);
      return;
    }

    if (!aEvent || aEvent == "new-node-front") {
      let done = this.inspector.updating("rule-view");
      this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
    }
  },

  refresh: function RVT_refresh() {
    this.view.nodeChanged();
  },

  destroy: function RVT_destroy() {
    this.inspector.off("layout-change", this.refresh);
    this.inspector.selection.off("pseudoclass", this.refresh);
    this.inspector.selection.off("new-node-front", this._onSelect);

    this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
      this._cssLinkHandler);

    this.view.element.removeEventListener("CssRuleViewChanged",
      this._changeHandler);

    this.view.element.removeEventListener("CssRuleViewRefreshed",
      this._refreshHandler);

    this.doc.documentElement.removeChild(this.view.element);

    this.view.destroy();

    delete this.outerIFrame;
    delete this.view;
    delete this.doc;
    delete this.inspector;
  }
};

function ComputedViewTool(aInspector, aWindow, aIFrame)
{
  this.inspector = aInspector;
  this.window = aWindow;
  this.document = aWindow.document;
  this.outerIFrame = aIFrame;
  this.view = new ComputedView.CssHtmlTree(this, aInspector.pageStyle);

  this._onSelect = this.onSelect.bind(this);
  this.inspector.selection.on("detached", this._onSelect);
  this.inspector.selection.on("new-node-front", this._onSelect);
  this.refresh = this.refresh.bind(this);
  this.inspector.on("layout-change", this.refresh);
  this.inspector.selection.on("pseudoclass", this.refresh);

  this.view.highlight(null);

  this.onSelect();
}

exports.ComputedViewTool = ComputedViewTool;

ComputedViewTool.prototype = {
  onSelect: function CVT_onSelect(aEvent)
  {
    if (!this.view) {
      // Skip the event if ComputedViewTool has been destroyed.
      return;
    }
    this.view.setPageStyle(this.inspector.pageStyle);

    if (!this.inspector.selection.isConnected() ||
        !this.inspector.selection.isElementNode()) {
      this.view.highlight(null);
      return;
    }

    if (!aEvent || aEvent == "new-node-front") {
      let done = this.inspector.updating("computed-view");
      this.view.highlight(this.inspector.selection.nodeFront).then(() => {
        done();
      });
    }
  },

  refresh: function CVT_refresh() {
    this.view.refreshPanel();
  },

  destroy: function CVT_destroy(aContext)
  {
    this.inspector.off("layout-change", this.refresh);
    this.inspector.sidebar.off("computedview-selected", this.refresh);
    this.inspector.selection.off("pseudoclass", this.refresh);
    this.inspector.selection.off("new-node-front", this._onSelect);

    this.view.destroy();
    delete this.view;

    delete this.outerIFrame;
    delete this.cssLogic;
    delete this.cssHtmlTree;
    delete this.window;
    delete this.document;
    delete this.inspector;
  }
};
