/*
    ***** BEGIN LICENSE BLOCK *****
    
    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org
    
    This file is part of Zotero.
    
    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    
    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
    
    
    Based on code from Greasemonkey and PiggyBank
    
    ***** END LICENSE BLOCK *****
*/

//
// Zotero Ingester Browser Functions
//

//////////////////////////////////////////////////////////////////////////////
//
// Zotero_Browser
//
//////////////////////////////////////////////////////////////////////////////

// Class to interface with the browser when ingesting data

var Zotero_Browser = new function() {
	this.init = init;
	this.annotatePage = annotatePage;
	this.toggleMode = toggleMode;
	this.toggleCollapsed = toggleCollapsed;
	this.chromeLoad = chromeLoad;
	this.itemUpdated = itemUpdated;
	this.tabClose = tabClose;
	this.resize = resize;
	
	this.tabbrowser = null;
	this.appcontent = null;
	this.isScraping = false;
	
	var _browserData = new WeakMap();
	var _attachmentsMap = new WeakMap();
	var _detectCallbacks = [];
	
	var _blacklist = [
		"googlesyndication.com",
		"doubleclick.net",
		"questionmarket.com",
		"atdmt.com",
		"aggregateknowledge.com",
		"ad.yieldmanager.com"
	];
	
	var _locationBlacklist = [
		"zotero://debug/"
	];
	
	var tools = {
		'zotero-annotate-tb-add':{
			cursor:"pointer",
			event:"click",
			callback:function(e) { _add("annotation", e) }
		},
		'zotero-annotate-tb-highlight':{
			cursor:"text",
			event:"mouseup",
			callback:function(e) { _add("highlight", e) }
		},
		'zotero-annotate-tb-unhighlight':{
			cursor:"text",
			event:"mouseup",
			callback:function(e) { _add("unhighlight", e) }
		}
	};

	//////////////////////////////////////////////////////////////////////////////
	//
	// Public Zotero_Browser methods
	//
	//////////////////////////////////////////////////////////////////////////////
	
	
	/**
	 * Initialize some variables and prepare event listeners for when chrome is done loading
	 */
	function init() {
		// No gBrowser - running in standalone
		if (!window.hasOwnProperty("gBrowser")) {
			return;
		}
		
		var zoteroInitDone;
		if (!Zotero || Zotero.skipLoading) {
			// Zotero either failed to load or is reloading in Connector mode
			// In case of the latter, listen for the 'zotero-loaded' event (once) and retry
			var zoteroInitDone_deferred = Zotero.Promise.defer();
			var obs = Components.classes["@mozilla.org/observer-service;1"]
				.getService(Components.interfaces.nsIObserverService);
			var observer = {
				"observe":function() {
					obs.removeObserver(observer, 'zotero-loaded')
					zoteroInitDone_deferred.resolve();
				}
			};
			obs.addObserver(observer, 'zotero-loaded', false);
			
			zoteroInitDone = zoteroInitDone_deferred.promise;
		} else {
			zoteroInitDone = Zotero.Promise.resolve();
		}
		
		var chromeLoaded = Zotero.Promise.defer();
		window.addEventListener("load", function(e) { chromeLoaded.resolve() }, false);
		
		// Wait for Zotero to init and chrome to load before proceeding
		Zotero.Promise.all([
			zoteroInitDone.then(function() {
				ZoteroPane_Local.addReloadListener(reload);
				reload();
			}),
			chromeLoaded.promise
		])
		.then(function() {
			Zotero_Browser.chromeLoad()
		});
	}
	
	/**
	 * Called when Zotero is reloaded
	 */
	function reload() {
		// Handles the display of a div showing progress in scraping
		Zotero_Browser.progress = new Zotero.ProgressWindow();
	}
	
	/**
	 * Saves from current page using translator (called when the capture icon is clicked)
	 *
	 * @param {String} [translator]
	 * @param {Event} [event]
	 * @return {Promise}
	 */
	this.scrapeThisPage = Zotero.Promise.coroutine(function* (translator, event) {
		// Perform translation
		var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
		var page = tab.getPageObject();
		if(page.translators && page.translators.length) {
			page.translate.setTranslator(translator || page.translators[0]);
			yield Zotero_Browser.performTranslation(page.translate);
		}
		else {
			yield this.saveAsWebPage(
				(event && event.shiftKey) ? !Zotero.Prefs.get('automaticSnapshots') : null
			);
		}
	});
	
	/**
	 * Keep in sync with cmd_zotero_newItemFromCurrentPage
	 *
	 * @return {Promise}
	 */
	this.saveAsWebPage = function (includeSnapshots) {
		// DEBUG: Possible to just trigger command directly with event? Assigning it to the
		// command property of the icon doesn't seem to work, and neither does goDoCommand()
		// from chrome://global/content/globalOverlay.js. Getting the command by id and
		// running doCommand() works but doesn't pass the event.
		return ZoteroPane.addItemFromPage('temporaryPDFHack', includeSnapshots);
	}
	
	/*
	 * flags a page for annotation
	 */
	function annotatePage(id, browser) {
		if (browser) {
			var tab = _getTabObject(browser);
		}
		else {
			var tab = _getTabObject(this.tabbrowser.selectedBrowser);
		}
	}
	
	/*
	 * toggles a tool on/off
	 */
	function toggleMode(toggleTool, ignoreOtherTools) {
		// make sure other tools are turned off
		if(!ignoreOtherTools) {
			for(var tool in tools) {
				if(tool != toggleTool && document.getElementById(tool).getAttribute("tool-active")) {
					toggleMode(tool, true);
				}
			}
		}
		
		// make sure annotation action is toggled
		var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
		var page = tab.getPageObject();
		if(page && page.annotations && page.annotations.clearAction) page.annotations.clearAction();
		
		if(!toggleTool) return;
		
		var body = Zotero_Browser.tabbrowser.selectedBrowser.contentDocument.getElementsByTagName("body")[0];
		var addElement = document.getElementById(toggleTool);
		
		if(addElement.getAttribute("tool-active")) {
			// turn off
			body.style.cursor = "auto";
			addElement.removeAttribute("tool-active");
			Zotero_Browser.tabbrowser.selectedBrowser.removeEventListener(tools[toggleTool].event, tools[toggleTool].callback, true);
		} else {
			body.style.cursor = tools[toggleTool].cursor;
			addElement.setAttribute("tool-active", "true");
			Zotero_Browser.tabbrowser.selectedBrowser.addEventListener(tools[toggleTool].event, tools[toggleTool].callback, true);
		}
	}
	
	/*
	 * expands all annotations
	 */
	function toggleCollapsed() {
		var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
		tab.getPageObject().annotations.toggleCollapsed();
	}
	
	/*
	 * When chrome loads, register our event handlers with the appropriate interfaces
	 */
	function chromeLoad() {
		this.tabbrowser = gBrowser;
		this.appcontent = document.getElementById("appcontent");
		
		// this gives us onLocationChange, for updating when tabs are switched/created
		gBrowser.tabContainer.addEventListener("TabClose",
			function(e) {
				//Zotero.debug("TabClose");
				Zotero_Browser.tabClose(e);
			}, false);
		gBrowser.tabContainer.addEventListener("TabSelect",
			function(e) {
				//Zotero.debug("TabSelect");
				// Note: async
				Zotero_Browser.updateStatus();
			}, false);
		// this is for pageshow, for updating the status of the book icon
		this.appcontent.addEventListener("pageshow", contentLoad, true);
		// this is for turning off the book icon when a user navigates away from a page
		this.appcontent.addEventListener("pagehide",
			function(e) {
				//Zotero.debug("pagehide");
				Zotero_Browser.contentHide(e);
			}, true);
		
		this.tabbrowser.addEventListener("resize",
			function(e) { Zotero_Browser.resize(e) }, false);
		// Resize on text zoom changes
		
		var reduce = document.getElementById('cmd_fullZoomReduce');
		var enlarge = document.getElementById('cmd_fullZoomEnlarge');
		var reset = document.getElementById('cmd_fullZoomReset');
		
		if(reduce) reduce.addEventListener("command",
			function(e) { Zotero_Browser.resize(e) }, false);
		if(enlarge) enlarge.addEventListener("command",
			function(e) { Zotero_Browser.resize(e) }, false);
		if(reset) reset.addEventListener("command",
			function(e) { Zotero_Browser.resize(e) }, false);
	}
	
	
	/*
	 * An event handler called when a new document is loaded. Creates a new document
	 * object, and updates the status of the capture icon
	 */
	var contentLoad = function (event) {
		var doc = event.originalTarget;
		var isHTML = doc instanceof HTMLDocument;
		var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc);
		var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc);
		if(!browser) return;
		
		if(isHTML) {
			// ignore blacklisted domains
			try {
				if(doc.domain) {
					for (let i = 0; i < _blacklist.length; i++) {
						let blacklistedURL = _blacklist[i];
						if(doc.domain.substr(doc.domain.length-blacklistedURL.length) == blacklistedURL) {
							Zotero.debug("Ignoring blacklisted URL "+doc.location);
							return;
						}
					}
				}
			}
			catch (e) {}
		}
		
		try {
			if (_locationBlacklist.indexOf(doc.location.href) != -1) {
				return;
			}
			
			// Ignore TinyMCE popups
			if (!doc.location.host && doc.location.href.indexOf("tinymce/") != -1) {
				return;
			}
		}
		catch (e) {}
		
		// get data object
		var tab = _getTabObject(browser);
		
		if(isHTML && !Zotero.isConnector) {
			var annotationID = Zotero.Annotate.getAnnotationIDFromURL(browser.currentURI.spec);
			if(annotationID) {
				if(Zotero.Annotate.isAnnotated(annotationID)) {
					//window.alert(Zotero.getString("annotations.oneWindowWarning"));
				} else {
					var page = tab.getPageObject();
					if(!page.annotations) {
						// enable annotation
						page.annotations = new Zotero.Annotations(Zotero_Browser, browser, annotationID);
						var saveAnnotations = function() {
							page.annotations.save();
							page.annotations = undefined;
						};
						browser.contentWindow.addEventListener('beforeunload', saveAnnotations, false);
						browser.contentWindow.addEventListener('close', saveAnnotations, false);
						page.annotations.load();
					}
				}
			}
		}
		
		// detect translators
		tab.detectTranslators(rootDoc, doc);
		
		// register metadata updated event
		if(isHTML) {
			var contentWin = doc.defaultView;
			if(!contentWin.haveZoteroEventListener) {
				contentWin.addEventListener("ZoteroItemUpdated", function(event) { itemUpdated(event.originalTarget) }, false);
				contentWin.haveZoteroEventListener = true;
			}
		}
	};

	/*
	 * called to unregister Zotero icon, etc.
	 */
	this.contentHide = function (event) {
		var doc = event.originalTarget;
		if(!(doc instanceof HTMLDocument)) return;
	
		var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc);
		var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc);
		if(!browser) return;
		
		var tab = _getTabObject(browser);
		if(!tab) return;

		var page = tab.getPageObject();
		if(!page) return;

		if(doc == page.document || doc == rootDoc) {
			// clear translator only if the page on which the pagehide event was called is
			// either the page to which the translator corresponded, or the root document
			// (the second check is probably paranoid, but won't hurt)
			tab.clear();
		}
		
		// update status
		if(Zotero_Browser.tabbrowser.selectedBrowser == browser) {
			// Note: async
			this.updateStatus();
		}
	}
	
	/**
	 * Called when item should be updated due to a DOM event
	 */
	function itemUpdated(doc) {
		try {
			var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc);
			var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc);
			var tab = _getTabObject(browser);
			if(doc == tab.getPageObject().document || doc == rootDoc) tab.clear();
			tab.detectTranslators(rootDoc, doc);
		} catch(e) {
			Zotero.debug(e);
		}
	}
	
	/*
	 * called when a tab is closed
	 */
	function tabClose(event) {
		// Save annotations when closing a tab, since the browser is already
		// gone from tabbrowser by the time contentHide() gets called
		var tab = _getTabObject(event.target);
		var page = tab.getPageObject();
		if(page && page.annotations) page.annotations.save();
		tab.clear();
		
		// To execute if document object does not exist
		toggleMode();
	}
	
	
	/*
	 * called when the window is resized
	 */
	function resize(event) {
		var tab = _getTabObject(this.tabbrowser.selectedBrowser);
		var page = tab.getPageObject();
		if(!page.annotations) return;
		
		page.annotations.refresh();
	}
	
	/*
	 * Updates the status of the capture icon to reflect the scrapability or lack
	 * thereof of the current page
	 */
	this.updateStatus = Zotero.Promise.coroutine(function* () {
		// Wait for translator initialization. This allows detection to still run on a page at startup
		// once translators have finished loading.
		if (Zotero.Schema && Zotero.Schema.schemaUpdatePromise.isPending()) {
			yield Zotero.Schema.schemaUpdatePromise;
		}
		
		if (!Zotero_Browser.tabbrowser) return;
		var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
		
		Components.utils.import("resource:///modules/CustomizableUI.jsm");
		var buttons = getSaveButtons();
		if (buttons.length) {
			let state = tab.getCaptureState();
			let tooltiptext = tab.getCaptureTooltip();
			for (let { button, placement } of buttons) {
				let inToolbar = placement.area == CustomizableUI.AREA_NAVBAR;
				button.image = tab.getCaptureIcon(Zotero.hiDPI || !inToolbar);
				button.tooltipText = tooltiptext;
				if (state == tab.CAPTURE_STATE_TRANSLATABLE) {
					button.classList.add('translate');
				}
				else {
					button.classList.remove('translate');
				}
				button.removeAttribute('disabled');
			}
		}
		
		// set annotation bar status
		var page = tab.getPageObject();
		if(page.annotations && page.annotations.annotations.length) {
			document.getElementById('zotero-annotate-tb').hidden = false;
			toggleMode();
		} else {
			document.getElementById('zotero-annotate-tb').hidden = true;
		}
	});
	
	this.addDetectCallback = function (func) {
		_detectCallbacks.push(func);
	};
	
	this.resolveDetectCallbacks = Zotero.Promise.coroutine(function* () {
		while (_detectCallbacks.length) {
			let cb = _detectCallbacks.shift();
			var res = cb();
			if (res && res.then) {
				yield res.then;
			}
		}
	});
	
	function getSaveButtons() {
		Components.utils.import("resource:///modules/CustomizableUI.jsm");
		var buttons = [];
		
		var placement = CustomizableUI.getPlacementOfWidget("zotero-toolbar-buttons");
		if (placement) {
			let button = document.getElementById("zotero-toolbar-save-button");
			if (button) {
				buttons.push({
					button: button,
					placement: placement
				});
			}
		}
		
		placement = CustomizableUI.getPlacementOfWidget("zotero-toolbar-save-button-single");
		if (placement) {
			let button = document.getElementById("zotero-toolbar-save-button-single");
			if (button) {
				buttons.push({
					button: button,
					placement: placement
				});
			}
		}
		
		return buttons;
	}
	
	/**
	 * Called when status bar icon is right-clicked
	 */
	this.onStatusPopupShowing = function(e) {
		var popup = e.target;
		while(popup.hasChildNodes()) popup.removeChild(popup.lastChild);
		
		var tab = _getTabObject(this.tabbrowser.selectedBrowser);
		var captureState = tab.getCaptureState();
		if (captureState == tab.CAPTURE_STATE_TRANSLATABLE) {
			let translators = tab.getPageObject().translators;
			for (var i=0, n = translators.length; i < n; i++) {
				let translator = translators[i];
				
				let menuitem = document.createElement("menuitem");
				menuitem.setAttribute("label",
					Zotero.getString("ingester.saveToZoteroUsing", translator.label));
				menuitem.setAttribute("image", (translator.itemType === "multiple"
					? "chrome://zotero/skin/treesource-collection.png"
					: Zotero.ItemTypes.getImageSrc(translator.itemType)));
				menuitem.setAttribute("class", "menuitem-iconic");
				menuitem.addEventListener("command", function(e) {
					Zotero_Browser.scrapeThisPage(translator, e);
					e.stopPropagation();
				}, false);
				popup.appendChild(menuitem);
			}
		}
		
		let webPageIcon = tab.getWebPageCaptureIcon(Zotero.hiDPI);
		let menuitem = document.createElement("menuitem");
		menuitem.setAttribute("label", Zotero.getString('ingester.saveToZoteroAsWebPageWithSnapshot'));
		menuitem.setAttribute("image", webPageIcon);
		menuitem.setAttribute("class", "menuitem-iconic");
		menuitem.addEventListener("command", function (event) {
			Zotero_Browser.saveAsWebPage(true);
			event.stopPropagation();
		});
		popup.appendChild(menuitem);
		
		menuitem = document.createElement("menuitem");
		menuitem.setAttribute("label", Zotero.getString('ingester.saveToZoteroAsWebPageWithoutSnapshot'));
		menuitem.setAttribute("image", webPageIcon);
		menuitem.setAttribute("class", "menuitem-iconic");
		menuitem.addEventListener("command", function (event) {
			Zotero_Browser.saveAsWebPage(false);
			event.stopPropagation();
		});
		popup.appendChild(menuitem);
		
		if (captureState == tab.CAPTURE_STATE_TRANSLATABLE) {
			popup.appendChild(document.createElement("menuseparator"));
			
			let menuitem = document.createElement("menuitem");
			menuitem.setAttribute("label", Zotero.getString("locate.libraryLookup.label"));
			menuitem.setAttribute("tooltiptext", Zotero.getString("locate.libraryLookup.tooltip"));
			menuitem.setAttribute("image", "chrome://zotero/skin/locate-library-lookup.png");
			menuitem.setAttribute("class", "menuitem-iconic");
			menuitem.addEventListener("command", _constructLookupFunction(tab, function(event, obj) {
				var urls = [];
				for (let i = 0; i < obj.newItems.length; i++) {
					var url = Zotero.OpenURL.resolve(obj.newItems[i]);
					if(url) urls.push(url);
				}
				ZoteroPane.loadURI(urls, event);
			}), false);
			popup.appendChild(menuitem);		
			
			var locateEngines = Zotero.LocateManager.getVisibleEngines();
			Zotero_LocateMenu.addLocateEngines(popup, locateEngines,
				_constructLookupFunction(tab, function(e, obj) {
				Zotero_LocateMenu.locateItem(e, obj.newItems);
			}), true);
		}
	}
	
	/**
	 * Translates using the specified translation instance. setTranslator() must already
	 * have been called
	 * @param {Zotero.Translate} translate
	 */
	this.performTranslation = Zotero.Promise.coroutine(function* (translate, libraryID, collection) {
		if (Zotero.locked) {
			Zotero_Browser.progress.Translation.operationInProgress();
			return;
		}
		
		if (!Zotero.isConnector && Zotero.DB.inTransaction()) {
			yield Zotero.DB.waitForTransaction();
		}
		
		Zotero_Browser.progress.show();
		Zotero_Browser.isScraping = true;
		
		// Get libraryID and collectionID
		if(libraryID === undefined && ZoteroPane && !Zotero.isConnector) {
			// Save to My Library by default if pane hasn't been opened
			if (!ZoteroPane.collectionsView || !ZoteroPane.collectionsView.selectedTreeRow) {
				libraryID = Zotero.Libraries.userLibraryID;
			}
			else if (!ZoteroPane.collectionsView.editable) {
				Zotero_Browser.progress.Translation.cannotEditCollection();
				return;
			}
			else {
				libraryID = ZoteroPane.getSelectedLibraryID();
			}
			
			collection = ZoteroPane.getSelectedCollection();
		}
		
		if (!Zotero.isConnector) {
			if (ZoteroPane.collectionsView
					&& ZoteroPane.collectionsView
					&& ZoteroPane.collectionsView.selectedTreeRow
					&& ZoteroPane.collectionsView.selectedTreeRow.isPublications()) {
				Zotero_Browser.progress.Translation.cannotAddToPublications();
				return;
			}
			
			if (Zotero.Feeds.get(libraryID)) {
				Zotero_Browser.progress.Translation.cannotAddToFeed();
				return;
			}
		}
		
		Zotero_Browser.progress.Translation.scrapingTo(libraryID, collection);
		
		translate.clearHandlers("done");
		translate.clearHandlers("itemDone");
		translate.clearHandlers("attachmentProgress");
		
		var deferred = Zotero.Promise.defer();
		
		translate.setHandler("done", function() {
			Zotero_Browser.progress.Translation.doneHandler.apply(Zotero_Browser.progress.Translation, arguments);
			Zotero_Browser.isScraping = false;
			deferred.resolve();
		});
		
		translate.setHandler("itemDone", function() {
			let handler = Zotero_Browser.progress.Translation.itemDoneHandler(_attachmentsMap);
			handler.apply(Zotero_Browser.progress.Translation, arguments);
		});
		
		translate.setHandler("attachmentProgress", function() {
			let handler = Zotero_Browser.progress.Translation.attachmentProgressHandler(_attachmentsMap);
			handler.apply(Zotero_Browser.progress.Translation, arguments);
		});
		
		translate.translate({
			libraryID,
			collections: collection ? [collection.id] : false
		});
		
		return deferred.promise;
	});
	
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Private Zotero_Browser methods
	//
	//////////////////////////////////////////////////////////////////////////////
	
	function _constructLookupFunction(tab, success) {
		return function(e) {
			var page = tab.getPageObject();
			page.translate.setTranslator(page.translators[0]);
			page.translate.clearHandlers("done");
			page.translate.clearHandlers("itemDone");
			page.translate.setHandler("done", function(obj, status) {
				if(status) {
					success(e, obj);
					Zotero_Browser.progress.close();
				} else {
					Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.lookup.error"));
					Zotero_Browser.progress.startCloseTimer(8000);
				}
			});
			
			Zotero_Browser.progress.show();
			Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.lookup.performing"));
			page.translate.translate(false);
			e.stopPropagation();
		}
	}
	
	/*
	 * Gets a data object given a browser window object
	 */
	function _getTabObject(browser) {
		if(!browser) return false;
		var obj = _browserData.get(browser);
		if(!obj) {
			obj = new Zotero_Browser.Tab(browser);
			_browserData.set(browser, obj);
		}
		return obj;
	}
	
	/**
	 * Adds an annotation
	 */
	 function _add(type, e) {
		var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser);
		
		if(type == "annotation") {
			// ignore click if it's on an existing annotation
			if(e.target.getAttribute("zotero-annotation")) return;
			
			var annotation = tab.getPageObject().annotations.createAnnotation();
			annotation.initWithEvent(e);
			
			// disable add mode, now that we've used it
			toggleMode();
		} else {
			try {
				var selection = Zotero_Browser.tabbrowser.selectedBrowser.contentWindow.getSelection();
			} catch(err) {
				return;
			}
			if(selection.isCollapsed) return;
			
			if(type == "highlight") {
				tab.getPageObject().annotations.highlight(selection.getRangeAt(0));
			} else if(type == "unhighlight") {
				tab.getPageObject().annotations.unhighlight(selection.getRangeAt(0));
			}
			
			selection.removeAllRanges();
		}
		
		// stop propagation
		e.stopPropagation();
		e.preventDefault();
	 }
}


