/* See license.txt for terms of usage */

"use strict";

module.metadata = {
  "stability": "experimental"
};

var self = require("sdk/self");
var main = require("../main.js");

const { Cu, Ci } = require("chrome");
const { BasePanel } = require("../chrome/base-panel.js");
const { Class } = require("sdk/core/heritage");
const { Trace, TraceError } = require("../core/trace.js").get(module.id);
const { Tool } = require("dev/toolbox");
const { Locale } = require("../core/locale.js");
const { DomTree } = require("../dom/dom-tree.js");
const { DomProvider } = require("../dom/dom-provider.js");
const { loadSheet, removeSheet } = require("sdk/stylesheet/utils");
const { defer } = require("sdk/core/promise");
const { getFrameElement } = require("sdk/window/utils");
const { Dom } = require("../core/dom.js");
const { target } = require("../target.js");
const { Theme } = require("../chrome/theme.js");
const { setTimeout } = require("sdk/timers");

const { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});

// xxxHonza: part of the workaround for 1075490 (see the bottom of the file).
// Use the same weird panel ID that is generated by the SDK.
// There are places in the code (test, unregistering) that use the ID.
const panelId = "dev-panel-firebugsoftware-joehewitt-com-DOM";

/**
 * @panel This object implements a main DOM panel. It displays structure
 * of the current document. The panel might be converted into DevTools SDK
 * example showing how to render remote objects (grips) by using repository
 * of registered templates {@Reps}.
 */
const DomPanel = Class(
/** @lends DomPanel */
{
  extends: BasePanel,

  label: Locale.$STR("dom.panel.title"),
  tooltip: Locale.$STR("dom.panel.tip"),
  icon: "./icon-16.png",
  url: "./dom/dom.html",
  searchable: true,

  // Initialization
  initialize: function(options) {
    BasePanel.prototype.initialize.apply(this, arguments);

    Trace.sysout("DomPanel.initialize;", options);
  },

  setup: function({debuggee, frame, toolbox}) {
    BasePanel.prototype.setup.apply(this, arguments);

    Trace.sysout("DomPanel.setup;", frame);
  },

  open: function(window, toolbox) {
    let parentDoc = toolbox.doc;
    let iframe = parentDoc.getElementById("toolbox-panel-iframe-" + panelId);

    let self = this;

    // Make sure the iframe content window is ready.
    let deferredIframe = defer();
    let win = iframe.contentWindow;
    if (win && win.document && win.document.readyState === "complete") {
      Trace.sysout("domPanel.complete;");
      deferredIframe.resolve(self);
    }
    else {
      iframe.addEventListener("load", function onIframeLoad() {
        iframe.removeEventListener("load", onIframeLoad, true);
        Trace.sysout("domPanel.load;");
        deferredIframe.resolve(self);
      }, true);
    }

    this.chrome.getPanelWhenReady(panelId).then(panel => {
      Trace.sysout("ready ", panel)
      panel.onReady();
    });

    return deferredIframe.promise;
  },

  onReady: function() {
    BasePanel.prototype.onReady.apply(this, arguments);

    Trace.sysout("DomPanel.onReady;", this);

    // xxxHonza: dom.html doesn't include the theme-switching.js file
    // and so, theme events are not fired for it FIX ME
    // We might want to change dom.html to dom.xul and put it into
    // chrome directory to also fix:
    // issue #15 - DomTree items are not expandable
    // In such case 'onStateChange' event that causes onReady method
    // to be executed doesn't have to be called.
    let win = this.getPanelWindow();
    loadSheet(win, "chrome://firebug/skin/domTree.css", "author");

    this.update();
  },

  initContext: function(context) {
    BasePanel.prototype.initContext.apply(this, arguments);

    this.update();
  },

  update: function() {
    Trace.sysout("domPanel.update;");

    // Get the current {@ThreadClient}. It might cause tab attach
    // (happens asynchronously) if the toolbox isn't attached yet.
    let target = this.toolbox.target;
    target.activeTab.attachThread({}, (response, threadClient) => {
      Trace.sysout("domPanel.update; threadClient", arguments);

      // Attach Console. It might involve RDP communication, so wait
      // asynchronously for the result
      this.attachConsole(threadClient).then(consoleClient => {
        Trace.sysout("domPanel.update; consoleClient", arguments);

        // Evaluate an expression to get the top level actor.
        // The goal is to render that actor in the panel content.
        consoleClient.evaluateJS("document", (response) => {
          Trace.sysout("domPanel.update; document", arguments);

          this.refresh(response.result);

          // xxxHonza: this is needed? Shouldn't it resume automatically?
          threadClient.resume();
        });

        // An example of getting node info (e.g. isDisplayed)
        /*this.toolbox.initInspector().then(() => {
          let expr = "document.getElementById('hidden')";
          consoleClient.evaluateJS(expr, (response) => {
            let objectActor = response.result;
            let walker = this.toolbox.walker;
            walker.getNodeActorFromObjectActor(objectActor.actor).
              then((nodeFront) => {
                Trace.sysout("nodeFront", nodeFront);
              });
          });
        })*/
      });
    });
  },

  attachConsole: function(threadClient) {
    let deferred = defer();
    let debuggerClient = threadClient.client;
    let consoleActor = this.toolbox.target.form.consoleActor;

    debuggerClient.attachConsole(consoleActor, ["ConsoleAPI"],
      (response, webConsoleClient) => {

      Trace.sysout("domPanel.attachConsole; ", arguments);

      if (response.error) {
        deferred.reject(response);
      } else {
        deferred.resolve(webConsoleClient);
      }
    });

    return deferred.promise;
  },

  // Selection
  select: function(object) {
    Trace.sysout("domPanel.select; object:", object);

    this.refresh(object);
  },

  refresh: function(actor) {
    Trace.sysout("domPanel.refresh; actor:", actor);

    let target = this.toolbox.target;
    if (!target) {
      return;
    }

    let context = this.getContext();
    context.getCache().then(cache => {
      let provider = new DomProvider(cache);
      this.tree = new DomTree(provider);
      this.tree.replace(this.panelNode, {object: actor});

      this.emit("refreshed", this.tree);
    });
  },

  // Theme

  onApplyTheme: function(iframeWin, oldTheme) {
    Trace.sysout("domPanel.onApplyTheme;");
  },

  onUnapplyTheme: function(iframeWin, newTheme) {
    Trace.sysout("domPanel.onUnapplyTheme;");
  },

  // Toolbar

  getPanelToolbarButtons: function() {
    let buttons = [];

    buttons.push({
      label: "dom.refresh.label",
      tooltiptext: "dom.refresh.tip",
      command: this.onRefresh.bind(this)
    });

    return buttons;
  },

  // Commands

  onRefresh: function() {
    this.update();
  },

  // Search

  onSearch: function(text, reverse) {
    BasePanel.prototype.onSearch.apply(this, arguments);

    Trace.sysout("domPanel.onSearch; " + text);
  },

  findSearchResultContainer: function(node) {
    return Dom.getAncestorByClass(node, "memberRow");
  },
});

/**
 * The DOM panel is only available if the Firebug theme
 * is active.
 */
target.on("onRegisterPanels", registrar => {
  registrar.registerPanel(DomPanel);
});

target.on("onUnregisterPanels", registrar => {
  registrar.unregisterPanel(DomPanel);
});

// Exports from this module
exports.DomPanel = DomPanel;
exports.domPanelId = panelId;
