const electron = require('electron');
const UIRegistry = require('./registry.js');
const UIEventsHandler = require('./events.js');

/**
 * Configuration handler.
 */
class UIConfig {
	/**
	 * Class constructor.
	 */
	constructor() {
		// get config directory:
		// - C:\Users\{user}\AppData\Roaming\VRE Printer (win)
		let configDir = (electron.app || electron.remote.app).getPath('userData');
		// remove trailing separator, if any
		configDir = configDir.replace(/[\/\\\\]$/, '');

		this.path     = require('path');
		this.handler  = require('fs');
		this.file     = [configDir, 'config.json'].join(this.path.sep);
		this.reg      = new UIRegistry();
		this.isLoaded = false;
		this.isSaved  = true;

		this.loadEvents = new UIEventsHandler();
		this.saveEvents = new UIEventsHandler();

		this.load();
	}

	/**
	 * Returns the path halper.
	 *
	 * @return 	object
	 */
	getPath() {
		return this.path;
	}

	/**
	 * Returns the path of the file that stores the config.
	 *
	 * @return 	string
	 */
	getFile() {
		return this.file;
	}

	/**
	 * Loads the configuration.
	 *
	 * @return 	void
	 */
	load() {
		let data = '';

		// create stream reader
		const stream = this.handler.createReadStream(this.file, 'utf8');

		// define callback to use to initialize the configuration
		// once the stream finished to read the contents
		let onInit = () => {
			if (!data) {
				data = '{}';
			}

			try {
				// attempt to decode the JSON configuration
				data = JSON.parse(data);
			} catch (err) {
				// malformed JSON, use an empty object to avoid breaking the flow
				console.error(err);
				data = {};
			}
			
			// init configuration
			this.reg.init(data);

			// notify the subscribers
			this.loadEvents.execute();

			// mark as loaded
			this.isLoaded = true;
		};

		// read data
		stream.on('data', (chunk) => {
		    data += chunk;
		});

		// init configuration on finish/error
		stream.on('end', onInit)
		stream.on('error', onInit);
	}

	/**
	 * Saves the configuration.
	 *
	 * @return 	void
	 */
	save() {
		this.isSaved = false;

		// stringify configuration
		var data = JSON.stringify(this.reg.getObject());

		// save into file
		this.handler.writeFile(this.file, data, (err) => {
			if (err) {
				// an error occurred, log it
				console.error('An error ocurred while saving the file', err);
			}

			// mark as saved
			this.isSaved = true;
			// notify the subscribers
			this.saveEvents.execute();
		});
	}

	/**
	 * Configuration getter.
	 *
	 * @param 	string  key  The setting key.
	 * @param 	mixed   def  The default value to return when missing.
	 *
	 * @return 	mixed   The setting value, or the default one.
	 */
	get(key, def) {
		return this.reg.get(key, def);
	}

	/**
	 * Configuration setter.
	 *
	 * @param 	string   key  The setting key.
	 * @param 	mixed    val  The setting value.
	 *
	 * @return 	self     This object to support chaining.
	 */
	set(key, val, skipsave) {
		// update internal registry
		this.reg.set(key, val);

		return this;
	}

	/**
	 * Listeners can subscriber here to be notified when
	 * the configuration is ready for the usage.
	 *
	 * @return 	void
	 */
	onLoad(func) {
		// register subscriber
		this.loadEvents.add(func);

		// check whether the configuration is already loaded
		if (this.isLoaded) {
			// immediately dispatch
			this.loadEvents.execute();
		}
	}

	/**
	 * Listeners can subscriber here to be notified when
	 * the configuration is saved.
	 *
	 * @return 	void
	 */
	onSave(func) {
		// register subscriber
		this.saveEvents.add(func);

		// check whether the configuration has been saved
		if (this.isSaved) {
			// immediately dispatch
			this.saveEvents.execute();
		}
	}
}

// export for external usage
module.exports = UIConfig;
