"use strict"

class Storage {
	constructor(type = 'sync') {
		this._type = type
	}
	static _clear(cb) {
		chrome.storage[this._type].clear(chromeCB.bind(this, cb))
	}
	static _get(keys, cb) {
		chrome.storage[this._type].get(keys, chromeCB.bind(this, cb))
	}
	static _remove(keys, cb) {
		chrome.storage[this._type].remove(keys, chromeCB.bind(this, cb))
	}
	static _set(data, cb) {
		chrome.storage[this._type].set(data, chromeCB.bind(this, cb))
	}
	*clear() {
		return yield Storage._clear.bind(this)
	}
	*get(keys) {
		return yield Storage._get.bind(this, keys)
	}
	*remove(keys) {
		return yield Storage._remove.bind(this, keys)
	}
	*set(data) {
		return yield Storage._set.bind(this, data)
	}
}

const chromeSync = new Storage()
const chromeLocal = new Storage('local')

window.STORE = {local: {}, sync: {}}

class StorageInternal {
	constructor(type = 'sync') {
		this._type = type
	}
	get(...keys) {
		return getObject(window.STORE[this._type] || {}, ...keys)
	}
	gets(keyString) {
		return getObject(window.STORE[this._type] || {}, ...keyString.split('.'))
	}
	set(...keysValue) {
		window.STORE[this._type] = Object.assignDeep(window.STORE[this._type], setObject(...keysValue))
		window.STORE[this._type]._ = Date.now()
	}
	remove(...keys) {
		removeObject(window.STORE[this._type] || {}, ...keys)
		window.STORE[this._type]._ = Date.now()
	}
	updated() {
		window.STORE[this._type]._ = Date.now()
	}
	load(storage) {
		window.STORE[this._type] = storage
		window["saved_"+this._type] = Date.now()
	}
	save() {
		const name = "saved_"+this._type
		if (window[name] < window.STORE[this._type]._) {
			window[name] = window.STORE[this._type]._
			return window.STORE[this._type]
		}
		return null
	}
	send(win, key) {
		win.STORE[this._type][key] = window.STORE[this._type][key]
	}
	static connect(win) {
		window.STORE = (win && win.STORE) || window.STORE
	}
}

const syncStore = new StorageInternal()
const localStore = new StorageInternal('local')

function verify_checksum(salt, bacon) {
	let pepper = window.spice = new Date(1970, 0, 1)
	let sauce = new Date()

	try {
		pepper.setTime(salt * 3600000)
		pepper.setTime(pepper.getTime() + (932614 - bacon) * 60000)
		return pepper > sauce || bacon === 0 || bacon === null
	} catch(ex) {}
}

class Permissions {
	constructor(own) {
		this._own = Permissions.normalize(own)
	}
	static normalize(perm) {
		if (perm && perm.get) {
			return perm.get()
		}
		perm = Array.isArray(perm) ? {permissions: perm} : (perm || {})
		perm.origins = perm.origins || []
		perm.permissions = perm.permissions || []
		return perm
	}
	get() {
		return this._own
	}
	set(other) {
		other = Permissions.normalize(other)
		this._own.origins = other.origins
		this._own.permissions = other.permissions
		return this._own
	}
	origins() {
		return this._own.origins.join(', ')
	}
	length() {
		return this._own.origins.length + this._own.permissions.length
	}
	grant(other, dropOrigins) {
		other = Permissions.normalize(other)
		!dropOrigins && this._own.origins.push(...other.origins.without(this._own.origins))
		this._own.permissions.push(...other.permissions.without(this._own.permissions))
		return this
	}
	hasAll(other) {
		other = Permissions.normalize(other)
		return !other.origins.without(this._own.origins).length && !other.permissions.without(this._own.permissions).length
	}
	hasAny(other) {
		other = Permissions.normalize(other)
		return this._own.origins.includesAny(other.origins) || this._own.permissions.includesAny(other.permissions)
	}
	revoke(other) {
		other = Permissions.normalize(other)
		this._own.origins = this._own.origins.without(other.origins)
		this._own.permissions = this._own.permissions.without(other.permissions)
		return this
	}
	static defaultFilter(perm) {
		return !perm.match(/\d/)
	}
	filter(check = Permissions.defaultFilter) {
		this._own.permissions = this._own.permissions.filter(check)
		return this
	}
	count(check = Permissions.defaultFilter) {
		return this._own.permissions.length - this._own.permissions.filter(check).length
	}
	save(clearFirst) {
		clearFirst && localStore.remove('perms')
		localStore.set('perms', this._own)
		if (bg !== window) {
			Permissions.send(bg)
			localStore.send(bg, 'perms')
		}
		return this
	}
	static send(win) {
		win.perms = window.perms
	}
	static connect(win) {
		window.perms = (win && win.perms) || window.perms
	}
}

window.perms = new Permissions()

class Packages {
	static #pkg = {
		MODULES: {
			permissions: [
				"e3fdd77de3a682bfcfd8dc3000a25a5b",
				"x28vizh6hn8i8usnc22atdw12u57g435",
				"h24dwc0qi188he96xduakfc0kr4jqjaf",
				"d8zwmh92wfj8ge3yzgyxbttf5zhaa3zh",
				"HYvYqTSNBB7AEVfLG7csDUHnLmBnzfe7",
				"t7vu9cbx85sezba9zct9dgmb4yrqw5xf",
			],
		},
		ADDONS: {
			permissions: [
				"c9wrao5iy3d8ebh2lfcgxq184w6zfxrq",
				"0g5t2xydnwdm90xj5nkup2cqr8rwrji9",
				"3zmru2ftaql6wbxefsymng4njx4803mv",
				"7ayhnl3nda5wtvu86cn38nujpsn5ub5h",
				"dbvs8tvdbaqppmtu27g94hfs4m2rj8i1",
				"ckdj0m7v4l5d364jraugs5evoo0b3mju",
			],
		},
		NONE: {
			permissions: [],
		},
	}

	static get(name) {
		if (!name || !(name = _(name))) {
			throw new SyntaxError("Package Name is required!")
		}
		if (!Packages.#pkg[name]) {
			throw new ReferenceError("Invalid Package Name provided!")
		}
		return Packages.#pkg[name]
	}
}
