/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2001
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Joe Hewitt <hewitt@netscape.com> (original author)
 *
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/***************************************************************
* InspectorApp -------------------------------------------------
*  The primary object that controls the Inspector application.
****************************************************************/

//////////// global variables /////////////////////

var inspector;

//////////// global constants ////////////////////

const kSearchRegURL        = "resource:///res/inspector/search-registry.rdf";

const kWindowDataSourceCID = "@mozilla.org/rdf/datasource;1?name=window-mediator";
const kClipboardHelperCID  = "@mozilla.org/widget/clipboardhelper;1";
const kPromptServiceCID    = "@mozilla.org/embedcomp/prompt-service;1";
const nsIWebNavigation     = Components.interfaces.nsIWebNavigation;

//////////////////////////////////////////////////

window.addEventListener("load", InspectorApp_initialize, false);
window.addEventListener("unload", InspectorApp_destroy, false);

function InspectorApp_initialize()
{
  inspector = new InspectorApp();

  // window.arguments may be either a string or a node.
  // If passed via a command line handler, it will be a uri string.
  // If passed via navigator hooks, it will be a dom node to inspect.
  var initNode, initURI;
  if (window.arguments.length) {
    if (typeof window.arguments[0] == "string") {
      initURI = window.arguments[0];
    }
    else if (window.arguments[0] instanceof Components.interfaces.nsIDOMNode) {
      initNode = window.arguments[0];
    }
  }
  inspector.initialize(initNode, initURI);
}

function InspectorApp_destroy()
{
  inspector.destroy();
}

////////////////////////////////////////////////////////////////////////////
//// class InspectorApp

function InspectorApp()
{
}

