Ext.namespace('Voyeur.Tool');


/**
 * @class Voyeur.Tool
 * @namespace Voyeur
 * @extends_ext Ext.util.Observable
 * @param {Object} config configuration object
 * @constructor Create a new Voyeur tool.
 */
Voyeur.Tool = Ext.extend(Ext.util.Observable, {
	
    // the parameter values last sent to an update
    lastUpdate : {}
	
	,constructor : function(config, tool) {

		// make sure that we have i18n and api values
		Ext.applyIf(tool, {
			i18n: {}, api: {toolFlow: {'default': null}}
		})
		
		Ext.applyIf(tool.api, {
			'corpus': {
				'default': null
				,'type': String
				,'required': false
				,'value': null
				,'multiple': false
				,'example': 'a_valid_corpus_id'
			}
		})
		

		Voyeur.Tool.superclass.constructor.call(this);
		
		
		this.xtype = config.xtype;
		this.events = [];


		// make sure the application knows about us
		this.addListener('render', function(cmp) {
			Voyeur.application.addTool(cmp);
		});
		
		this.loadLocalization(tool.i18n, this.xtype);
		
		// copy query and config values to this API
		var query = this.getApplication().query;
		for (var k in tool.api) {
			if (config.api && config.api[k]) {
				tool.api[k].value=config.api[k];
			} else if (query[k] != undefined) {
				tool.api[k].value=query[k];
			}
		}
		if (config.api) {
			delete config.api;
		}
		
		this.exporters = config.exporters ? config.exporters : {};
		Ext.applyIf(this.exporters, {
			url: this.localize('exportUrl','tool'),
			button: this.localize('exportButton','tool'),
			iframe: this.localize('exportIframe','tool'),
			citation: this.localize('exportCitation','tool')
			//html: this.localize('exportHtml','tool')
		});
				
		// build the default toolset
		var tools = [
			{
				id : 'save',
				qtip : '<h3>' + this.localize('export', 'tool')
				+ this.localize('colon', 'app')
				+ this.localize('title') + '</h3>'
				+ this.localize('exportTip','tool'),
				handler : this.onExport
				,scope : this
			},/*{
				id : 'plus',
				qtip : '<h3>' + this.localize('more', 'tool')
						+ this.localize('colon', 'app')
						+ this.localize('title') + '</h3>'
						+ this.localize('moreTip', 'tool')
				,handler : this.onMore
				,scope : this
			},*/
			{
				id : 'help',
				qtip : '<h3>' + this.localize('help', 'tool')
						+ this.localize('colon', 'app')
						+ this.localize('title') + '</h3>'
						+ this.localize('help') + '<br /><br /><i>'+this.localize('helpTipClick','tool')+"</i>"
				,handler : this.onHelp
				,scope : this
			}
		];
		
		if (tool.showOptions != undefined) {
			tools.splice(0, 0, {
				id : 'gear',
				qtip : '<h3>' + this.localize('options', 'tool')
				+ this.localize('colon', 'app')
				+ this.localize('title') + '</h3>'
				+ this.localize('optionsTip','tool'),
				handler : this.onOptions,
				scope : this
			});
		}
		
		Ext.applyIf(config, {
			title : this.localize('title') +
				'<span  class="Z3988" title="ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rfr_id=info%3Asid%2Focoins.info%3Agenerator&amp;rft.genre=book&amp;rft.btitle=Voyeur+Tools&amp;rft.title='+this.getApplication().localize('app.title')+' :: '+this.localize('title')+'&amp;rft.aulast=Sinclair&amp;rft.aufirst=St%C3%A9fan&amp;rft.au=St%C3%A9fan+Sinclair&amp;rft.au=Geoffrey+Rockwell&amp;rft.date=2009&amp;rft_id=http://voyeurtools.org/"></span>',
			toolNames : [ 'help' ],
			tools : tools
		});
	}
	
	/**
	 * Sets the given API params for this tool.
	 * @param {Object} params An object containing the keys and values to set.
	 */
	,setApiParams: function(params) {
		for (var k in params) {
			if (this.api[k]) {this.api[k].value = params[k];}
		}
	}
	
	/**
	 * Gets all the API params for this tool.
	 * @return {Object} The API params.
	 */
	,getApiParams : function() {
		var params = {};
		var val;
		for (var k in this.api) {
			val = this.getApiParamValue(k);
			if (val!==null && val!=undefined && val!='' && val != []) {
				params[k] = val;
			}
		}
		return params;
	}

	/**
	 * Gets an API param value by key.
	 * @param {String} key The name of the API param to get the value of.
	 * @return {Mixed} The value of the API param.
	 */
	,getApiParamValue : function(key) {
		if (!this.api[key]) {return null;}
		return this.api[key].value != undefined && this.api[key].value != null ? this.api[key].value : this.api[key]['default'];
	}
	
	/**
	 * Gets the default value of an API param.
	 * @param {String} key The name of the API param to get the default value of.
	 * @return {Mixed} The default value of the API param.
	 */
	,getApiParamDefaultValue : function(key) {
		if (!this.api[key]) {return null;}
		return this.api[key]['default'];
	}
	
	/**
	 * Resets all API params to their default values.
	 */
	,resetApiParamValues : function() {
		var changed = false;
		for (var k in this.api) {
			if (changed==false) {changed = this.api[k].value = this.getApiParamDefaultValue(k)}
			this.api[k].value = this.getApiParamDefaultValue(k);
		}
		return changed;
	}
	
	,onExport : function(ev, tool, panel) {
		if (panel.getColumnModel) {
			Ext.applyIf(this.exporters, {
				htmlTable: this.localize('exportHtmlTable','tool'),
				textTable: this.localize('exportTextTable','tool'),
				csvTable: this.localize('exportCsvTable','tool'),
				tabTable: this.localize('exportTabTable','tool'),
				xmlTable: this.localize('exportXmlTable','tool')
			})
		}
		
		if (this.getCanvasElement(panel)) {
			Ext.applyIf(this.exporters, {
				canvasPng: this.localize('exportCanvasPng','tool')
			})
		}
		var items = [];
		for (var k in panel.exporters) {
			if (panel.exporters[k]) {
				items.push({
					inputValue: k,
					boxLabel: panel.exporters[k],
					name: 'exp',
					checked: k=='url' ? true : false
				})
			}
		}
		
		var win = new Ext.Window({
			title: 'Export',
			modal: true,
			width: 370,
			items: {
				xtype: 'form',
				labelAlign: 'top',
				bodyStyle: 'padding:0 10px 0;',
				items: {
					xtype: 'radiogroup'
					,fieldLabel: ''
					,hideLabel: true
					,items: items
					,columns: 1
				}
				,buttons: [{
					text: this.localize('ok','tool'),
					listeners: {
						click: {
							fn: function(btn) {
								var exp = btn.findParentByType('form').getForm().getValues(false).exp;
								this.fireEvent('export', exp);
								var content = '';
								var isGrid = this.getView && this.getColumnModel;
								msg = ''; // if this is empty at the end nothing will be displayed
								var url = panel.getApplication().getBaseUrl();
								var params = {};
								if (panel.xtype!='voyeurDocumentInputAdd') {url+='tool/'+panel.xtype.replace(/^voyeur/,'')+'/';}
								
								if (exp=='url' || exp=='iframe' || exp=='button') {
									msg += '<p>'+this.localize('apiWarn','tool')+'</p>';
									if (exp=='iframe' || exp=='button') {
										content+="<!--\tExported from "+this.getApplication().getBaseUrl()+".\n\t"+this.localize('apiWarn','tool').replace(/<.+?>/g,'')+" -->\n"
									}
									if (exp=='iframe') {
										var size = this.getSize();
										content+='<iframe width="'+size.width+'" height="'+size.height+'" src="';
									}
									else if (exp=='button') {
										content+="<form action='"+url+"' method='get' target='_blank'>\n";
									}
									if (panel.xtype!='voyeurDocumentInputAdd') {
										url += '?';
										params.corpus = this.getCorpus().getId();
										var apiParams = this.getApiParams();
										for (var k in this.api) {
											if (k && this.api[k].value != null && this.api[k].value != this.api[k]['default']) {
												params[k] = this.api[k].value;
											}
										}
										url+=Ext.urlEncode(params);
									}
									if (exp=='iframe' || exp=='url') {content+=url;}
									if (exp=='iframe') {content+='"></iframe>';}
									else if (exp=='button') {
										for (var k in params) {
	
											if (typeof params[k] == 'string') {
												content+="<input type='hidden' name='"+k+"' value=\""+params[k]+"\" />\n"
											}
											else if (params[k] && params[k].length) {
												for (var i=0;i<params[k].length;i++) {
													content+="<input type='hidden' name='"+k+"' value=\""+params[k][i]+"\" />\n"
												}
											}
										}
										content+='<input type="submit" value="'+new Ext.Template(this.localize('viewResults','tool')).apply([this.localize('title')])+'" />\n</form>';
									}
									else if (exp=='url') {
										msg += '<p>'+ new Ext.Template(this.localize('clickUrl','tool')).apply([url])+'</p>';
									}
								}
								else if (exp=='htmlTable' || exp=='textTable' || exp=='csvTable' || exp=='tabTable' || exp=='xmlTable') {
									if (isGrid) {
										msg += '<p>'+this.localize('exportContentTip','tool')+'</p>';
										var columnModel = this.getColumnModel();
										var columnCount = columnModel.getColumnCount();
										var view = this.getView();
										var rowCount = view.getRows().length;
										var cols = [];
										var rows = [];
										var val;
										for (var i=0;i<columnCount;i++) {
											val = columnModel.getColumnHeader(i);
											if (val.indexOf('-checker')==-1) {
												cols.push(val);
											}
										}
										rows.push(cols);
										for (var i=0;i<rowCount;i++) {
											cols = [];
											for (var j=0;j<columnCount;j++) {
												val = view.getCell(i,j).innerHTML;
												if (val.indexOf('-checker')==-1) {
													cols.push(val);
												}
											}
											rows.push(cols);
										}
										var table = "";
										if (exp=='textTable') {
											longest = new Array(rows[0].length);
											for (var i=0;i<rows.length;i++) {
												for (var j=0;j<rows[i].length;j++) {
													val = rows[i][j].replace(/&nbsp;/g,' ').replace(/<\/?\w+.*?>/g,'');
													rows[i][j] = val;
													if (!longest[j] || longest[j]<val.length) {longest[j]=val.length;}
												}
											}
											for (var i=0;i<rows.length;i++) {
												for (var j=0;j<rows[i].length;j++) {
													rows[i][j] = String.leftPad(rows[i][j],longest[j],' ');
												}
											}
										}
										for (var i=0;i<rows.length;i++) {
											if (exp=='htmlTable') {
												if (i==0) {table+="\t<thead>\n";}
												if (i==1) {table+="\t<tbody>\n";}
												table+="\t\t<tr>\n\t\t\t<td>"+rows[i].join("</td>\n\t\t\t<td>")+"</td>\n\t\t</tr>\n";
												if (i==0) {table+="\t</thead>\n";}
												if (i==rows.length-1) {table+="\t</tbody>\n";}
											}
											else if (exp=='csvTable') {table+=rows[i].join(',')+"\n";}
											else if (exp=='tabTable' || exp=='textTable') {table+=rows[i].join("\t")+"\n";}
											else if (exp=='xmlTable') {
												if (i==0) {continue;}
												table+="\t<row>\n";
												for (var j=0;j<rows[0].length;j++) {
													table+="\t\t<field name='"+rows[0][j]+"'>"+rows[i][j].replace(/<\/?\w+.*?>/g,'')+"</field>\n";
												}
												table+="\t</row>\n";
											}
										}
										table = table.replace(/&nbsp;/g,' ').replace(/<\/?div.*?>/g,'');
										
										if (exp=='htmlTable') {
											if (exp=='html') {
												content+='<html><body><'
											}
											content+="<table cellpadding='0' cellspacing='0' border='1'>\n<caption style='caption-side: bottom; text-align: left;'><p><strong>"+this.localize('title')+"</strong>. "+this.localize('help')+'</p><p>'+this.getFooterText()+"</p></caption>"+table+"</table>\n";
										}
										else if (exp=='xmlTable') {
											content+="<?xml version='1.0'>\n<rows>\n"+table+"</rows>\n";
										}
										else if (exp=='tabTable' || exp=='csvTable') {
											// get rid of tags and commas in numbers (we may get rid of other commas we want, but oh well)
											content+=table.replace(/<\/?\w+.*?>/g,'').replace(/(\d),(\d)/g,'$1$2');
										}
										else {content+=table;}
									}
								}
								else if (exp=='citation') {
									msg+='<br clear="all" />'
									var cite = '<p>Sinclair, Stéfan and Geoffrey Rockwell. &ldquo;' +this.localize('title')+'.&rdquo; '+
										'<u>'+this.localize('title','app')+'</u>. '+
										new Date().format("j M. Y") +
										' &lt;'+url+'&gt;</p>'
									msg+=cite;
									content+=cite;
									cite = '<p>Sinclair, S. and G. Rockwell ('+new Date().format("Y")+'). '+
										this.localize('title')+'. <i>'+this.localize('title','app')+'</i>. '+
										'Retrieved '+new Date().format("F j, Y")+' from '+url+'</p>'
									msg+=cite;
									content+="\n\n"+cite;
								}
								else if (exp=='canvasPng') {
									msg = ' ';
									var canvas = this.getCanvasElement(this);
									var strData = canvas.toDataURL("image/png");
									content = '<img src="'+strData+'" />';
								}
								
								btn.findParentByType('window').close();
								
								if (msg) {
									
									// before we start, change the text area to monospace, nowrapping
									var textarea = Ext.DomQuery.selectNode('textarea',Ext.Msg.getDialog().body.dom);
									textarea.originalFontFamily=textarea.style.fontFamily;
									textarea.originalWrap=textarea.originalWrap;
									textarea.style.fontFamily='monospace';
									textarea.setAttribute('wrap','off');
	
									var msgBox = Ext.Msg.show({
									   title: this.localize('export','tool'),
									   msg:  '<p>'+this.localize('export','tool')+' '+this.exporters[exp]+'.</p>'+msg+"\n",
									   value: content,
									   width: Ext.getBody().getWidth()-50,
									   buttons: Ext.MessageBox.OK,
									   multiline: true,
									   icon: Ext.MessageBox.INFO,
									   fn: function() {
									   		textarea.style.fontFamily=textarea.originalFontFamily;
									   		textarea.setAttribute('wrap',textarea.originalWrap ? textarea.originalWrap : '');
									   		this.fireEvent('exportComplete', exp);
									   },
									   scope: this
									});
								}
							}
							,scope: panel
						}
					}
				},{
					text: this.localize('cancel','tool')
					,handler: function(btn) {
						btn.findParentByType('window').close();
					}
				}]
			},
			listeners: {
				destroy: function() {
					this.fireEvent('exportComplete');
				},
				scope: this
			}
		});
		this.showWindow(win);
	}
	
	,onOptions : function(ev, tool, panel) {
		if (panel.showOptions) {
			panel.showOptions.apply(panel, arguments)
		} else {
			this.alertInfo( {
				msg : this.localize('noOptions', 'tool')
			})
		}
	}
	
	,onHelp : function(ev, tool, panel) {
		console.warn(tool,panel)
		this.showWindow({
			title : this.localize('help', 'tool')
					+ this.localize('colon', 'app')
					+ this.localize('title'),
			msg : this.localize('help') + '<p><a href="http://hermeneuti.ca/voyeur/tools/'+this.xtype.replace(/^voyeur/,'')+'">'+this.localize('helpLink','tool')+'</a></p>',
			buttons : Ext.Msg.OK,
			icon : Ext.Msg.INFO,
			minWidth: 300
			
		}, true);
	}
	
	,onMore : function(ev, tool, panel) {
	}

	/**
	 * @method showOptions
	 * @description Overwrite to provide custom options for each subclass of Voyeur.Tool.
	 * Typically this will involve passing a Ext.Window config to showOptionsWindow.
	 */
	
	/**
	 * Shows the options window.
	 * @param {Object} config An Ext.Window config object.
	 * @param {Boolean} showStopWords True to add the commonly used stop words field.
	 * NB: You need to provide handling of the stop word field in your config object. 
	 */
	,showOptionsWindow : function(config, showStopWords) {
		showStopWords = showStopWords == null ? false : showStopWords;
		var renderTo = config.renderTo ? Ext.get(config.renderTo) : Ext.getBody();
		var width = renderTo.getWidth(true) - 40;
		width = Math.max(Math.min(width, 650), 350);
		Ext.applyIf(config, {
			renderTo : document.body
			,modal : true
			,title : this.localize('options', 'tool')
			,items: []
			,width: width
		});
		if (showStopWords) {
			config.items[0].items.unshift({
				xtype: 'compositefield',
			    fieldLabel : '<span ext:qtip="'
			        + this.localize('stopListTip','tool') + '">'
			        + this.localize('stopList','tool') + '</span>',
			    items:[{
			        xtype : 'combo',
			        id : 'stopList',
			        value : this.getApiParamValue('stopList'),
			        loadingText : this.localize('loading', 'tool'),
			        width : 200,
			        store : this.getApplication().getStopListsStore(),
			        selectOnFocus : true,
			        displayField: 'label',
			        valueField: 'id',
			        mode: 'local',
			        emptyText: this.localize('none','tool'),
			        listeners: {
			        	render: function(combo) {
			        		if (combo.getValue() != null) combo.nextSibling().enable();
			        	},
			            select: function(combo, record, index) {
			                combo.nextSibling().enable();
			            },
			            change: function(combo, newval, oldval) {
			                if (newval == '') combo.nextSibling().disable();
			            }
			        }
			    },{
			        xtype: 'button',
			        text: this.localize('viewStopWords', 'tool'),
			        disabled: true,
			        handler: function(button, event) {
			            var stopListId = button.previousSibling().getValue();
			            var win = new Ext.Window({
			                width: 200,
			                height: 400,
			                modal: false,
			                layout: 'fit',
			                tools: [{
			    				id : 'save',
			    				qtip: '<h3>' + this.localize('export', 'tool')
			    				+ this.localize('colon', 'app')
			    				+ this.localize('stopList', 'tool') + '</h3>'
			    				+ this.localize('exportTip','tool'),
			    				handler: function(ev, tool, panel, tc) {
			    					var stopList = panel.findByType('stopListView')[0];
			    					var grid = stopList.findByType('grid')[0];
			    					stopList.onExport.call(grid, ev, tool, grid);
			    				},
			    				scope: this
			    			}],
			                items: {
			                    xtype: 'stopListView',
			                    application: this.getApplication(),
			                    stopListId: stopListId
			                },
			                buttons: [{
			                    text: this.localize('ok', 'tool'),
			                    handler: function(b, e) {
			                        b.findParentByType('window').close();
			                    }
			                }]
			            });
			            win.show(button.getEl());
			        },
			        scope: this
			    },{
					xtype: 'checkbox',
					boxLabel: '<span ext:qtip="'
				        + this.localize('applyStopWordsGloballyTip','tool') + '">'
				        + this.localize('applyStopWordsGlobally','tool') + '</span>',
					id: 'globalStopWords',
					name: 'globalStopWords'
				}]
			});
		}
		config.items.unshift({
			border: false,
			html : '<div style="text-align: center; margin: .75em;">'+this.localize('hoverLabel','tool')+'</div>'
		});
		this.optionsWindow = new Ext.Window(config);
		this.showWindow(this.optionsWindow);
	},
	
	/**
	 * Hides the options window.
	 */
	hideOptionsWindow : function() {
		if (this.optionsWindow) {this.optionsWindow.close();}
	},
	
	showWindow : function(win, isMsg) {
		var availSpace = Ext.getBody().getBox();
		var dimensions;
		if (isMsg) {
			dimensions = {width: win.width || win.minWidth || 400, height: 100}
		} else {
			if (win.rendered) {
				dimensions = win.getBox();
			} else {
				dimensions = {width: win.initialConfig.width, height: win.initialConfig.height};
			}
		}
		if (dimensions.width > availSpace.width || dimensions.height > availSpace.height) {
			var newWin = confirm(this.localize('newWindow', 'tool'));
			if (newWin == true) {
				win.destroy();
				window.open(document.location.href, '_blank');
				return;
			}
		}
		if (isMsg) {
			Ext.Msg.show(win);
		} else {
			win.show();
		}
	}
	
	// this is meant to be overridden
	,getAdditionalExportParams: function() {
		return {};
	}

	/**
	 * Gets the corpus.
	 * @return {Voyeur.Corpus}
	 */
	,getCorpus : function() {
		return this.getApplication().getCorpus();
	}

	/**
	 * Gets the localization.
	 * @return {Voyeur.Localization}
	 */
	,getLocalization : function() {
		return this.getApplication().getLocalization();
	}

	,loadLocalization : function(loc) {
		this.getApplication().loadLocalization(loc, this.xtype + '.');
	}

	/**
	 * Localizes a string.
	 * @param {String} key The key of the string to localize.
	 * @param {String} [prefix] The prefix to use with the key (optional).
	 * @return {String} The localized string.
	 */
	,localize : function(key, prefix) {
		return this.getLocalization().get(
				(prefix ? prefix : this.xtype) + '.' + key);
	}

	/**
	 * Gets the Voyeur application.
	 * @return {Voyeur.Application}
	 */
	,getApplication : function() {
		return Voyeur.application;
	}

	/**
	 * Displays an error message.
	 * @param {Mixed} config Either a string, or an Ext.MessageBox.show config object.
	 */
	,alertError : function(config) {
		if (typeof config == "string") {
			config = {msg: config}
		}
		Ext.applyIf(config, {
			title : this.localize('error', 'app')+ " ("+this.localize('title')+")",
			buttons : Ext.Msg.OK,
			icon : Ext.Msg.ERROR,
			resizable: true,
			width: 600,
			height: 400
		})
		return Ext.MessageBox.show(config);
	}

	,handleAjaxError: function(response, options) {
		this.alertError({msg: response.responseText})
	}
	
	/**
	 * Displays a message.
	 * @param {Mixed} config Either a string, or an Ext.MessageBox.show config object.
	 */
	,alertInfo : function(config) {
		if (typeof config == "string") {
			config = {msg: config}
		}
		Ext.applyIf(config, {
			title : this.localize('info', 'app')+ " ("+this.localize('title')+")",
			buttons : Ext.Msg.OK,
			icon : Ext.Msg.INFO,
			width: 600,
			height: 400
		})
		return Ext.MessageBox.show(config);
	}

    /**
     * Initiates an Ajax call with progress bar.
     * @param {Object} args configuration options that may contain the following properties:<ul>
     * <li><b>progressWin</b> : Ext.ProgressBar<div class="sub-desc">(Optional) A {@link Ext.ProgressBar} to show progress (if no progressWin is specified, one will be created).</div></li>
     * <li><b>renderTo</b>: Mixed<div class="sub-desc">(Optional) A DOM id, DOM node or {@link Ext.Element} (same arguments as {@link Ext.get}. If this property is not provided then the document body will be used.</div></li>
     * <li><b>title</b>: String<div class="sub-desc">(Optional) The progress bar's title.</div></li>
     * <li><b>params</b>: Mixed<div class="sub-desc">(Optional) Parameters to be passed to the Ajax request, either as an object or as a URL encoded string (name=value&amp;name=value...)</div></li>
     * </ul>
     */
	,update : function(config) {
		this.lastUpdate = config;
		this.getApplication().update(config, this);
	}
	
	/**
	 * Reinitiate the previous update, optionally setting new parameter values (other arguments will remain the same).
	 * @param {Object} config an object representing override parameter values compared to the last update
	 */
	,reupdate : function(params) {
		Ext.apply(this.lastUpdate.params, params);
		this.update(this.lastUpdate);
	}

	/**
	 * Gets the Trombone URL.
	 * @return {String}
	 */
	,getTromboneUrl : function() {
		return this.getApplication().getTromboneUrl();
	}
	
	/**
	 * Gets the directory (URL) that this tool resides in.
	 * Useful for accessing tool-related resources.
	 * @return {String}
	 */
	,getToolDirectoryUrl: function() {
		return this.getApplication().getBaseUrl()+'resources/tools/'+this.xtype.replace(/^voyeur/,'')+'/';
	}
	
	,getFooterText : function(forElement) {
		forElement = forElement ? forElement : Ext.getBody();
		var availableWidth = forElement.getWidth();
		var parts = [
			this.localize('appLink','tool'),
			", <a href='http://stefansinclair.name/'>St&eacute;fan Sinclair</a> &amp; <a href='http://geoffreyrockwell.com'>Geoffrey Rockwell</a>",
			" (&copy;"+ new Date().getFullYear() +")",
			" v. "+this.getApplication().getVersion() + " ("+this.getApplication().getBuild()+")"
		];
		var footer = '';
		var footerWidth = 0;
		var partWidth;
		for (var i=0;i<parts.length;i++) {
			partWidth = forElement.getTextWidth(parts[i]);
			if (footerWidth+partWidth < availableWidth) {
				footer += parts[i];
				footerWidth += partWidth;
			}
		}
		return footer;
	}
	
	/**
	 * Gets a spark line.
	 * @param {Array} values An array of numerical values.
	 * @param {Integer} stretch The width to stretch the spark line towards (currently unused).
	 * @return {String} The image(s) of the spark line.
	 */
	,getSparkLine : function(values, stretch) {
		if (values.length<2) {return ''}
		var min = Number.MAX_VALUE;
		var max = Number.MIN_VALUE;
		var hasDecimal = false;
		for (var i=0;i<values.length;i++) {
			if (values[i]<min) {min=values[i];}
			if (values[i]>max) {max=values[i];}
			if (!hasDecimal && values[i].toString().indexOf('.')>-1) {hasDecimal=true;}
		}
		var dif = (max-min).toString();
		var multiplier = 1;
		var divider = 1;
		
		var newvalues = [];
		if (hasDecimal) {
			var multiplier = 100;
			var ind = dif.indexOf(".")+1;
			for (var i=ind;i<dif.length;i++) {
				if (dif.charAt(i)=='0') {multiplier*=10;}
				else {break;}
			}
			for (var i=0;i<values.length;i++) {
				newvalues[i]=parseInt(values[i]*multiplier);
			}
			max = parseInt(max*multiplier);
			min = parseInt(min*multiplier);
			
		}
		else {
			var divider = 1;
			for (var i=dif.length-1;i>-1;i--) {
				if (dif.charAt(i)=='0') {divider*=10;}
				else {break;}
			}
			if (divider!=1) {
				for (var i=0;i<values.length;i++) {
					newvalues[i]=values[i]/divider;
				}
				max /= divider;
				min /= divider;
			}
			else {
				newvalues=values;
			}
		}
		
		var valLen = (max.toString().length > min.toString().length ? max.toString().length : min.toString().length)+1;
		var valuesPerImage = Math.floor(1800/valLen);
		var baseUrl = 'http://chart.apis.google.com/chart?cht=ls&amp;chco=0077CC&amp;chls=1,0,0&amp;chds='+min+','+max
		var images = Math.ceil(values.length/valuesPerImage);
		var counter=0;
		var response = '';
		var wid;
		if (values.length<5) {wid=5;}
		else if (values.length<10) {wid=4;}
		else if (values.length<20) {wid=3;}
		else if (values.length<50) {wid=2;}
		else {wid=1;}

		/*if (stretch) {
			wid = Math.ceil(stretch/values.length);
			if (wid>5) {wid=5;}
		}*/

		for (var i=0;i<images;i++) {
			var vals = newvalues.slice(counter,counter+=valuesPerImage);
			response += "<img style='margin: 0; padding: 0;' border='0' src='"+baseUrl+'&amp;chs='+(wid*vals.length)+'x15&amp;chd=t:'+vals.join(',')+"' alt='' class='chart-";
			if (images==1) {response+='full';}
			else {
				if (i>0 && i+1<images) {response+'middle';}
				else if (i==0) {response+='left';}
				else {response+='right';}
			}
			response += "' />";
		}
		return response;
	}
	
	/**
	 * Gets the canvas element for this tool.
	 * @return {Element} The canvas element, or undefined if none is found.
	 */
	,getCanvasElement : function(panel) {
		return Ext.DomQuery.selectNode('canvas', panel.body.dom);
	}
});

