//
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2008
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// 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.
//
// END FLOCK GPL
//

const CLASS_ID                = Components.ID("{C711A35C-8949-4D01-9676-47E71F1A78F4}");
const CLASS_NAME              = "Flock Results Search Module";
const CONTRACT_ID             = "@flock.com/flock-search-flock;1";
const flockISearchService     = Components.interfaces.flockISearchService;

const FLOCK_NS = "http://flock.com/rdf#";
const NC_NS = "http://home.netscape.com/NC-rdf#";

const RDFCU = Components.classes["@mozilla.org/rdf/container-utils;1"].getService(Components.interfaces.nsIRDFContainerUtils);
const RDFS = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);

function flockSearchAPIFlock()
{
  this._favService = Components.classes["@mozilla.org/rdf/datasource;1?name=flock-favorites"].getService(Components.interfaces.nsIRDFDataSource);
  this._logger = Components.classes["@flock.com/logger;1"].createInstance(Components.interfaces.flockILogger);
  this._logger.init("search");
  var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
  var bundle = sbs.createBundle("chrome://flock/locale/search/search.properties");
  this.serviceName = bundle.GetStringFromName("flock.search.api.flock");
};

flockSearchAPIFlock.prototype = {

  _searchListener: null,
  _maxResults: null,
  _searchesOutstanding: null,
  _resultsBookmarks: null,
  _resultsHistory: null,
  _currentQuery: null,
  RDFS: null,
  shortName: "flock",
  icon: "rdf:http://flock.com/rdf#icon",
  ref: "urn:flock:search:results",
  mDS: null,
  fullResultsUrl: "flock://search/?query=",

  search: function(aQuery, aNumResults, aListener, aDatasource)
  {
    this._searchListener = aListener;
    this._maxResults = aNumResults;
    
    this._currentQuery = aQuery;
    
    // get references to the services we use
    var searchService = Components.classes["@flock.com/lucene/flockLucene;1"].getService(Components.interfaces.flockILucene);
    this.RDFS = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
    var prefService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
    
    // save a ref to the ds passed in
    this.mDS = aDatasource;
    
    var normalizedQuery = this.normalizeQuery(aQuery);
    
    this._logger.debug("query: \"" + aQuery + "\" => \"" + normalizedQuery + "\"");
    
    var showHistory = prefService.getBoolPref("flock.search.flyout.showHistory") && prefService.getBoolPref("flock.service.indexer.indexWebHistory");
    
    // clear results
    this._resultsBookmarks = [];
    this._resultsHistory = [];
    
    // start the searchs
    if (showHistory) {
      this._searchesOutstanding = 2;
      searchService.search(normalizedQuery, "bookmark", this._maxResults, this);
      searchService.search(normalizedQuery, "history", this._maxResults, this);
    } else {
      this._searchesOutstanding = 1;
      searchService.search(normalizedQuery, "bookmark", this._maxResults, this);
    }
  },
  
  normalizeQuery: function(input)
  {
    var output;
    var in_quotes = false;

    // strip leading spaces
    while (input.length && input[0]==" ") {
      // chop off the first character
      input = input.substr(1);
    }

    // Convert to lowercase for the wildcard search
    input = input.toLowerCase();
  
    // add leading plus
    output = "+";
    while (input.length) {
      if (input[0] == "\"") {
        if (in_quotes) {
          in_quotes = false;
        } else {
          in_quotes = true;
          // FIXME: remove spaces from the start of quoted strings
        }
      }

      // escape special chars
      if ("&|!(){}[]^~*?:\\".indexOf(input[0]) >= 0) {
        output = output + "\\";
      }

      output = output + input[0];

      if (!in_quotes && input[0] == " " && input.length > 1) {
          // put a plus before every word
          output = output + "+";
      }

      input = input.substr(1);
    }

    // remove trailing spaces
    var removed_spaces = false;
    while (output.length > 0 && output[output.length - 1] == " ") {
      output = output.substr(0, output.length - 1);
      removed_spaces = true;
    }

    // if we're in a quoted string we should close that
    if (in_quotes) {
      output = output + "\"";
    }
  
    // if theres an incomplete word
    if ((!removed_spaces) && (output[output.length - 1] != "\"")) {
      // search for partial matches
      output = output + "*";
    }

    return output;
  },
  
  // flockILuceneListener
  onSearchComplete: function(aNumResults, aResults) {
    this._logger.debug("onSearchComplete: hits: " + aNumResults);

    if (aNumResults > 0) {
      while (aResults.hasMoreElements()) {
        var result = aResults.getNext();
        result.QueryInterface(Components.interfaces.flockILuceneResult);
        this._logger.debug("foundResults: " + result.type + " " + result.URI);

        switch (result.type) {
          case "bookmark":
            this._resultsBookmarks.push([result.URL, result.title]);
            break;
          case "history":
            this._resultsHistory.push([result.URL, result.title]);
            break;
        }
      }
    }
    
    this._searchesOutstanding--;
    
    if (this._searchesOutstanding == 0) {
      this._returnResults();
    }
  },
  
  _returnResults: function()
  {
    this._logger.debug("_returnResults");
    
    var resultSet = this.mDS;
    var resultSetRoot = this.RDFS.GetResource("urn:flock:search:results");
    var rootContainer = RDFCU.MakeSeq(resultSet, resultSetRoot);
    
    var c;
    var urlHash = {};
    var url;
    var title;
    var favicon;
    var result;
    var resultCount = 0;
    
    // return bookmark results
    for (c = 0; c < this._resultsBookmarks.length && resultCount < this._maxResults; c++) {
      url = this._resultsBookmarks[c][0];
      title = this._resultsBookmarks[c][1];
      favicon = "chrome://browser/skin/flock/search/documentIcon16.png";
      infoicon = "chrome://flock/skin/common/star16.png";
      
      // skip duplicate bookmarks (online bookmarks)
      if (urlHash[url] == true) {
        continue;
      }

      urlHash[url] = true;
      
      result = this.RDFS.GetAnonymousResource();

      rootContainer.AppendElement(result);
    
      resultSet.Assert(result, this.RDFS.GetResource("http://home.netscape.com/NC-rdf#URL"), this.RDFS.GetLiteral(url), true);
      resultSet.Assert(result, this.RDFS.GetResource("http://home.netscape.com/NC-rdf#Name"), this.RDFS.GetLiteral(title), true);
      resultSet.Assert(result, this.RDFS.GetResource("http://home.netscape.com/NC-rdf#favicon"), this.RDFS.GetLiteral(favicon), true);
      resultSet.Assert(result, this.RDFS.GetResource(FLOCK_NS + "infoicon"), this.RDFS.GetLiteral(infoicon), true);
      
      resultCount++;
    }
    
    // fill in history results
    for (c = 0; c < this._resultsHistory.length && resultCount < this._maxResults; c++) {
      url = this._resultsHistory[c][0];
      title = this._resultsHistory[c][1];
      favicon = "chrome://browser/skin/flock/search/documentIcon16.png";
      
      // skip history results that have already been returned as bookmarks
      if (urlHash[url] == true) {
        continue;
      }
      
      result = this.RDFS.GetAnonymousResource();

      rootContainer.AppendElement(result);
    
      resultSet.Assert(result, this.RDFS.GetResource("http://home.netscape.com/NC-rdf#URL"), this.RDFS.GetLiteral(url), true);
      resultSet.Assert(result, this.RDFS.GetResource("http://home.netscape.com/NC-rdf#Name"), this.RDFS.GetLiteral(title), true);
      resultSet.Assert(result, this.RDFS.GetResource("http://home.netscape.com/NC-rdf#favicon"), this.RDFS.GetLiteral(favicon), true);
      
      resultCount++;
    }
  
    this._searchListener.foundResults(resultCount, resultSet, this.shortName, this._currentQuery);
    // JMC - Trying to fix leak of this listener
    this._searchListener = null;
  },  
  
  // nsIClassInfo
  getInterfaces: function(aCount)
  {
    var interfaces = [Components.interfaces.flockISearchService, Components.interfaces.nsIClassInfo, Components.interfaces.flockILuceneListener];
    aCount.value = interfaces.length;
    return interfaces;
  },

  // nsIClassInfo
  getHelperForLanguage: function(aLanguage)
  {
    return null;
  },

  // nsIClassInfo
  contractID: CONTRACT_ID,

  // nsIClassInfo
  classDescription: CLASS_NAME,

  // nsIClassInfo
  classID: CLASS_ID,

  // nsIClassInfo
  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,

  // nsIClassInfo
  flags: Components.interfaces.nsIClassInfo.SINGLETON,
  
  // nsISupports
  QueryInterface: function(aIID)
  {
    if (!aIID.equals(Components.interfaces.nsISupports) && !aIID.equals(Components.interfaces.flockISearchService) && !aIID.equals(Components.interfaces.nsIClassInfo) && !aIID.equals(Components.interfaces.flockILuceneListener))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }

};

