'use strict';

import Logger from 'utilities/logging';
import Module from 'utilities/module';

import {DEBUG} from 'utilities/constants';
import {serializeBlob, deserializeBlob} from 'utilities/blobs';

import SettingsManager from './settings/index';

class FFZBridge extends Module {
	constructor() {
		super();
		const start_time = performance.now();

		FFZBridge.instance = this;

		this.host = 'null';
		this.flavor = 'bridge';

		this.name = 'ffz_bridge';
		this.__state = 0;
		this.__modules.core = this;

		// ========================================================================
		// Logging
		// ========================================================================

		this.log = new Logger(null, null, null);
		this.log.label = 'FFZBridge';
		this.log.init = true;

		this.core_log = this.log.get('core');
		this.log.hi(this);


		// ========================================================================
		// Core Systems
		// ========================================================================

		this.inject('settings', SettingsManager);


		// ========================================================================
		// Startup
		// ========================================================================

		this.enable().then(() => {
			const duration = performance.now() - start_time;
			this.core_log.info(`Initialization complete in ${duration.toFixed(5)}ms.`);
			this.log.init = false;
		}).catch(err => {
			this.core_log.error(`An error occurred during initialization.`, err);
			this.log.init = false;
		});
	}

	static get() {
		return FFZBridge.instance;
	}

	async onEnable() {
		window.addEventListener('message', this.onMessage.bind(this));
		this.settings.provider.on('changed', this.onProviderChange, this);
		this.settings.provider.on('changed-blob', this.onProviderBlobChange, this);
		this.settings.provider.on('clear-blobs', this.onProviderClearBlobs, this);

		await this.settings.awaitProvider();
		await this.settings.provider.awaitReady();

		this.send({
			ffz_type: 'ready'
		});
	}

	async onMessage(event) {
		const msg = event.data;
		if ( ! msg || ! msg.ffz_type )
			return;

		if ( msg.ffz_type === 'load' ) {
			const out = {};
			for(const [key, value] of this.settings.provider.entries())
				out[key] = value;

			this.send({
				ffz_type: 'loaded',
				data: out
			});

			return;

		} else if ( msg.ffz_type === 'change' ) {
			this.onChange(msg);
			return;
		}

		if ( ! msg.id )
			return this.log.warn('Received command with no reply ID');

		let reply, transfer;

		try {
			if ( msg.ffz_type === 'init-load' ) {
				reply = {
					blobs: this.settings.provider.supportsBlobs,
					values: {}
				};

				for(const [key,value] of this.settings.provider.entries())
					reply.values[key] = value;

			} else if ( msg.ffz_type === 'set' )
				this.settings.provider.set(msg.key, msg.value);

			else if ( msg.ffz_type === 'delete' )
				this.settings.provider.delete(msg.key);

			else if ( msg.ffz_type === 'clear' )
				this.settings.provider.clear();

			else if ( msg.ffz_type === 'get-blob' ) {
				reply = await serializeBlob(await this.settings.provider.getBlob(msg.key));
				if ( reply )
					transfer = reply.buffer;

			} else if ( msg.ffz_type === 'set-blob' ) {
				const blob = deserializeBlob(msg.value);
				await this.settings.provider.setBlob(msg.key, blob);

			} else if ( msg.ffz_type === 'delete-blob' )
				await this.settings.provider.deleteBlob(msg.key);

			else if ( msg.ffz_type === 'has-blob' )
				reply = await this.settings.provider.hasBlob(msg.key);

			else if ( msg.ffz_type === 'clear-blobs' )
				await this.settings.provider.clearBlobs();

			else if ( msg.ffz_type === 'blob-keys' )
				reply = await this.settings.provider.blobKeys();

			else if ( msg.ffz_type === 'flush' )
				await this.settings.provider.flush();

			else
				return this.send({
					ffz_type: 'reply-error',
					id: msg.id,
					error: 'bad-command'
				});

			this.send({
				ffz_type: 'reply',
				id: msg.id,
				reply
			}, transfer);

		} catch(err) {
			this.log.error('Error handling command.', err);
			this.send({
				ffz_type: 'reply-error',
				id: msg.id
			});
		}
	}

	send(msg, blob) { // eslint-disable-line class-methods-use-this
		try {
			window.parent.postMessage(msg, '*', blob ? [blob] : undefined)
		} catch(err) { this.log.error('send error', err); /* no-op */ }
	}

	onChange(msg) {
		const key = msg.key,
			value = msg.value,
			deleted = msg.deleted;

		if ( deleted )
			this.settings.provider.delete(key);
		else
			this.settings.provider.set(key, value);
	}

	onProviderChange(key, value, deleted) {
		this.send({
			ffz_type: 'change',
			key,
			value,
			deleted
		});
	}

	onProviderBlobChange(key, deleted) {
		this.send({
			ffz_type: 'change-blob',
			key,
			deleted
		});
	}

	onProviderClearBlobs() {
		this.send({
			ffz_type: 'clear-blobs'
		});
	}
}

FFZBridge.Logger = Logger;

const VER = FFZBridge.version_info = Object.freeze({
	major: __version_major__,
	minor: __version_minor__,
	revision: __version_patch__,
	extra: __version_prerelease__?.length && __version_prerelease__[0],
	commit: __git_commit__,
	build: __version_build__,
	hash: __webpack_hash__,
	toString: () =>
		`${VER.major}.${VER.minor}.${VER.revision}${VER.build ? `.${VER.build}` : ''}${VER.extra || ''}${DEBUG ? '-dev' : ''}`
});

window.FFZBridge = FFZBridge;
window.ffz_bridge = new FFZBridge();