/*
    ***** BEGIN LICENSE BLOCK *****
	
	Copyright (c) 2009  Zotero
	                    Center for History and New Media
						George Mason University, Fairfax, Virginia, USA
						http://zotero.org
	
	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/>.
    
    ***** END LICENSE BLOCK *****
*/

/**
 * A common installer interface used by word processor plugins to make themselves
 * installable and available in the cite preferences pane.
 */

import { Zotero } from "chrome://zotero/content/zotero.mjs";

import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";


var installationInProgress = false;

// Prefs:
// version - last successfully installed version
// lastAttemptedVersion - last version that was attempted to automatically install
// skipInstallation - if user cancels an attempt to automatically install the plugin, we do not
// 		automatically install it again until a successful manual installation

/**
 * Generic plugin installation orchestrator for word processor installers
 * @param addon {Object}
 * @param failSilently {Boolean} whether the installation should not throw errors (typically when installing automatically)
 * @param force {Boolean} force install even if the plugin version is up-to-date
 * @constructor
 */
export var ZoteroPluginInstaller = function (addon, failSilently, force) {
	this._addon = addon;
	this.failSilently = failSilently;
	this.force = force;
	
	this.prefBranch = Services.prefs.getBranch(this._addon.EXTENSION_PREF_BRANCH);

	this.prefPaneDoc = null;
	this._errorDisplayed = false;
	
	this.init();
};

