const electron = require('electron');
const {app, BrowserWindow, Menu, MenuItem, autoUpdater, dialog, Notification} = require('electron');

const UIConfig = require('./assets/js/system/config.js');
const UIBundle = require('./assets/js/language/bundle.js');
const UILicense = require('./assets/js/system/license.js');
const UIPrintFormat = require('./assets/js/print/format.js');
const UINetRequest = require('./assets/js/net/request.js');
const UIManager = require('./assets/js/system/manager.js');

// set up application user model for Windows platform
app.setAppUserModelId('com.squirrel.vre-printer.VREPrinter');

/**
 * Make sure we are not running the installation via Squirrel,
 * otherwise we would fall in a runtime error due to a double
 * running process.
 *
 * @link https://github.com/electron/electron/issues/7155
 */
if(require('electron-squirrel-startup'))
{
	let promise;

	// check what kind of action has been requested
	switch (process.argv[1]) {
		// look for install
		case '--squirrel-install':
			promise = UIManager.install();
			break;

		// runs after completing the update
		case '--squirrel-updated':
			promise = UIManager.updated();
			break;

		// look for uninstall
		case '--squirrel-uninstall':
			promise = UIManager.uninstall();
			break;

		// runs before performing an update
		case '--squirrel-obsolete':
			promise = UIManager.preflight();
			break;

		// handle unsupported request
		default:
			promise = UIManager.unsupported(process.argv[1]);
	}

	// handle fetched promise
	promise.then(() => {
		// the action went fine
	}).catch((error) => {
		// an error occurred, log it
		console.error(error);
	}).finally(() => {
		// finally quit the application
		app.quit();
	});

	return true;
}

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
let preferences;
let clogWin;
let printerWin = {};

// get configuration handler
let config = new UIConfig();

// get translator
let bundle = UIBundle.getInstance();

// get license instance
let license;

// keep a reference of the changelog
let changelogManifest = null;

config.onLoad(() => {
	// create license instance
	license = new UILicense(config);
});

// build the deployment URL used by the autoupdater system
const deploymentUrl = 'https://vikwp.com/api/?task=electron.squirrel'
	+ '&sku=vreprint'
	+ '&platform=' + process.platform
	+ '&arch=' + process.arch;

/**
 * Helper constant used to quickly determine whether
 * we are in a development environment.
 *
 * @var boolean
 */
const isDev = require('electron-is-dev');

/**
 * Main window builder.
 *
 * @return 	void
 */
function createMainWindow() {
	// create the browser window
	win = new BrowserWindow({
		width: 960,
		height: 640,
		backgroundColor: '#f5f5f5',
		title: bundle.__('common.logo'),
		show: false,
		darkTheme: config.get('theme') == 'dark-mode',
		webPreferences: {
			nodeIntegration: true,
			enableRemoteModule: true,
			contextIsolation: false,
		},
	});

	// and load the index.html of the app.
	win.loadURL(`file://${__dirname}/index.html`);
	win.setMinimumSize(800, 600);

	// decomment the line below to automatically
	// open the DevTools on load
	// win.webContents.openDevTools();

	win.on('ready-to-show', () => {
		win.webContents.send('set-theme-mode', config.get('theme'));
		
		win.show();

		// check whether the TRIAL is expired
		license.isTrialExpired().then(() => {
			// immediately hide the window
			win.hide();

			// auto-close the window with a short delay
			// to prevent loading errors
			setTimeout(() => {
				win.close();
			}, 1000);

			// auto-open the preferences window instead
			createPreferencesWindow();
		}).catch(() => {
			license.isPro().then(() => {
				// PRO version
			}).catch(() => {
				// trial still active
				let trial = {
					days: license.getRemainingDays(),
					activation: license.getActivationDate().getTime(),
				};

				// notify window
				win.webContents.send('trial-remaining-days', trial);
			});
		});
	});

	// when main window is created : disable reopen
	setReopenMenuItemStatus(false);

	win.on('closed', () => {
		// Dereference the window object, usually you would store windows
		// in an array if your app supports multi windows, this is the time
		// when you should delete the corresponding element.
		win = null;

		// when main window is closed : enable reopen	
		setReopenMenuItemStatus(true);
	});
}