//////////////////////////////////////////////////////////////////////////////
//
// Zotero_Browser.Tab
//
//////////////////////////////////////////////////////////////////////////////

Zotero_Browser.Tab = function(browser) {
	this.browser = browser;
	this.wm = new WeakMap();
}

Zotero_Browser.Tab.prototype.CAPTURE_STATE_DISABLED = 0;
Zotero_Browser.Tab.prototype.CAPTURE_STATE_GENERIC = 1;
Zotero_Browser.Tab.prototype.CAPTURE_STATE_TRANSLATABLE = 2;

/**
 * Gets page-specific information (stored in WeakMap to prevent holding
 * a reference to translate)
 */
Zotero_Browser.Tab.prototype.getPageObject = function() {
	var doc = this.browser.contentWindow;
	if(!doc) return null;
	var obj = this.wm.get(doc);
	if(!obj) {
		obj = {};
		this.wm.set(doc, obj);
	}
	return obj;
}

/*
 * Removes page-specific information from WeakMap
 */
Zotero_Browser.Tab.prototype.clear = function() {
	this.wm.delete(this.browser.contentWindow);
}

/*
 * detects translators for this browser object
 */
Zotero_Browser.Tab.prototype.detectTranslators = Zotero.Promise.coroutine(function* (rootDoc, doc) {
	if (Zotero.Schema && Zotero.Schema.schemaUpdatePromise.isPending()) {
		yield Zotero.Schema.schemaUpdatePromise;
	}
	
	// If document no longer exists after waiting for schema updates (probably because another page has
	// been loaded), bail
	if (Components.utils.isDeadWrapper(doc)) {
		return;
	}
	
	if (doc instanceof HTMLDocument) {
		if (doc.documentURI.startsWith("about:")) {
			return;
		}
		
		// get translators
		var me = this;
		
		var translate = new Zotero.Translate.Web();
		translate.setDocument(doc);
		translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
		translate.setHandler("pageModified", function(translate, doc) { Zotero_Browser.itemUpdated(doc) });
		translate.getTranslators(true);
	} else if(doc.documentURI.substr(0, 7) == "file://") {
		this._attemptLocalFileImport(doc);
	}
});


