const Engine = (function () {
	const preloader = new Preloader();

	let loadPromise = null;
	let loadPath = '';
	let initPromise = null;

	function load(basePath) {
		if (loadPromise == null) {
			loadPath = basePath;
			loadPromise = preloader.loadPromise(`${loadPath}.wasm`);
			requestAnimationFrame(preloader.animateProgress);
		}
		return loadPromise;
	}

	function unload() {
		loadPromise = null;
	}

	/** @constructor */
	function Engine(opts) { // eslint-disable-line no-shadow
		this.config = new EngineConfig(opts);
		this.rtenv = null;
	}

	Engine.prototype.init = /** @param {string=} basePath */ function (basePath) {
		if (initPromise) {
			return initPromise;
		}
		if (loadPromise == null) {
			if (!basePath) {
				initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.'));
				return initPromise;
			}
			load(basePath);
		}
		preloader.setProgressFunc(this.config.onProgress);
		let config = this.config.getModuleConfig(loadPath, loadPromise);
		const me = this;
		initPromise = new Promise(function (resolve, reject) {
			Godot(config).then(function (module) {
				module['initFS'](me.config.persistentPaths).then(function (fs_err) {
					me.rtenv = module;
					if (me.config.unloadAfterInit) {
						unload();
					}
					resolve();
					config = null;
				});
			});
		});
		return initPromise;
	};

	/** @type {function(string, string):Object} */
	Engine.prototype.preloadFile = function (file, path) {
		return preloader.preload(file, path);
	};

	/** @type {function(...string):Object} */
	Engine.prototype.start = function (override) {
		this.config.update(override);
		const me = this;
		return me.init().then(function () {
			if (!me.rtenv) {
				return Promise.reject(new Error('The engine must be initialized before it can be started'));
			}

			let config = {};
			try {
				config = me.config.getGodotConfig(function () {
					me.rtenv = null;
				});
			} catch (e) {
				return Promise.reject(e);
			}
			// Godot configuration.
			me.rtenv['initConfig'](config);

			// Preload GDNative libraries.
			const libs = [];
			me.config.gdnativeLibs.forEach(function (lib) {
				libs.push(me.rtenv['loadDynamicLibrary'](lib, { 'loadAsync': true }));
			});
			return Promise.all(libs).then(function () {
				return new Promise(function (resolve, reject) {
					preloader.preloadedFiles.forEach(function (file) {
						me.rtenv['copyToFS'](file.path, file.buffer);
					});
					preloader.preloadedFiles.length = 0; // Clear memory
					me.rtenv['callMain'](me.config.args);
					initPromise = null;
					resolve();
				});
			});
		});
	};

	Engine.prototype.startGame = function (override) {
		this.config.update(override);
		// Add main-pack argument.
		const exe = this.config.executable;
		const pack = this.config.mainPack || `${exe}.pck`;
		this.config.args = ['--main-pack', pack].concat(this.config.args);
		// Start and init with execName as loadPath if not inited.
		const me = this;
		return Promise.all([
			this.init(exe),
			this.preloadFile(pack, pack),
		]).then(function () {
			return me.start.apply(me);
		});
	};

	Engine.prototype.copyToFS = function (path, buffer) {
		if (this.rtenv == null) {
			throw new Error('Engine must be inited before copying files');
		}
		this.rtenv['copyToFS'](path, buffer);
	};

	Engine.prototype.requestQuit = function () {
		if (this.rtenv) {
			this.rtenv['request_quit']();
		}
	};

	// Closure compiler exported engine methods.
	/** @export */
	Engine['isWebGLAvailable'] = Utils.isWebGLAvailable;
	Engine['load'] = load;
	Engine['unload'] = unload;
	Engine.prototype['init'] = Engine.prototype.init;
	Engine.prototype['preloadFile'] = Engine.prototype.preloadFile;
	Engine.prototype['start'] = Engine.prototype.start;
	Engine.prototype['startGame'] = Engine.prototype.startGame;
	Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
	Engine.prototype['requestQuit'] = Engine.prototype.requestQuit;
	return Engine;
}());
if (typeof window !== 'undefined') {
	window['Engine'] = Engine;
}