/**
 * Preferences window builder.
 *
 * @return 	void
 */
function createPreferencesWindow() {
	if (preferences) {
		// preferences already visible, focus it
		preferences.focus();
		return;
	}

	// create the browser window
	preferences = new BrowserWindow({
		width: 800,
		height: 600,
		backgroundColor: '#f5f5f5',
		title: bundle.__('config.title'),
		show: false,
		darkMode: config.get('theme') == 'dark-mode',
		webPreferences: {
			nodeIntegration: true,
			enableRemoteModule: true,
			contextIsolation: false,
		},
	});

	// hide application menu from secondary frames (Windows)
	preferences.setMenuBarVisibility(false);

	// and load the index.html of the app.
	preferences.loadURL(`file://${__dirname}/views/preferences.html`);
	preferences.setMinimumSize(preferences.getSize()[0], preferences.getSize()[1]);

	preferences.on('ready-to-show', () => {
		preferences.webContents.send('set-theme-mode', config.get('theme'));

		// retrieve supported printers
		let supportedPrinters = preferences.webContents.getPrinters();
		// pass them to the preferences window
		preferences.webContents.send('supported-printers', supportedPrinters);

		// inform the window whether the platform supports desktop notifications
		preferences.webContents.send('supported-notifications', Notification.isSupported());

		// check whether the TRIAL is expired
		license.isTrialExpired().then(() => {
			// notify window that TRIAL is expired
			preferences.webContents.send('trial-expired');
		}).catch(() => {
			// TRIAL still active
			license.isPro().then(() => {
				// is a PRO
				preferences.webContents.send('license-validated', {result: true});
			}).catch(() => {
				// not a PRO
			});
		});

		preferences.show();
	});

	preferences.on('closed', () => {
		// Dereference the window object, usually you would store windows
		// in an array if your app supports multi windows, this is the time
		// when you should delete the corresponding element.
		preferences = null;
	});
}

/**
 * Changelog window builder.
 *
 * @return 	void
 */
function createChangelogWindow() {
	if (clogWin) {
		// changelog already visible, focus it
		clogWin.focus();
		return;
	}
	
	// create the browser window
	clogWin = new BrowserWindow({
		width: 800,
		height: 600,
		backgroundColor: '#f5f5f5',
		title: bundle.__('appmenuitem.changelog'),
		show: false,
		darkMode: config.get('theme') == 'dark-mode',
		webPreferences: {
			nodeIntegration: true,
			enableRemoteModule: true,
			contextIsolation: false,
		},
	});

	// hide application menu from secondary frames (Windows)
	clogWin.setMenuBarVisibility(false);

	// and load the index.html of the app.
	clogWin.loadURL(`file://${__dirname}/views/changelog.html`);
	clogWin.setMinimumSize(clogWin.getSize()[0], clogWin.getSize()[1]);

	clogWin.on('ready-to-show', () => {
		clogWin.webContents.send('set-theme-mode', config.get('theme'));

		clogWin.show();

		// load changelog from server
		loadChangelog().then((data) => {
			// pass changelog to window
			clogWin.webContents.send('changelog', data);
		}).catch((error) => {
			// auto-close window
			clogWin.close();

			// unable to load the changelog, prompt alert
			dialog.showMessageBox({
				type: 'error',
				buttons: [
					bundle.__('changelog.error.close'),
				],
				title: bundle.__('changelog.error.title'),
				message: bundle.__('changelog.error.message'),
				detail: error.message,
			});
		});
	});

	clogWin.on('closed', () => {
		// Dereference the window object, usually you would store windows
		// in an array if your app supports multi windows, this is the time
		// when you should delete the corresponding element.
		clogWin = null;
	});
}

/**
 * Helper function used to check whether the application menu
 * supported the given item key.
 *
 * @param 	string 	name  The key of the menu item.
 *
 * @return 	mixed   The item object when found, null otherwise.
 */