/*
 * searches for a document in all of the frames of a given document
 */
Zotero_Browser.Tab.prototype._searchFrames = function(rootDoc, searchDoc) {
	if(rootDoc == searchDoc) return true;
	var frames = rootDoc.getElementsByTagName("frame");
	for (let i = 0; i < frames.length; i++) {
		let frame = frames[i];
		if(frame.contentDocument &&
				(frame.contentDocument == searchDoc ||
				this._searchFrames(frame.contentDocument, searchDoc))) {
			return true;
		}
	}
	
	var frames = rootDoc.getElementsByTagName("iframe");
	for (let i = 0; i < frames.length; i++) {
		let frame = frames[i];
		if(frame.contentDocument &&
				(frame.contentDocument == searchDoc ||
				this._searchFrames(frame.contentDocument, searchDoc))) {
			return true;
		}
	}
	
	return false;
}

/*
 * Attempts import of a file; to be run on local files only
 */
Zotero_Browser.Tab.prototype._attemptLocalFileImport = function(doc) {
	if(doc.documentURI.match(/\.csl(\.xml|\.txt)?$/i)) {
		// read CSL string
		var csl = Zotero.File.getContentsFromURL(doc.documentURI);
		if(csl.indexOf("http://purl.org/net/xbiblio/csl") != -1) {
			// looks like a CSL; try to import
			Zotero.Styles.install(csl, doc.documentURI);
		}
	} else {
		// see if we can import this file
		var file = Components.classes["@mozilla.org/network/protocol;1?name=file"]
									.getService(Components.interfaces.nsIFileProtocolHandler)
									.getFileFromURLSpec(doc.documentURI);
		
		var me = this;
		var translate = new Zotero.Translate.Import();
		translate.setLocation(file);
		translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
		translate.getTranslators();
	}
}