InspectorApp.prototype = 
{
  ////////////////////////////////////////////////////////////////////////////
  //// Initialization

  mSearchService: null,
  mShowBrowser: false,
  mClipboardHelper: null,
  mPromptService: null,
  
  get document() { return this.mDocPanel.viewer.subject },
  get searchRegistry() { return this.mSearchService },
  get panelset() { return this.mPanelSet; },
  
  initialize: function(aTarget, aURI)
  {
    this.mInitTarget = aTarget;
    
    //this.initSearch();

    var el = document.getElementById("bxBrowser");
    el.addEventListener("load", BrowserLoadListener, true);

    this.setBrowser(false, true);
    //this.setSearch(false, true);

    this.mClipboardHelper = XPCU.getService(kClipboardHelperCID, "nsIClipboardHelper");
    this.mPromptService = XPCU.getService(kPromptServiceCID, "nsIPromptService");

    this.mPanelSet = document.getElementById("bxPanelSet");
    this.mPanelSet.addObserver("panelsetready", this, false);
    this.mPanelSet.initialize();

    if (aURI) {
      this.gotoURL(aURI);
    }
  },

  destroy: function()
  {
    InsUtil.persistAll("bxDocPanel");
    InsUtil.persistAll("bxObjectPanel");
  },
  
  ////////////////////////////////////////////////////////////////////////////
  //// Viewer Panels
  
  initViewerPanels: function()
  {
    this.mDocPanel = this.mPanelSet.getPanel(0);
    this.mDocPanel.addObserver("subjectChange", this, false);
    this.mObjectPanel = this.mPanelSet.getPanel(1);

    if (this.mInitTarget) {
      if (this.mInitTarget.nodeType == Node.DOCUMENT_NODE)
        this.setTargetDocument(this.mInitTarget);
      else if (this.mInitTarget.nodeType == Node.ELEMENT_NODE) {
        this.setTargetDocument(this.mInitTarget.ownerDocument);
        this.mDocPanel.params = this.mInitTarget;
      }
      this.mInitTarget = null;
    }
  },

  onEvent: function(aEvent)
  {
    switch (aEvent.type) {
      case "panelsetready":
        this.initViewerPanels();
        break;
      case "subjectChange":
        if (aEvent.target == this.mDocPanel.viewer &&
            aEvent.subject && "location" in aEvent.subject) {
          this.locationText = aEvent.subject.location; // display document url
        }
    }
  },
  
  ////////////////////////////////////////////////////////////////////////////
  //// UI Commands

  doViewerCommand: function(aCommand)
  {
    this.mPanelSet.execCommand(aCommand);
  },
  
  showOpenURLDialog: function()
  {
    var bundle = this.mPanelSet.stringBundle;
    var msg = bundle.getString("inspectURL.message");
    var title = bundle.getString("inspectURL.title");
    var url = { value: "http://" };
    var dummy = { value: false };
    var go = this.mPromptService.prompt(window, title, msg, url, null, dummy);
    if (go) {
      this.gotoURL(url.value);
    }
  },

  showPrefsDialog: function()
  {
    goPreferences("advancedItem", "chrome://inspector/content/prefs/pref-inspector.xul", "inspector");
  },
  
  toggleBrowser: function(aToggleSplitter)
  {
    this.setBrowser(!this.mShowBrowser, aToggleSplitter)
  },

  setBrowser: function(aValue, aToggleSplitter)
  {
    this.mShowBrowser = aValue;
    if (aToggleSplitter)
      this.openSplitter("Browser", aValue);
    var cmd = document.getElementById("cmdToggleBrowser");
    cmd.setAttribute("checked", aValue);
  },

  toggleSearch: function(aToggleSplitter)
  {
    this.setSearch(!this.mShowSearch, aToggleSplitter);
  },

  setSearch: function(aValue, aToggleSplitter)
  {
    this.mShowSearch = aValue;
    if (aToggleSplitter)
      this.openSplitter("Search", aValue);
    var cmd = document.getElementById("cmdToggleSearch");
    cmd.setAttribute("checked", aValue);
  },

  openSplitter: function(aName, aTruth)
  {
    var splitter = document.getElementById("spl" + aName);
    if (aTruth)
      splitter.open();
    else
      splitter.close();
  },

/*
  XXXcaa
  The following code needs to evaluated.  What does it do?  Why does nobody
  call it?  If deemed necessary, turn it back on with the right callers,
  and localize it.
 */
/*
  runSearch: function()
  {
    var path = null; // TODO: should persist last path chosen in a pref
    var file = FilePickerUtils.pickFile("Find Search File", path, ["filterXML"], "Open");
    if (file) {
      var ioService = XPCU.getService("@mozilla.org/network/io-service;1","nsIIOService");
      var fileHandler = XPCU.QI(ioService.getProtocolHandler("file"), "nsIFileProtocolHandler");

      var url = fileHandler.getURLSpecFromFile(file);

      this.startSearchModule(url);
    }
  },

  viewSearchItem: function()
  {
    if (this.mCurrentSearch.canViewItems)
      window.openDialog("chrome://inspector/content/utilWindow.xul", "viewItem", "chrome,resizable");
  },

  doViewSearchItem: function(aWindow)
  {
    var idx = this.getSelectedSearchIndex();
    var el = this.mCurrentSearch.viewItemAt(idx);

    aWindow.title = this.mCurrentSearch.getItemDescription(idx);
    aWindow.document.getElementById("bxCenter").appendChild(el);
  },

  editSearchItem: function()
  {
  },
  
  onSearchTreeClick: function(aEvent)
  {
    if (aEvent.detail == 2) { // double click
      this.viewSearchItem();
    }
  },

  copySearchItemLine: function()
  {
    var mod = this.mSearchService.currentModule;
    var idx = this.mSearchService.getSelectedIndex(0);
    var text = mod.getItemText(idx);
    this.mClipboardHelper.copyString(text);
  },

  // XXX what is this?  It doesn't seem to get called from anywhere?
  copySearchItemAll: function()
  {
    var text = this.getAllSearchItemText();
    this.mClipboardHelper.copyString(text);
  },

  saveSearchItemText: function()
  {
    var target = FilePickerUtils.pickFile("Save Results As", null, ["filterAll", "filterText"], "Save");

    var text = this.getAllSearchItemText();

    var file = new File(target.path);
    file.open('w');
    file.write(text);
    file.close();
  },

  getAllSearchItemText: function()
  {
    var mod = this.mSearchService.currentModule;
    var len = mod.resultCount;
    var text = "";
    for (var i = 0; i < len; i++) {
      text += mod.getItemText(i) + "\r";
    }

    return text;
  },
*/

  exit: function()
  {
    window.close();
    // Todo: remove observer service here
  },

  ////////////////////////////////////////////////////////////////////////////
  //// Navigation
  
  gotoTypedURL: function()
  {
    var url = document.getElementById("tfURLBar").value;
    this.gotoURL(url);
  },

  gotoURL: function(aURL, aNoSaveHistory)
  {
    this.mPendingURL = aURL;
    this.mPendingNoSave = aNoSaveHistory;
    this.browseToURL(aURL);
    this.setBrowser(true, true);
  },

  browseToURL: function(aURL)
  {
    try {
      this.webNavigation.loadURI(aURL, nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
    }
    catch(ex) {
      // nsIWebNavigation.loadURI will spit out an appropriate user prompt, so
      // we don't need to do anything here.  See nsDocShell::DisplayLoadError()
    }
  },

  goToWindow: function(aMenuitem)
  {
    this.setTargetWindowById(aMenuitem.id);
  },

  setTargetWindowById: function(aResId)
  {
    var windowManager = XPCU.getService(kWindowDataSourceCID, "nsIWindowDataSource");
    var win = windowManager.getWindowForResource(aResId);

    if (win) {
      this.setTargetWindow(win);
      this.setBrowser(false, true);
    } else {
      var bundle = this.mPanelSet.stringBundle;
      var msg = bundle.getString("inspectWindow.error.message");
      var title = bundle.getString("inspectWindow.error.title");
      this.mPromptService.alert(window, title, msg);
    }
  },

  setTargetWindow: function(aWindow)
  {
    this.setTargetDocument(aWindow.document);
  },

  setTargetDocument: function(aDoc)
  {
    this.mDocPanel.subject = aDoc;
  },

  get webNavigation()
  {
    var browser = document.getElementById("ifBrowser");
    return browser.webNavigation;
  },

  ////////////////////////////////////////////////////////////////////////////
  //// UI Labels getters and setters

  get locationText() { return document.getElementById("tfURLBar").value; },
  set locationText(aText) { document.getElementById("tfURLBar").value = aText; },

  get statusText() { return document.getElementById("txStatus").value; },
  set statusText(aText) { document.getElementById("txStatus").value = aText; },

  get progress() { return document.getElementById("pmStatus").value; },
  set progress(aPct) { document.getElementById("pmStatus").value = aPct; },

/*
  XXXcaa -- What is this?


  get searchTitle(aTitle) { return document.getElementById("splSearch").label; },
  set searchTitle(aTitle)
  {
    var splitter = document.getElementById("splSearch");
    splitter.setAttribute("label", "Search" + (aTitle ? " - " + aTitle : ""));
  },
*/
  ////////////////////////////////////////////////////////////////////////////
  //// Document Loading 

  documentLoaded: function()
  {
    this.setTargetWindow(_content);

    var url = this.webNavigation.currentURI.spec;
    
    // put the url into the urlbar
    this.locationText = url;

    // add url to the history, unless explicity told not to
    if (!this.mPendingNoSave)
      this.addToHistory(url);

    this.mPendingURL = null;
    this.mPendingNoSave = null;
  },

  ////////////////////////////////////////////////////////////////////////////
  //// Search 
/*
  initSearch: function()
  {
    var ss = new inSearchService();
    this.mSearchService = ss;
    
    ss.addSearchObserver(this);
    ss.resultsTree = document.getElementById("trSearch");
    ss.contextMenu = document.getElementById("ppSearchResults");
    ss.contextMenuInsertPt = document.getElementById("ppSearchResults-insertion");
    ss.contextMenuInsert = inSearchService.INSERT_BEFORE;
  },

  startSearchModule: function(aModuleURL)
  {
    this.mSearchService.startModule(aModuleURL);
  },
  
  clearSearchResults: function()
  {
    this.searchTitle = null;
    this.statusText = "";
    this.progress = 0;
    
    this.mSearchService.clearSearch();
  },
  
  ////////////////////////////////////////////////////////////////////////////
  //// interface inISearchObserver

  onSearchStart: function(aModule) 
  {
    this.searchTitle = aModule.title;
    this.toggleSearch(true, true);
  },

  onSearchResult: function(aModule)
  {
    if (aModule.isPastMilestone) {
      this.statusText = "Searching - " + aModule.resultCount + " results found."; // XXX localize
      this.progress = aModule.progressPercent;
    }
  },

  onSearchEnd: function(aModule, aResult)
  {
    var diff = Math.round(aModule.elapsed / 100) / 10;
    this.progress = 100;
    this.statusText = "Search complete - " + aModule.resultCount + " results found ("+diff+"s)"; // XXX localize
  },

  onSearchError: function(aModule, aMessage)
  {
    alert("Unable to complete this search due to the following error:\n" + aMessage);
  },

*/

  ////////////////////////////////////////////////////////////////////////////
  //// History 

  addToHistory: function(aURL)
  {
  },

  ////////////////////////////////////////////////////////////////////////////
  //// Uncategorized
  
  get isViewingContent() { return this.mPanelSet.getPanel(0).subject != null; },
  
  fillInTooltip: function(tipElement)
  {
    var retVal = false;
    var textNode = document.getElementById("txTooltip");
    if (textNode) {
      try {  
        var tipText = tipElement.getAttribute("tooltiptext");
        if (tipText != "") {
          textNode.setAttribute("value", tipText);
          retVal = true;
        }
      }
      catch (e) { }
    }
    
    return retVal;
  },

  initPopup: function(aPopup)
  {
    var items = aPopup.getElementsByTagName("menuitem");
    var js, fn, item;
    for (var i = 0; i < items.length; i++) {
      item = items[i];
      fn = "isDisabled" in item ? item.isDisabled : null;
      if (!fn) {
        js = item.getAttribute("isDisabled");
        if (js) {
          fn = new Function(js);
          item.isDisabled = fn;
        } else {
          item.isDisabled = null; // to prevent annoying "strict" warning messages
        }
      } 
      if (fn) {
        if (item.isDisabled())
          item.setAttribute("disabled", "true");
        else
          item.removeAttribute("disabled");
      }

      fn = null;
    }
  },

  emptyChildren: function(aNode)
  {
    while (aNode.childNodes.length > 0) {
      aNode.removeChild(aNode.lastChild);
    }
  },
  
  onSplitterOpen: function(aSplitter)
  {
    if (aSplitter.id == "splBrowser") {
      this.setBrowser(aSplitter.isOpened, false);
    } else if (aSplitter.id == "splSearch") {
      this.setSearch(aSplitter.isOpened, false);
    }
  },
  
  // needed by overlayed commands from viewer to get references to a specific
  // viewer object by name
  getViewer: function(aUID)
  {
    return this.mPanelSet.registry.getViewerByUID(aUID);
  }
  
};

////////////////////////////////////////////////////////////////////////////
//// event listeners

function BrowserLoadListener(aEvent) 
{
  // since we will also get load events for frame documents,
  // make sure we respond to the top-level document load
  if (aEvent.target.defaultView == _content)
    inspector.documentLoaded();
}

function UtilWindowOpenListener(aWindow)
{
  inspector.doViewSearchItem(aWindow);
}