Voyeur.localization.load( {
	'tool.help' : {
		en : 'Help'
	},
	'tool.helpTipClick' : {
		en : 'Click on the question mark icon for more help.'
	},
	'tool.helpLink' : {
		en: 'More help for this tool...'
	},
	'tool.viewResults' : {
		en : 'View {0} in Voyant.'
	},
	'tool.noResults' : {
		en : 'No results.'
	},
	'tool.noOptions' : {
		en : 'This tool does not define any user-controlled options.'
	},
	'tool.options' : {
		en : 'Options'
	},
	'tool.optionsTip' : {
		en : 'Click here to modify options for this tool.'
	},
	'tool.export' : {
		en : 'Export'
	},
	'tool.exportTip' : {
		en : 'Click here to export data from this tool.'
	},
	'tool.noExport' : {
		en : 'This tool does not provide any export options.'
	},
	'tool.more' : {
		en : 'More Tools'
	},
	'tool.moreTip' : {
		en : 'Click here to see other tools that are available.'
	},
	'tool.more' : {
		en : 'More Tools'
	},
	'tool.hoverLabel' : {
		en: 'Tip: Hover over the labels to see more information about each item.'
	},
	'tool.ok' : {
		en : 'OK'
	},
	'tool.cancel' : {
		en : 'Cancel'
	},
	'tool.restore' : {
		en : 'Defaults'
	},
	'tool.exportUrl' : {
		en: 'a URL for this tool and current data'
	},
	'tool.clickUrl' : {
		en: 'Open this <a href="{0}" target="_blank">URL</a> in a new window.'
	},
	'tool.exportIframe' : {
		en: 'an HTML snippet to embed this tool and current data'
	},
	'tool.exportButton' : {
		en: 'an HTML button for this tool and current data'
	},
//	'tool.exportHtml' : {
//		en: 'a simple HTML page with static tabular data'
//	},
	'tool.exportHtmlTable' : {
		en: 'an HTML snippet with static tabular data'
	},
	'tool.exportTextTable' : {
		en: 'tabular data as plain text'
	},
	'tool.exportCsvTable' : {
		en: 'current data as comma-separated values'
	},
	'tool.exportTabTable' : {
		en: 'current data as tab-separated values'
	},
	'tool.exportXmlTable' : {
		en: 'current data as XML'
	},
	'tool.exportContentTip': {
		en: 'Select all content in the box below and copy it into the clipboard.'
	}
	,'tool.exportCitation' : {
		en: 'a bibliographic citation for this tool'
	},
	'tool.exportCanvasPng' : {
		en: 'a PNG image of the visualization'
	},
	'tool.apiWarn' : {
		en: 'Please note that this is an early version and the API may change.\n\tYou are strongly encouraged to subscribe to a list to receive notifications\n\tof updates to Voyant (updated code, planned outages, etc.) – please send\n\ta message to sgsinclair@voyeurtools.org.'
	},
	'tool.appLink' : {
		en: "<a href='http://hermeneuti.ca/voyeur/' target='_blank'>Voyant Tools</a>"
	},
	'tool.extendedSortZscoreMinimum' : {
			en : 'Minimum Z-Score'
	},
	'tool.extendedSortZscoreMinimumTip' : {
			en : 'This defines the minimum z-score (up to two decimals) for a corpus type. The default value of 1 helps to ensure that only higher frequency terms are kept.'
	},
	'tool.stopList' : {
		en: 'Stop Words List'
	},
	'tool.stopListTip' : {
		en: 'Stop words are usually high frequency words that carry little meaning, such as articles (the, a, etc.) and prepositions (of, to, etc.). You can select a pre-existing list from the menu, specify a URL for a list of stop words in plain text format (one per line), or provide a custom list with words separated by commas.'
	},
	'tool.viewStopWords' : {
		en: 'View Stop Words'
	},
	'tool.applyStopWordsGlobally' : {
		en: 'Apply Stop Words Globally'
	},
	'tool.applyStopWordsGloballyTip' : {
		en: 'Applies the selected stop words to all the tools.  NB: This will take effect on all proceeding actions carried out with the tools.'
	},
	'tool.filtersInEffect' : {
		en: 'Filters in Effect (see options):'
	},
	'tool.none' : {
		en : 'none'
	},
	'tool.searchText' : {
		en : 'Search'
	},
	'tool.newWindow' : {
		en : "There isn't enough room to display the window in the current space.  Would you like to open a new window?"
	}
});