Zotero_Browser.Tab.prototype.getCaptureState = function () {
	var page = this.getPageObject();
	if (!page.saveEnabled) {
		return this.CAPTURE_STATE_DISABLED;
	}
	if (page.translators && page.translators.length) {
		return this.CAPTURE_STATE_TRANSLATABLE;
	}
	return this.CAPTURE_STATE_GENERIC;
}

/*
 * returns the URL of the image representing the translator to be called on the
 * current page, or false if the page cannot be scraped
 */
Zotero_Browser.Tab.prototype.getCaptureIcon = function (hiDPI) {
	switch (this.getCaptureState()) {
	case this.CAPTURE_STATE_TRANSLATABLE:
		var itemType = this.getPageObject().translators[0].itemType;
		return (itemType === "multiple"
				? "chrome://zotero/skin/treesource-collection" + Zotero.hiDPISuffix + ".png"
				: Zotero.ItemTypes.getImageSrc(itemType));
	
	default:
		return this.getWebPageCaptureIcon(hiDPI);
	}
}

// TODO: Show icons for images, PDFs, etc.?
Zotero_Browser.Tab.prototype.getWebPageCaptureIcon = function (hiDPI) {
	return "chrome://zotero/skin/treeitem-webpage" + Zotero.hiDPISuffix + ".png";
}