/******************************************************************************
 * XPCOM Functions for construction and registration
 ******************************************************************************/
var Module = {
  _firstTime: true,
  registerSelf: function(aCompMgr, aFileSpec, aLocation, aType)
  {
    if (this._firstTime) {
      this._firstTime = false;
      throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
    }
    aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
    aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
    
    // register search provider
    var catmgr = Components.classes["@mozilla.org/categorymanager;1"]
      .getService(Components.interfaces.nsICategoryManager);
    catmgr.addCategoryEntry("flockISearchService", "flock", CONTRACT_ID, true, true);
  },

  unregisterSelf: function(aCompMgr, aLocation, aType)
  {
    aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
    aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);        
  },
  
  getClassObject: function(aCompMgr, aCID, aIID)
  {
    if (!aIID.equals(Components.interfaces.nsIFactory))
      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
    if (aCID.equals(CLASS_ID))
      return Factory;
    throw Components.results.NS_ERROR_NO_INTERFACE;
  },

  canUnload: function(aCompMgr) { return true; }
};

var Factory = {
  createInstance: function(aOuter, aIID)
  {
    if (aOuter != null)
      throw Components.results.NS_ERROR_NO_AGGREGATION;
    return (new flockSearchAPIFlock()).QueryInterface(aIID);
  }
};

function NSGetModule(aCompMgr, aFileSpec) { return Module; }