ZoteroPluginInstaller.prototype = {
	init: async function () {
		this.debug('Fetching addon info');
		
		this._currentPluginVersion = (await Zotero.File.getContentsFromURLAsync(this._addon.VERSION_FILE)).trim();
		let lastInstalledVersion = this.prefBranch.getCharPref("version");
		let lastAttemptedVersion = this.prefBranch.getCharPref("lastAttemptedVersion", "");
		let lastPluginFileVersion = this._addon.LAST_INSTALLED_FILE_UPDATE;
		this.debug(`Addon info fetched. version: ${this._currentPluginVersion}, `
			+ `file version: ${lastPluginFileVersion}, installed version: ${lastInstalledVersion}`
			+ `attempted: ${lastAttemptedVersion}`);
		const newVersionSinceLastInstall = Services.vc.compare(lastInstalledVersion, lastPluginFileVersion) < 0;
		const newVersionSinceLastAttempt = Services.vc.compare(lastAttemptedVersion, lastPluginFileVersion) < 0;
		const shouldSkipInstallation = this.prefBranch.getBoolPref("skipInstallation");
		if (this.force) {
			this.debug('Force-installing');
			// Should never fail silently
			this.failSilently = false;
			return this.install();
		}
		else if (shouldSkipInstallation) {
			this.debug('Skipping automatic installation because skipInstallation is true');
			return;
		}
		if (newVersionSinceLastAttempt) {
			this.debug('New version since last attempt to install. Will display prompt upon failure.');
			this.failSilently = false;
			this.prefBranch.setCharPref("lastAttemptedVersion", this._currentPluginVersion);
			return this.install();
		}
		else if (newVersionSinceLastInstall) {
			this.debug('New version since last successful install. Attempting to install silently.');
			return this.install();
		}
		this.debug('No new updates');
	},
	
	install: async function () {
		if (installationInProgress) {
			this.debug('Extension installation is already in progress');
			return;
		}
		installationInProgress = true;
		try {
			if (!this._addon.DISABLE_PROGRESS_WINDOW && !this.failSilently) {
				this._progressWindow = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
					.getService(Components.interfaces.nsIWindowWatcher)
					.openWindow(null, "chrome://zotero/content/progressWindow.xhtml", '',
						"chrome,resizable=no,close=no,centerscreen", null);
				this._progressWindow.addEventListener("load", () => this._firstRunListener(), false);
			}
			else {
				let result = this._addon.install(this);
				if (result.then) await result;
			}
		}
		catch (e) {
			Zotero.logError(e);
			this.error(e);
		}
		finally {
			installationInProgress = false;
		}
	},
	
	isInstalled: function () {
		return !!this.prefBranch.getCharPref("version");
	},
	
	setProgressWindowLabel: function (value) {
		if (this._progressWindow) this._progressWindowLabel.value = value;
	},
	
	closeProgressWindow: function () {
		if (this._progressWindow) this._progressWindow.close();
	},
	
	success: function () {
		this.debug(`Installation was successful. Version ${this._currentPluginVersion}`);
		installationInProgress = false;
		this.closeProgressWindow();
		this.prefBranch.setCharPref("version", this._currentPluginVersion);
		this.updateInstallStatus();
		this.prefBranch.setBoolPref("skipInstallation", false);
		if (this.force && !this._addon.DISABLE_PROGRESS_WINDOW) {
			setTimeout(() => {
				Services.prompt.alert(
					null,
					this._addon.EXTENSION_STRING,
					Zotero.getString("zotero.preferences.wordProcessors.installationSuccess")
				);
			});
		}
	},
	
	error: async function (error, notFailure) {
		this.debug(`Installation failed with error ${error}`);
		installationInProgress = false;
		this.closeProgressWindow();
		if (notFailure) {
			this.prefBranch.setCharPref("version", this._currentPluginVersion);
			this.updateInstallStatus();
		}
		if (this.failSilently) {
			this.debug('Not displaying error because failSilently is true');
			return;
		}
		if (this._errorDisplayed) return;
		this._errorDisplayed = true;
		let errorMessage = await Zotero.getString("zotero.preferences.wordProcessors.installationError", [
			this._addon.APP,
			Zotero.appName
		]);
		if (error) {
			errorMessage += "\n\n" + error;
		}
		setTimeout(() => {
			var ps = Services.prompt;
			var buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) + ps.BUTTON_POS_0_DEFAULT
				+ (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL);
			var result = ps.confirmEx(null,
				this._addon.EXTENSION_STRING,
				errorMessage,
				buttonFlags,
				Zotero.getString('general-view-troubleshooting-instructions'),
				null, null, null, {});
			if (result == 0) {
				Zotero.launchURL("https://www.zotero.org/support/kb/word_processor_plugin_installation_error");
			}
		});
	},
	
	cancelled: function (dontSkipInstallation) {
		this.debug('Installation cancelled');
		installationInProgress = false;
		this.closeProgressWindow();
		if (!this.force && !dontSkipInstallation) this.prefBranch.setBoolPref("skipInstallation", true);
	},

	showPreferences: function (document) {
		this.prefPaneDoc = document;
		var isInstalled = this.isInstalled(),
			groupbox = document.createXULElement("groupbox");
		groupbox.id = this._addon.EXTENSION_DIR;

		var label = document.createXULElement("label");
		var h2 = document.createElement('h2');
		h2.textContent = this._addon.APP;
		label.appendChild(h2);
		groupbox.appendChild(label);

		var description = document.createXULElement("description");
		description.appendChild(document.createTextNode(isInstalled
			? Zotero.getString('zotero.preferences.wordProcessors.installed', this._addon.APP)
			: Zotero.getString('zotero.preferences.wordProcessors.notInstalled', this._addon.APP)));
		groupbox.appendChild(description);

		var hbox = document.createXULElement("hbox");
		hbox.setAttribute("pack", "center");
		var button = document.createXULElement("button"),
			addon = this._addon;
		button.setAttribute("label", isInstalled
			? Zotero.getString('zotero.preferences.wordProcessors.reinstall', this._addon.APP)
			: Zotero.getString('zotero.preferences.wordProcessors.install', this._addon.APP));
			
		button.addEventListener("command", () => {
			this.debug('Install button pressed');
			try {
				var zpi = new ZoteroPluginInstaller(addon, false, true);
				zpi.showPreferences(document);
			}
			catch (e) {
				Zotero.logError(e);
			}
		}, false);
		hbox.appendChild(button);
		groupbox.appendChild(hbox);

		var container = document.getElementById("wordProcessorInstallers"),
			old = document.getElementById(this._addon.EXTENSION_DIR);
		if (old) {
			container.replaceChild(groupbox, old);
		}
		else {
			container.appendChild(groupbox);
		}
	},
	
	updateInstallStatus: function () {
		if (!this.prefPaneDoc) return;
		var isInstalled = this.isInstalled();
		var description = this.prefPaneDoc.querySelector(`#${this._addon.EXTENSION_DIR} description`);
		description.replaceChild(this.prefPaneDoc.createTextNode(isInstalled
			? Zotero.getString('zotero.preferences.wordProcessors.installed', this._addon.APP)
			: Zotero.getString('zotero.preferences.wordProcessors.notInstalled', this._addon.APP)
		), description.childNodes[0]);
		var button = this.prefPaneDoc.querySelector(`#${this._addon.EXTENSION_DIR} button`);
		button.setAttribute("label", isInstalled
			? Zotero.getString('zotero.preferences.wordProcessors.reinstall', this._addon.APP)
			: Zotero.getString('zotero.preferences.wordProcessors.install', this._addon.APP));
	},
	
	_firstRunListener: async function () {
		this._progressWindowLabel = this._progressWindow.document.getElementById("progress-label");
		this._progressWindowLabel.value = Zotero.getString('zotero.preferences.wordProcessors.installing', this._addon.EXTENSION_STRING);
		this._progressWindow.sizeToContent();
		await Zotero.Promise.delay(100);
		this._progressWindow.focus();
		await Zotero.Promise.delay(500);
		this._progressWindow.focus();
		try {
			await this._addon.install(this);
		}
		catch (e) {
			Zotero.logError(e);
			this.error(e);
		}
	},
	
	debug: function (message) {
		Zotero.debug(`PluginInstaller ${this._addon.APP}: ${message}`);
	}
};
