/***************************************************************
* inSearchService -----------------------------------------------
*  The centry registry where information about all installed
*  search modules is kept.  
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Until Bug 54237 is fixed, there be some ugly hacks in this 
* file.  We'll need to load search modules via a XUL document
* within an iframe, instead of using the xml loader for now.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
* REQUIRED IMPORTS:
*   chrome://inspector/content/jsutil/xpcom/XPCU.js
*   chrome://inspector/content/search/inSearchTreeBuilder.js
*   chrome://inspector/content/search/inSearchModule.js
****************************************************************/

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

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

var kSearchURLPrefix = "chrome://inspector/content/search/";

////////////////////////////////////////////////////////////////////////////
//// class inSearchService

function inSearchService() 
{
  this.mInstances = {};
  this.mObservers = [];
  
  // the browser and webnav are a hack.  We should be able to use 
  // the xmlextras facility for loading xml, but it's broken, so 
  // we use a browser for onw
  var browser = document.getElementById("inSearchServiceLoader");
  browser.addEventListener("load", inSearchService_LoadListener, true);
  this.mWebNav = browser.webNavigation;
}

// constants
inSearchService.INSERT_BEFORE = 1;
inSearchService.INSERT_AFTER = 2;

inSearchService.prototype = 
{
  mInstances: null,
  mObservers: null,
  mCurrentModule: null,
  
  mTree: null,
  mContextMenu: null,
  mCMInsertPt: null,
  mCMInsert: inSearchService.INSERT_BEFORE,
  
  ////////////////////////////////////////////////////////////////////////////
  //// Properties
  
  get currentModule() { return this.mCurrentModule },

  get resultsTree() { return this.mTree },
  set resultsTree(aTree) {
    // XX this condition could be fixed with a little bit of effort
    if (this.mTree) throw "inSearchService.tree should only be set once"
    
    this.mTree = aTree;
    aTree._searchService = this;

    this.mTreeBuilder = new inSearchTreeBuilder(aTree, kInspectorNSURI, "results");
    this.mTreeBuilder.isIconic = true;

    // XX HACKERY AT IT'S FINEST - the click event won't fire when I add it to the tree -- go figure
    // in the mean time I'll add it to the parentNode, which seems to work. FIX ME!
    var parent = aTree.parentNode;
    parent._tempTreeYuckyHack = aTree;
    parent.addEventListener("click", inSearchService_TreeClickListener, false);
  },

  get contextMenu() { return this.mContextMenu },
  set contextMenu(aVal) 
  { 
    this.mContextMenu = aVal;
    aVal._searchService = this;
  },
  
  get contextMenuInsertPt() { return this.mCMInsertPt },
  set contextMenuInsertPt(aVal) { this.mCMInsertPt = aVal },
  
  get contextMenuInsert() { return this.mCMInsert },
  set contextMenuInsert(aVal) { this.mCMInsert = aVal },
  
  ////////////////////////////////////////////////////////////////////////////
  //// Running Modules

  startModule: function(aURL)
  {
    var instance = this.mInstances[aURL];
    if (instance)
      this.doStartModule(instance);
    else
      this.loadModule(aURL);
  },
  
  doStartModule: function(aModule)
  {
    aModule.startSearch();
  },
 
  startSearch: function(aModule)
  { 
    this.mCurrentModule = aModule;
    
    // build up the context menu
    this.installContextMenu();
    
    // build up the search results tree
    this.mTreeBuilder.module = aModule;
  },    

  clearSearch: function()
  {
    var mod = this.mCurrentModule;
    if (mod) {
      // clear datasource from search tree
      this.mTreeBuilder.module = null;
      this.mTreeBuilder.buildContent();
      
      // clear context menu
      this.uninstallContextMenu();
    } 

    this.mCurrentModule = null;
  },

  ////////////////////////////////////////////////////////////////////////////
  //// Loading Modules

  loadModule: function(aURL)
  {
    this.mWebNav.loadURI(aURL, nsIWebNavigation.LOAD_FLAGS_NONE);
    this.mLoadingURL = aURL;
    /* 
    // This method of loading the xml doesn't work, but it should.  See bug 54237... 
    var doc = document.implementation.createDocument("", "", null);
    doc.addEventListener("load", SearchFileLoadListener, false);
    doc.load(aURL, "text/xml");
    */ 
  },
  
  searchFileLoaded: function()
  {
    var mod = this.createModule(this.mWebNav.document);
    mod.addSearchObserver(this);
    this.mInstances[this.mLoadingURL] = mod;
    this.doStartModule(mod);
  },
  
  createModule: function(aDocument)
  {
    var mod = new inSearchModule(aDocument.location);
    mod.searchService = this;
    mod.initFromElement(aDocument.documentElement);
    
    return mod;    
  },
  
  ////////////////////////////////////////////////////////////////////////////
  //// interface inISearchObserver

  onSearchStart: function(aModule) 
  {
    this.startSearch(aModule);
    this.notifySearchStart();
  },

  onSearchResult: function(aModule)
  {
    this.notifySearchResult();
  },

  onSearchEnd: function(aModule, aResult)
  {
    this.notifySearchEnd(aResult);
  },

  onSearchError: function(aModule, aMessage)
  {
    this.notifySearchError(aMessage);
    this.clearSearch();
  },

  ////////////////////////////////////////////////////////////////////////////
  //// Results Tree

  get selectedItemCount()
  {
    return this.mTree ? this.mTree.selectedItems.length : null;
  },
  
  getSelectedIndex: function(aIdx)
  {
    if (this.mTree) {
      var items = this.mTree.selectedItems;
      return this.mTree.getIndexOfItem(items[aIdx]);
    }
    return null;
  },
  
  onTreeDblClick: function()
  {
    this.mCurrentModule.callDefaultCommand();
  },

  //////////////////////////////////////////////////////////////////////////
  //// ContextMenu

  installContextMenu: function()
  {
    var mod = this.mCurrentModule;
    if (mod) {
      var menu = this.mContextMenu;
      menu.addEventListener("create", inSearchService_onCreatePopup, true);
      mod.installContextMenu(menu, this.mCMInsertPt, this.mCMInsert);
    }
  },
  
  uninstallContextMenu: function()
  {
    var mod = this.mCurrentModule;
    if (mod) {
      // remove the createion listener
      var menu = this.mContextMenu;
      menu.removeEventListener("create", inSearchService_onCreatePopup, true);
      mod.uninstallContextMenu(menu, this.mCMInsertPt, this.mCMInsert);
    }
  },

  onCreatePopup: function(aMenu)
  {
    
  },
  
  //////////////////////////////////////////////////////////////////////////
  //// Event Notification
  
  // NOTE TO SELF - this code could be cut down to nothing if you write a module
  // called "ObserverManager" to do the work for you
  
  addSearchObserver: function(aObserver)
  {
    this.mObservers.push(aObserver);
  },
  
  removeSearchObserver: function(aObserver)
  {
    var o;
    var obs = this.mObservers;
    for (var i = 0; i < obs.length; i++) {
      o = obs[i];
      if (o == aObserver) {
        obs.splice(i, 1);
        return;
      }
    }
  },

  notifySearchStart: function()
  {
    var o = this.mObservers;
    for (var i = 0; i < o.length; i++)
      o[i].onSearchStart(this.mCurrentModule);
  },
  
  notifySearchResult: function()
  {
    var o = this.mObservers;
    for (var i = 0; i < o.length; i++)
      o[i].onSearchResult(this.mCurrentModule);
  },

  notifySearchEnd: function(aResult)
  {
    var o = this.mObservers;
    for (var i = 0; i < o.length; i++)
      o[i].onSearchEnd(this.mCurrentModule, aResult);
  },

  notifySearchError: function(aMsg)
  {
    var o = this.mObservers;
    for (var i = 0; i < o.length; i++)
      o[i].onSearchError(this.mCurrentModule, aMsg);
  }
  
};