Zotero_Browser.Tab.prototype.getCaptureTooltip = function() {
	switch (this.getCaptureState()) {
	case this.CAPTURE_STATE_DISABLED:
		var text = Zotero.getString('ingester.saveToZotero');
		break;
	
	case this.CAPTURE_STATE_TRANSLATABLE:
		var text = Zotero.getString('ingester.saveToZotero');
		var translator = this.getPageObject().translators[0];
		if (translator.itemType == 'multiple') {
			text += '…';
		}
		text += ' (' + translator.label + ')';
		break;
	
	// TODO: Different captions for images, PDFs, etc.?
	default:
		var text = Zotero.getString('ingester.saveToZotero')
			+ " (" + Zotero.getString('itemTypes.webpage') + ")";
	}
	
	var key = Zotero.Keys.getKeyForCommand('saveToZotero');
	if (key) {
		// Add RLE mark in RTL mode to make shortcut render the right way
		text += (Zotero.rtl ? ' \u202B' : ' ') + '('
		+ (Zotero.isMac ? '⇧⌘' : Zotero.getString('general.keys.ctrlShift'))
		+ key
		+ ')';
	}
	
	return text;
}

Zotero_Browser.Tab.prototype.getCaptureCommand = function () {
	switch (this.getCaptureState()) {
	case this.CAPTURE_STATE_DISABLED:
		return '';
	case this.CAPTURE_STATE_TRANSLATABLE:
		return '';
	default:
		return 'cmd_zotero_newItemFromCurrentPage';
	}
}


