"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]
	}
}

class VarStore {
	set(key, value, ev = {}, cmd = {}) {
		if (!key) return
		if (key[0] === '$') key = key.slice(1)
		key = key.toLowerCase()

		const meta = localStore.get('vars', key) || {}
		if (meta.value === value) return
		meta.value = value
		meta[meta.created ? 'updated' : 'created'] = {_: Date.now(), id: ev.id||'', name: ev.name||'', alert: ev.alert||0, line: cmd._line||0}
		localStore.set('vars', key, meta)
		this.updated()
	}
	get(key) {
		if (!key) return
		if (key[0] === '$') key = key.slice(1)
		const meta = localStore.get('vars', key.toLowerCase())
		return meta && meta.value
	}
	remove(keys) {
		if (!keys || !keys.length) return
		for (const key of keys) {
			localStore.remove('vars', (key[0] === '$' ? key.slice(1) : key).toLowerCase())
		}
		this.updated(true)
		// $$$ alternative: broadcast msg?
	}
	rename(keyOld, keyNew) {
		if (keyOld[0] === '$') keyOld = keyOld.slice(1)
		if (keyNew[0] === '$') keyNew = keyNew.slice(1)
		if (keyNew === keyOld) return
		const meta = localStore.get('vars', keyOld)
		if (meta) {
			localStore.remove('vars', keyOld)
			localStore.set('vars', keyNew, meta)
		}
		//this.updated(true)
		// $$$ alternative: broadcast msg?
	}
	setAll(vars) {
		localStore.set('vars', vars)
		this.updated(true)
	}
	getAll() {
		return localStore.get('vars')
	}
	updated(refresh = false) {
		localStore.set('vars', '_', refresh ? -Date.now() : Date.now())
	}
	getRefresh(reset = true) {
		const ts = localStore.get('vars', '_')
		const refresh = ts < 0
		if (refresh && reset) {
			localStore.set('vars', '_', Math.abs(ts))
		}
		return refresh
	}
	getUpdated() {
		const ts = localStore.get('vars', '_')
		return Math.abs(ts)
	}
	ohlc(key, data, ohlc, noCase) {
		if (!key) return
		let value
		if (key[0] === '$') {
			value = this.get(key)
		} else {
			value = noCase ? data && data.getProp(key) : data && data.hasOwnProperty(key) ? data[key] : ohlc && ohlc[key.toLowerCase()]
			// $$$$$$ if missing '_' (custom/local prefix) try w '_' (local) first ???
			if (value === null || value === undefined) {
				value = this.get(key[0] === '_' ? key.slice(1) : key)
			}
		}
		return value === null ? undefined : value
	}
}

const globalVars = new VarStore()
