text: /*\
  title: $:/core/modules/indexers/field-indexer.js
  type: application/javascript
  module-type: indexer
  
  Indexes the tiddlers with each field value
  
  \*/
  (function(){
  
  /*jslint node: true, browser: true */
  /*global modules: false */
  "use strict";
  
  var DEFAULT_MAXIMUM_INDEXED_VALUE_LENGTH = 128;
  
  function FieldIndexer(wiki) {
  	this.wiki = wiki;
  }
  
  FieldIndexer.prototype.init = function() {
  	this.index = null;
  	this.maxIndexedValueLength = DEFAULT_MAXIMUM_INDEXED_VALUE_LENGTH;
  	this.addIndexMethods();
  }
  
  // Provided for testing
  FieldIndexer.prototype.setMaxIndexedValueLength = function(length) {
  	this.index = null;
  	this.maxIndexedValueLength = length;
  };
  
  FieldIndexer.prototype.addIndexMethods = function() {
  	var self = this;
  	this.wiki.each.byField = function(name,value) {
  		var titles = self.wiki.allTitles(),
  			lookup = self.lookup(name,value);
  		return lookup && lookup.filter(function(title) {
  			return titles.indexOf(title) !== -1;
  		});
  	};
  	this.wiki.eachShadow.byField = function(name,value) {
  		var titles = self.wiki.allShadowTitles(),
  			lookup = self.lookup(name,value);
  		return lookup && lookup.filter(function(title) {
  			return titles.indexOf(title) !== -1;
  		});
  	};
  	this.wiki.eachTiddlerPlusShadows.byField = function(name,value) {
  		var lookup = self.lookup(name,value);
  		return lookup ? lookup.slice(0) : null;
  	};
  	this.wiki.eachShadowPlusTiddlers.byField = function(name,value) {
  		var lookup = self.lookup(name,value);
  		return lookup ? lookup.slice(0) : null;
  	};
  };
  
  /*
  Tear down and then rebuild the index as if all tiddlers have changed
  */
  FieldIndexer.prototype.rebuild = function() {
  	// Invalidate the index so that it will be rebuilt when it is next used
  	this.index = null;
  };
  
  /*
  Build the index for a particular field
  */
  FieldIndexer.prototype.buildIndexForField = function(name) {
  	var self = this;
  	// Hashmap by field name of hashmap by field value of array of tiddler titles
  	this.index = this.index || Object.create(null);
  	this.index[name] = Object.create(null);
  	var baseIndex = this.index[name];
  	// Update the index for each tiddler
  	this.wiki.eachTiddlerPlusShadows(function(tiddler,title) {
  		if(name in tiddler.fields) {
  			var value = tiddler.getFieldString(name);
  			// Skip any values above the maximum length
  			if(value.length < self.maxIndexedValueLength) {
  				baseIndex[value] = baseIndex[value] || [];
  				baseIndex[value].push(title);
  			}
  		}
  	});
  };
  
  /*
  Update the index in the light of a tiddler value changing; note that the title must be identical. (Renames are handled as a separate delete and create)
  updateDescriptor: {old: {tiddler: <tiddler>, shadow: <boolean>, exists: <boolean>},new: {tiddler: <tiddler>, shadow: <boolean>, exists: <boolean>}}
  */
  FieldIndexer.prototype.update = function(updateDescriptor) {
  	var self = this;
  	// Don't do anything if the index hasn't been built yet
  	if(this.index === null) {
  		return;
  	}
  	// Remove the old tiddler from the index
  	if(updateDescriptor.old.tiddler) {
  		$tw.utils.each(this.index,function(indexEntry,name) {
  			if(name in updateDescriptor.old.tiddler.fields) {
  				var value = updateDescriptor.old.tiddler.getFieldString(name),
  					tiddlerList = indexEntry[value];
  				if(tiddlerList) {
  					var index = tiddlerList.indexOf(updateDescriptor.old.tiddler.fields.title);
  					if(index !== -1) {
  						tiddlerList.splice(index,1);
  					}
  				}
  			}
  		});
  	}
  	// Add the new tiddler to the index
  	if(updateDescriptor["new"].tiddler) {
  		$tw.utils.each(this.index,function(indexEntry,name) {
  			if(name in updateDescriptor["new"].tiddler.fields) {
  				var value = updateDescriptor["new"].tiddler.getFieldString(name);
  				if(value.length < self.maxIndexedValueLength) {
  					indexEntry[value] = indexEntry[value] || [];
  					indexEntry[value].push(updateDescriptor["new"].tiddler.fields.title);
  				}
  			}
  		});		
  	}
  };
  
  // Lookup the given field returning a list of tiddler titles
  FieldIndexer.prototype.lookup = function(name,value) {
  	// Fail the lookup if the value is too long
  	if(value.length >= this.maxIndexedValueLength) {
  		return null;
  	}
  	// Update the index if it has yet to be built
  	if(this.index === null || !this.index[name]) {
  		this.buildIndexForField(name);
  	}
  	return this.index[name][value] || [];
  };
  
  exports.FieldIndexer = FieldIndexer;
  
  })();
  