////////////////////////////////////////////////////////////////////////////
//// Event Listeners

function inSearchService_LoadListener(aEvent)
{
  inspector.searchRegistry.searchFileLoaded();
}

function inSearchService_TreeClickListener(aEvent)
{
  if (aEvent.detail == 2) {
    var tree = this._tempTreeYuckyHack;
    tree._searchService.onTreeDblClick();
  }
}

function inSearchService_onCreatePopup(aEvent)
{
  // event.target is returning null for this event - I should file a bug
  // in the mean time I'll just back off this feature for now...
  
/*  var menu = aEvent.target;
  var svc = menu._searchService;
  svc.onCreatePopup(menu);
*/
}

// This code is from when there was an RDF "search registry"... I might want to bring that back,
// so I'll keep this code sitting here for a little while...

/*
function inSearchServiceLoadObserver(aTarget) 
{
  this.mTarget = aTarget;
}

inSearchServiceLoadObserver.prototype = {
  mTarget: null,

  onError: function(aErrorMsg) 
  {
    this.mTarget.onLoadError(aErrorMsg);
  },

  onDataSourceReady: function(aDS) 
  {
    this.mTarget.onLoad(aDS);
  }
};

  load: function(aURL, aObserver)
  {
    this.mURL = aURL;
    this.mObserver = aObserver;
    RDFU.loadDataSource(aURL, new inSearchServiceLoadObserver(this));
  },

  onLoad: function(aDS)
  {
    this.mDS = aDS;
    this.prepareRegistry();
    this.mObserver.oninSearchServiceLoad();
  },

  onLoadError: function(aErrorMsg)
  {
    this.mObserver.oninSearchServiceLoadError(aErrorMsg);
  },

  prepareRegistry: function()
  {
    this.mModuleSeq = RDFU.findSeq(this.mDS, "inspector:search");

    var el, uid, fnName, factory;
    var els = this.mModuleSeq.GetElements();
    while (els.hasMoreElements()) {
      el = els.getNext();
      uid = RDFU.readAttribute(this.mDS, el, kInspectorNSURI+"uid");
      fnName = RDFU.readAttribute(this.mDS, el, kInspectorNSURI+"factory");
      factory = eval(fnName);
      if (factory)
        this.mFactories[uid] = factory;
    }

  },
  */

  