/*
 _       __      _ __
| |     / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__  )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */

import { Debug } from './log';
import { SendMessage } from 'ipc';

var callbacks = {};

/**
 * Returns a number from the native browser random function
 *
 * @returns number
 */
function cryptoRandom() {
	var array = new Uint32Array(1);
	return window.crypto.getRandomValues(array)[0];
}

/**
 * Returns a number using da old-skool Math.Random
 * I likes to call it LOLRandom
 *
 * @returns number
 */
function basicRandom() {
	return Math.random() * 9007199254740991;
}

// Pick a random number function based on browser capability
var randomFunc;
if (window.crypto) {
	randomFunc = cryptoRandom;
} else {
	randomFunc = basicRandom;
}


/**
 * Call sends a message to the backend to call the binding with the
 * given data. A promise is returned and will be completed when the
 * backend responds. This will be resolved when the call was successful
 * or rejected if an error is passed back.
 * There is a timeout mechanism. If the call doesn't respond in the given
 * time (in milliseconds) then the promise is rejected.
 *
 * @export
 * @param {string} name
 * @param {string} args
 * @param {number=} timeout
 * @returns
 */
export function Call(name, args, timeout) {

	// Timeout infinite by default
	if (timeout == null || timeout == undefined) {
		timeout = 0;
	}

	// Create a promise
	return new Promise(function (resolve, reject) {

		// Create a unique callbackID
		var callbackID;
		do {
			callbackID = name + '-' + randomFunc();
		} while (callbacks[callbackID]);

		// Set timeout
		if (timeout > 0) {
			var timeoutHandle = setTimeout(function () {
				reject(Error('Call to ' + name + ' timed out. Request ID: ' + callbackID));
			}, timeout);
		}

		// Store callback
		callbacks[callbackID] = {
			timeoutHandle: timeoutHandle,
			reject: reject,
			resolve: resolve
		};

		try {
			const payload = {
				name,
				args,
				callbackID,
			};

			// Make the call
			SendMessage('C' + JSON.stringify(payload));
		} catch (e) {
			// eslint-disable-next-line
			console.error(e);
		}
	});
}



/**
 * Called by the backend to return data to a previously called
 * binding invocation
 *
 * @export
 * @param {string} incomingMessage
 */
export function Callback(incomingMessage) {
	// Decode the message - Credit: https://stackoverflow.com/a/13865680
	//incomingMessage = decodeURIComponent(incomingMessage.replace(/\s+/g, '').replace(/[0-9a-f]{2}/g, '%$&'));

	// Parse the message
	var message;
	try {
		message = JSON.parse(incomingMessage);
	} catch (e) {
		const error = `Invalid JSON passed to callback: ${e.message}. Message: ${incomingMessage}`;
		Debug(error);
		throw new Error(error);
	}
	var callbackID = message.callbackid;
	var callbackData = callbacks[callbackID];
	if (!callbackData) {
		const error = `Callback '${callbackID}' not registered!!!`;
		console.error(error); // eslint-disable-line
		throw new Error(error);
	}
	clearTimeout(callbackData.timeoutHandle);

	delete callbacks[callbackID];

	if (message.error) {
		callbackData.reject(message.error);
	} else {
		callbackData.resolve(message.result);
	}
}

/**
 * SystemCall is used to call wails methods from the frontend
 *
 * @export
 * @param {string} method
 * @param {any[]=} data
 * @returns
 */
export function SystemCall(method, data) {
	return Call('.wails.' + method, data);
}