function getApplicationMenuItem(name) {
	const menu = Menu.getApplicationMenu();

	var found = null;

	if (!menu) {
		return found;
	}

	menu.items.forEach((item) => {
		if (item.submenu) {
			item.submenu.items.forEach((child) => {
				if (child.key === name) {
					// register item found and exit from foreach
					found = child;
					return false;
				}
			});
		}
	});

	return found;
}

/**
 * Helper function used to set the reopen state of a window.
 *
 * @param 	boolean  status  The menu item state.
 *
 * @return 	void
 */
function setReopenMenuItemStatus(status) {
	let reopenMenuItem = getApplicationMenuItem('reopenMenuItem');
	
	if (reopenMenuItem) {
		reopenMenuItem.enabled = status;
	}
}

/**
 * Helper function used to safely terminate the application
 * without breaking any pending process.
 *
 * @return 	void
 */
function quitApplication() {
	// wait for saving completion
	config.onSave(() => {
		// terminate app
		app.quit();
	});
}

/**
 * Set up the application menu.
 *
 * @return 	void
 */
function buildApplicationMenu() {
	const searchmode    = parseInt(config.get('searchmode', 1));
	const casesensitive = parseInt(config.get('casesensitive', 1));

	// File menu item
	const fileSubmenu = {
		label: bundle.__('appmenu.file'),
		submenu: [
			// reload
			{
				label: bundle.__('appmenuitem.reload'),
				accelerator: 'CmdOrCtrl+R',
				click: (item, focusedWindow) => {
					if (win !== null) {
						win.webContents.send('download-orders');
					}
				},
			},
			// add separator
			{
				type: 'separator',
			},
			// flag all as read
			{
				label: bundle.__('appmenuitem.readall'),
				click: (item, focusedWindow) => {
					if (win !== null) {
						win.webContents.send('orders-readall');
					}
				},
			},
			// add separator
			{
				type: 'separator'
			},
			// print
			{
				label: bundle.__('appmenuitem.print'),
				accelerator: 'CmdOrCtrl+P',
				click: (item, focusedWindow) => {
					if (win !== null) {
						win.webContents.send('print-active-order');
					}
				},
			},
		],
	};

	// Edit menu item
	const editSubmenu = {
		label: bundle.__('appmenu.edit'),
		submenu: [
			// undo
			{
				label: bundle.__('appmenuitem.undo'),
				accelerator: 'CmdOrCtrl+Z',
				role: 'undo',
			},
			// redo
			{
				label: bundle.__('appmenuitem.redo'),
				accelerator: 'Shift+CmdOrCtrl+Z',
				role: 'redo',
			},
			// add separator
			{
				type: 'separator',
			},
			// cut
			{
				label: bundle.__('appmenuitem.cut'),
				accelerator: 'CmdOrCtrl+X',
				role: 'cut',
			},
			// copy
			{
				label: bundle.__('appmenuitem.copy'),
				accelerator: 'CmdOrCtrl+C',
				role: 'copy',
			},
			// paste
			{
				label: bundle.__('appmenuitem.paste'),
				accelerator: 'CmdOrCtrl+V',
				role: 'paste',
			},
			// select all
			{
				label: bundle.__('appmenuitem.selectall'),
				accelerator: 'CmdOrCtrl+A',
				role: 'selectall',
			},
		]
	};

	// View menu item
	const viewSubmenu = {
		label: bundle.__('appmenu.view'),
		submenu: [
			// toggle full screen
			{
				label: bundle.__('appmenuitem.togglefullscreen'),
				accelerator: (() => {
					if (process.platform === 'darwin') {
						return 'Ctrl+Command+F'
					} else {
						return 'F11'
					}
				})(),
				click: (item, focusedWindow) => {
					if (focusedWindow) {
						focusedWindow.setFullScreen(!focusedWindow.isFullScreen())
					}
				},
			},
		]
	};

	if (isDev) {
		// add "toggle developer tools" only in case of dev mode
		viewSubmenu.submenu.push({
			label: 'Toggle Developer Tools',
			accelerator: (() => {
				if (process.platform === 'darwin') {
					return 'Alt+Command+I'
				} else {
					return 'Ctrl+Shift+I'
				}
			})(),
			click: (item, focusedWindow) => {
				if (focusedWindow) {
					focusedWindow.toggleDevTools()
				}
			},
		});
	}
		
	// Window menu item
	const windowSubmenu = {
		label: bundle.__('appmenu.window'),
		role: 'window',
		submenu: [
			// minimize
			{
				label: bundle.__('appmenuitem.minimize'),
				accelerator: 'CmdOrCtrl+M',
				role: 'minimize',
			},
			// close
			{
				label: bundle.__('appmenuitem.close'),
				accelerator: 'CmdOrCtrl+W',
				role: 'close',
			},
			// add separator
			{
				type: 'separator',
			},
			// open closed window
			{
				label: bundle.__('appmenuitem.reopenwindow'),
				accelerator: 'CmdOrCtrl+Shift+T',
				enabled: false,
				key: 'reopenMenuItem',
				click: () => {
					app.emit('activate');
				},
			},
		]
	};

	// Help menu item
	const helpSubmenu = {
		label: bundle.__('appmenu.help'),
		role: 'help',
		submenu: [
			// documentation
			{
				label: bundle.__('appmenuitem.documentation'),
				click: () => {
					let url, status = config.get('connectionstatus', {});

					if (status.platform === 'wordpress') {
						// use VikWP documentation URL
						url = 'https://vikwp.com/support/knowledge-base/vik-restaurants/vre-printer';
					} else {
						// use E4J documentation URL
						url = 'https://extensionsforjoomla.com/answers-area/knowledge-base/vik-restaurants/vre-printer';
					}

					electron.shell.openExternal(url);
				},
			},
			// add separator
			{
				type: 'separator',
			},
			// learn more
			{
				label: bundle.__('appmenuitem.learnmore'),
				click: () => {
					let url, status = config.get('connectionstatus', {});

					if (status.platform === 'wordpress') {
						// use VikWP product page URL
						url = 'https://vikwp.com/plugin/vikrestaurants-auto-print';
					} else {
						// use E4J product page URL
						url = 'https://extensionsforjoomla.com/components-modules/vikrestaurants-auto-print';
					}

					electron.shell.openExternal(url);
				},
			},
		],
	};

	// create list of submenus
	const template = [
		fileSubmenu,
		editSubmenu,
		viewSubmenu,
		windowSubmenu,
		helpSubmenu,
	];

	// look for macOS
	if (process.platform === 'darwin') {
		const name = electron.app.getName();

		// prepend system menu on MAC
		template.unshift({
			label: name,
			submenu: [
				// about
				{
					label: bundle.__('appmenuitem.aboutname', name),
					role: 'about',
				},
				// check updates
				{
					label: bundle.__('appmenuitem.checkupdates'),
					click: () => {
						// manually check for any updates
						autoUpdater.checkForUpdates();
					},
				},
				// changelog
				{
					label: bundle.__('appmenuitem.changelog'),
					click: () => {
						// create changelog window
						createChangelogWindow();
					},
				},
				// add separator
				{
					type: 'separator',
				},
				// preferences
				{
					label: bundle.__('appmenuitem.preferences'),
					accelerator: 'Cmd+,',
					click: () => {
						createPreferencesWindow();
					},
				},
				// add separator
				{
					type: 'separator',
				},
				// services
				{
					label: bundle.__('appmenuitem.services'),
					role: 'services',
					submenu: [],
				},
				// add separator
				{
					type: 'separator',
				},
				// hide
				{
					label: bundle.__('appmenuitem.hidename', name),
					accelerator: 'Cmd+H',
					role: 'hide',
				},
				// hide others
				{
					label: bundle.__('appmenuitem.hideothers'),
					accelerator: 'Cmd+Alt+H',
					role: 'hideothers',
				},
				// show all
				{
					label: bundle.__('appmenuitem.showall'),
					role: 'unhide',
				},
				// add separator
				{
					type: 'separator',
				},
				// quit
				{
					label: bundle.__('appmenuitem.quit'),
					accelerator: 'Cmd+Q',
					click: () => {
						quitApplication();
					},
				},
			],
		});

		// add separator to window menu
		windowSubmenu.submenu.push({
			type: 'separator',
		});

		// add "hide others" to window menu
		windowSubmenu.submenu.push({
			label: bundle.__('appmenuitem.hideothers'),
			role: 'front',
		});
	}
	// look for Windows
	else if (process.platform === 'win32') {
		// add preferences fo file menu
		fileSubmenu.submenu.push({
			label: bundle.__('appmenuitem.preferences'),
			accelerator: 'Ctrl+Shift+P',
			click: () => {
				createPreferencesWindow();
			},
		});

		// remove learn more from help menu
		learnMore = helpSubmenu.submenu.pop();

		// include check updates within help menu
		helpSubmenu.submenu.push({
			label: bundle.__('appmenuitem.checkupdates'),
			click: () => {
				// manually check for any updates
				autoUpdater.checkForUpdates();
			},
		});
		
		// include changelog within help menu
		helpSubmenu.submenu.push({
			label: bundle.__('appmenuitem.changelog'),
			click: () => {
				// create changelog window
				createChangelogWindow();
			},
		});

		// add separator
		helpSubmenu.submenu.push({
			type: 'separator',
		});
			
		// include about within help menu
		helpSubmenu.submenu.push({
			label: bundle.__('appmenuitem.aboutname', app.getName()),
			role: 'about',
		});
		
		// re-add learn more at the end of the list
		helpSubmenu.submenu.push(learnMore);
	}

	const menu = Menu.buildFromTemplate(template);
	Menu.setApplicationMenu(menu);
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
	// set up i18n
	if (UIBundle.isSupported(app.getLocale())) {
		bundle.setLocale(app.getLocale());
	}

	// register current locale in a global variable
	global.locale = bundle.getLocale();

	// init application window after finishing to load
	// the global configuration
	config.onLoad(() => {
		createMainWindow();
		buildApplicationMenu();

		setTimeout(() => {
			setupAutoUpdates();
		}, 2000);
	});
});