/**********CALLBACKS**********/

/*
 * called when a user is supposed to select items
 */
Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList, callback) {
	// this is kinda ugly, mozillazine made me do it! honest!
	var io = { dataIn:itemList, dataOut:null }
	var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
		"_blank","chrome,modal,centerscreen,resizable=yes", io);
	
	if(!io.dataOut) {	// user selected no items, so close the progress indicatior
		Zotero_Browser.progress.close();
	}
	
	callback(io.dataOut);
}

/*
 * called when translators are available
 */
Zotero_Browser.Tab.prototype._translatorsAvailable = Zotero.Promise.coroutine(function* (translate, translators) {
	var page = this.getPageObject();
	if (!page) return;
	page.saveEnabled = true;
	
	if(translators && translators.length) {
		//see if we should keep the previous set of translators
		if(//we already have a translator for part of this page
			page.translators && page.translators.length && page.document.location
			//and the page is still there
			&& page.document.defaultView && !page.document.defaultView.closed
			//this set of translators is not targeting the same URL as a previous set of translators,
			// because otherwise we want to use the newer set,
			// but only if it's not in a subframe of the previous set
			&& (page.document.location.href != translate.document.location.href ||
				Zotero.Utilities.Internal.isIframeOf(translate.document.defaultView, page.document.defaultView))
				//the best translator we had was of higher priority than the new set
			&& (page.translators[0].priority < translators[0].priority
				//or the priority was the same, but...
				|| (page.translators[0].priority == translators[0].priority
					//the previous set of translators targets the top frame or the current one does not either
					&& (page.document.defaultView == page.document.defaultView.top
						|| translate.document.defaultView !== page.document.defaultView.top)
			))
		) {
			Zotero.debug("Translate: a better translator was already found for this page");
			return; //keep what we had
		} else {
			this.clear(); //clear URL bar icon
			page = this.getPageObject();
			page.saveEnabled = true;
		}
		
		Zotero.debug("Translate: found translators for page\n"
			+ "Best translator: " + translators[0].label + " with priority " + translators[0].priority);
		page.translate = translate;
		page.translators = translators;
		page.document = translate.document;
	
		translate.clearHandlers("select");
		translate.setHandler("select", this._selectItems);
	} else if(translate.type != "import" && translate.document.documentURI.length > 7
			&& translate.document.documentURI.substr(0, 7) == "file://") {
		this._attemptLocalFileImport(translate.document);
	}
	
	if(!translators || !translators.length) Zotero.debug("Translate: No translators found");
	
	yield Zotero_Browser.updateStatus();
	yield Zotero_Browser.resolveDetectCallbacks();
});

Zotero_Browser.init();