app.on('browser-window-created', () => {
	// do not decomment code below to prevent false status
	// of REOPEN menu item
	// setReopenMenuItemStatus(false);
});

// quit when all windows are closed
app.on('window-all-closed', () => {
	// when all windows are closed, enable reopen
	setReopenMenuItemStatus(true);

	// On macOS it is common for applications and their menu bar
	// to stay active until the user quits explicitly with Cmd + Q
	if (process.platform !== 'darwin') {
		quitApplication();
	}
});

app.on('activate', () => {
	// On macOS it's common to re-create a window in the app when the
	// dock icon is clicked and there are no other windows open.
	if (win === null) {
		createMainWindow();
	}
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

const ipc = require('electron').ipcMain;

/**
 * Implement callback to create a context menu
 * every time a subscriber requests it.
 *
 * @param 	Event  event   The triggered event.
 * @param 	mixed  params  The given parameters.
 *
 * @return 	void
 */
ipc.on('show-context-menu', (event, params) => {
	// create context menu instance
	const menu = new Menu();

	// append menu item to access the order details
	menu.append(new MenuItem({
		label: bundle.__('contextmenu.open'),
		click: () => {
			if (win !== null) {
				win.webContents.send('open-order', params);
			}
		},
	}));

	// append menu item to reach VikRestaurants for edit
	menu.append(new MenuItem({
		label: bundle.__('contextmenu.edit'),
		click: () => {
			if (win !== null) {
				win.webContents.send('edit-order', params);
			}
		},
	}));

	// add separator
	menu.append(new MenuItem({ type: 'separator' }));

	// append menu item to print the order
	menu.append(new MenuItem({
		label: bundle.__('contextmenu.print'),
		click: () => {
			if (win !== null) {
				win.webContents.send('print-order-context', params);
			}
		},
	}));

	// add separator
	menu.append(new MenuItem({ type: 'separator' }));

	// append menu item to remove the order
	menu.append(new MenuItem({
		label: bundle.__('contextmenu.remove'),
		click: () => {
			if (win !== null) {
				win.webContents.send('remove-order', params);
			}
		},
	}));

	// create popup
	menu.popup(win);
});

/**
 * Implement callback used to notify the subscribers
 * every time the application config gets updated.
 *
 * @param 	Event  event  The triggered event.
 * @param 	mixed  arg    The given parameters.
 *
 * @return 	void
 */
ipc.on('config-updated', (event, arg) => {
	// update current config instance
	config.set(arg.key, arg.val);

	if (!arg.skipsave) {
		// commit changes
		config.save();
	}

	// notify subscribers
	notifyConfigListeners(arg);
});

/**
 * Helper function used to notify the subscribers
 * every time the config gets updated.
 *
 * @param 	object  arg  An object holding the updated setting.
 *
 * @return 	void
 */
function notifyConfigListeners(arg) {
	if (win !== null) {
		win.webContents.send('config-updated', arg);
	}
}

/**
 * Implement callback to create the printer frame.
 *
 * @param 	Event  event  The triggered event.
 * @param 	mixed  arg    The given parameters.
 *
 * @return 	void
 */
ipc.on('print-full-page', (event, arg) => {
	createPrinterFrame(arg);	
});

/**
 * Printer window builder.
 *
 * @param 	object 	details  An object holding the order details.
 *
 * @return 	void
 */
function createPrinterFrame(details) {
	// create the browser window (keep it hidden)
	printerWin[details.id] = new BrowserWindow({
		width: 800,
		height: 600,
		backgroundColor: '#f5f5f5',
		frame: false,
		show: false,
		webPreferences: {
			nodeIntegration: true,
			enableRemoteModule: true,
			contextIsolation: false,
		},
	});

	// hide application menu from secondary frames (Windows)
	printerWin[details.id].setMenuBarVisibility(false);

	// define default view name to load print preview
	let viewName = 'printer';

	if (details.id === 'test') {
		// requested print test page, use a different view
		viewName = 'testpage';
	}

	// load HTML page
	printerWin[details.id].loadURL(`file://${__dirname}/views/${viewName}.html`);

	// trigger event to inform the subscribers that the order details
	// have been received by the window
	printerWin[details.id].on('ready-to-show', () => {
		printerWin[details.id].webContents.send('receive-details', details);
	});

	printerWin[details.id].on('closed', () => {
		// Dereference the window object, usually you would store windows
		// in an array if your app supports multi windows, this is the time
		// when you should delete the corresponding element.
		printerWin[details.id] = null;
		delete printerWin[details.id];
	});
}

/**
 * Implement callback to start printing.
 *
 * @param 	Event  event  The triggered event.
 * @param 	mixed  arg    The given parameters.
 *
 * @return 	void
 *
 * @link 	https://www.electronjs.org/docs/api/web-contents#contentsprintoptions-callback
 */
ipc.on('printer-ready', (event, arg) => {
	if (win === null) {
		return;
	}

	// register printing status
	win.webContents.send('register-status', bundle.__('status.printing'));

	// check whether we should print background colors
	var printbg = parseInt(config.get('printbg', 0)) == 1 ? true : false;

	// set up printer options
	const printerOptions = {
		// don't ask user for print settings
		silent: true,
		// prints the background color and image of the web page
		printBackground: printbg,
		// set whether the printed web page will be in color or grayscale
		color: printbg,
		// set the printer device name to use
		deviceName: config.get('printerdevice'),
	};

	// retrieve page format and size from configuration
	let pageFormat = new UIPrintFormat(
		config.get('pageformat', 'A4'),
		config.get('pagesize', {})
	);

	// Specify page size of the printed document.
	// Can be A3, A4, A5, Legal, Letter, Tabloid
	// or an Object containing width and height,
	// expressed in microns.
	if (pageFormat.ISO !== 'custom') {
		// use standard format
		printerOptions.pageSize = pageFormat.ISO;
	} else {
		// use specified size
		printerOptions.pageSize = {
			width:  pageFormat.getPrintWidth() * 1000,
			height: pageFormat.getPrintHeight() * 1000,
		};
	}

	// retrieve page margins type from configuration
	let marginType = config.get('pagemargintype');

	if (marginType) {
		// Can be default, none, printableArea, or custom.
		// If custom is chosen, you will also need to specify
		// top, bottom, left, and right (in pixel).
		printerOptions.margins = {
			marginType: marginType,
		};

		if (marginType == 'custom') {
			let margins = config.get('pagemargins', {});

			// define custom margins
			printerOptions.margins.top    = margins.top;
			printerOptions.margins.bottom = margins.bottom;
			printerOptions.margins.left   = margins.left;
			printerOptions.margins.right  = margins.right;
		}
	}

	// run print
	printerWin[arg.id].webContents.print(printerOptions, (success, failure) => {
		let msg;

		if (success) {
			// use successful message
			msg = bundle.__('status.printed');
		} else {
			// use error message
			msg = bundle.__('error.printer');

			console.error(failure);
		}

		// notify message
		if (win !== null) {
			win.webContents.send('register-status', msg);
		}

		// close printer window on complete
		printerWin[arg.id].close();
	});
});

/**
 * Implement callback to support notifications.
 *
 * @param 	Event  event  The triggered event.
 * @param 	mixed  arg    The given parameters.
 *
 * @return 	void
 */
ipc.on('receive-order', (event, arg) => {
	// make sure notifications are supported
	if (!Notification.isSupported() || parseInt(config.get('notifications')) == 0) {
		if (win !== null) {
			// use internal notifications audio
			win.webContents.send('tweet');
		}
		return;
	}

	const data = {};

	if (arg.group == 0) {
		// fetch title and description for restaurant reservation
		data.title = bundle.__('notification.restaurant.title', arg.id);
		data.body  = bundle.__('notification.restaurant.body');
	} else {
		// fetch title and description for take-away order
		data.title = bundle.__('notification.takeaway.title', arg.id);
		data.body  = bundle.__('notification.takeaway.body');
	}

	// check whether the notification should play a sound
	data.silent = parseInt(config.get('playsound')) == 0 ? true : false;

	// display notification
	const notification = new Notification(data);

	// register click event
	notification.on('click', (event) => {
		if (win !== null) {
			// load order details
			win.webContents.send('open-order', arg);
			// auto-read the order
			win.webContents.send('read-order', arg);
		}
	});

	// ease in notification
	notification.show();
});

/**
 * Implement callback to validate the specified license key.
 *
 * @param 	Event   event  The triggered event.
 * @param 	string  key    The license key.
 *
 * @return 	void
 */
ipc.on('validate-license', (event, key) => {
	let status = {
		key: key,
	};

	// validate license key
	license.validateLicense(key).then(() => {
		// license is valid!
		status.result = true;

		// re-open main window
		app.emit('activate');
	}).catch((error) => {
		// license not valid
		status.result = false;
		
		if (typeof error === 'string') {
			if (error.match(/\s/)) {
				// use plain error message
				status.error = error;
			} else {
				// translate error message
				status.error = bundle.__(error);
			}
		} else {
			// received an array, use sprintf
			status.error = bundle.__(error[0], error[1]);
		}
	}).finally(() => {
		if (preferences) {
			// notify back
			preferences.webContents.send('license-validated', status);
		}
	});
});

/**
 * Helper function used to load the changelog of the program.
 *
 * @param 	string 	version  The current version of the program. If specified,
 * 							 the changelog will exclude all the versions prior
 * 							 than the specified one.
 *
 * @return 	Promise
 */
function loadChangelog(version) {
	return new Promise((resolve, reject) => {
		// check whether we already loaded the changelog
		if (changelogManifest !== null) {
			// immediately resolve with the cached manifest
			resolve(changelogManifest);

			return true;
		}

		// make request to load update changelog
		let request = new UINetRequest(deploymentUrl, {
			// do not validate SSL certificate
			rejectUnauthorized: false,
		});

		if (version) {
			// add current version to exclude older changelogs
			request.addParam('version', app.getVersion());
		}

		request.post((error, response, body) => {
			if (error) {
				// an error occurred
				reject(error);

				return false;
			}

			if (typeof body !== 'object') {
				// we received an unexpected response
				reject(body);

				return false;
			}

			// cache manifest
			changelogManifest = body;

			resolve(body);
		});
	});
}

/**
 * Adds support for application self-updates.
 *
 * @return 	void
 */
function setupAutoUpdates() {
	let changelog = null;

	/**
	 * Auto updates are handled in 2 different ways by 2 different modules.
	 * While MAC offers a more flexible solution, Windows needs a specific
	 * file system structure.
	 *
	 * Said so, we need to fetch the autoUpdater options according to the
	 * current platform.
	 */
	let options;

	if (process.platform == 'darwin') {
		// Mac OS found, use default deployment URL
		options = {
			url: deploymentUrl,
			serverType: 'json',
		};
	} else {
		// Point to a public folder on our server, which will have to contain
		// the RELEASE file and a .NUPKG file. Make sure that the file name of
		// .NUPKG file is equals to the one mentioned inside RELEASE. 
		options = "https://vikwp.com/electron/vre-printer/" + process.platform + '/' + process.arch;
	}

	try {
		// listen for auto updates
		autoUpdater.setFeedURL(options);
	} catch (err) {
		// catch error to prevent disturbing alert in dev mode
		if (!isDev) {
			// Production mode, something went wrong...
			// Propagate the exception to display an alert.
			throw err;
		}
	}

	if (config.get('autoupdates', 1) == 1) {
		// check for updates only once
		autoUpdater.checkForUpdates();
	}

	// Emitted when there is an error while updating.
	autoUpdater.on('error', (error) => {
		const dialogOpts = {
			type: 'error',
			buttons: [
				bundle.__('update.error.buttons.close'),
			],
			title: bundle.__('update.error.title'),
			message: bundle.__('update.error.message'),
			detail: error.message,
		};

		// display error message
		dialog.showMessageBox(dialogOpts);

		if (win !== null) {
			// clear status on error
			win.webContents.send('register-status', '');
		}
	});

	// Emitted when checking if an update has started.
	autoUpdater.on('checking-for-update', () => {
		if (win !== null) {
			// notify message
			win.webContents.send('register-status', bundle.__('update.checking'));
		}
	});

	// Emitted when there is an available update.
	// The update is downloaded automatically.
	autoUpdater.on('update-available', () => {
		if (win !== null) {
			// notify message
			win.webContents.send('register-status', bundle.__('update.avail'));
		}

		// new version found, always clear cached manifest
		changelogManifest = null;

		// retrieve the changelog of the latest version
		loadChangelog(app.getVersion()).then((data) => {
			// take only the notes of the latest changelog
			changelog = data.releases.shift().updateTo.notes;
		}).catch((error) => {
			console.error(error);
		});
	});

	// Emitted when there is no available update.
	autoUpdater.on('update-not-available', () => {
		if (win !== null) {
			// notify message (auto-clear after 3 seconds)
			win.webContents.send('register-status', {
				text:  bundle.__('update.notavail'),
				delay: 3000,
			});
		}
	});

	// Emitted when an update has been downloaded.
	// On Windows only releaseName is available.
	// Note: It is not strictly necessary to handle this event.
	// A successfully downloaded update will still be applied
	// the next time the application starts.
	autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateURL) => {
		const dialogOpts = {
			type: 'info',
			buttons: [
				bundle.__('update.confirm.buttons.restart'),
				bundle.__('update.confirm.buttons.later'),
			],
			title: bundle.__('update.confirm.title'),
			message: process.platform === 'win32' ? releaseNotes : releaseName,
			detail: bundle.__('update.confirm.detail') + (changelog ? '\n\n' + changelog : ''),
		};

		// ready to apply the update, ask for a confirmation
		dialog.showMessageBox(dialogOpts).then((returnValue) => {
			// check whether the first button was clicked
			if (returnValue.response === 0) {
				autoUpdater.quitAndInstall();
			}
		});

		if (win !== null) {
			// clear status on completion
			win.webContents.send('register-status', '');
		}
	});
}
