"use strict"

class LogBase {
	level = {
		DEBUG: 0,
		INFO: 1,
		WARN: 2,
		ERROR: 3,
		FATAL: 4,
		NOTICE: 5,
		SUCCESS: 6,
	}
	levelName = Object.keys(this.level)
	constructor(currentLevel = this.level.DEBUG) {
		this.currentLevel = currentLevel

		for (const name in this.level) {
			this[name.toLowerCase()] = msg => this.write(this.level[name], msg)
		}
	}
	isReady() {
		return true
	}
	upgrade() {}
	getLevel() {
		return this.currentLevel
	}
	setLevel(level) {
		this.currentLevel = level
	}
	write(level, msg) {
		if (level < this.currentLevel) return
		const date = new Date()
		this.append([level, date, msg])
		console.log(`${date.toISOString().replace("T", " ").slice(0, -1)}: ${this.levelName[level]}: ${msg}`)
	}
}

class LogClient extends LogBase {
	setLevel(level) {
		super.setLevel(level)
		relayMsg(LogWorker.prefix+'setLevel', level)
	}
	append(event) {
		event[1] = event[1].getTime()
		relayMsg(LogWorker.prefix+'append', event, true)
	}
	getNext() {
		return relayMsg(LogWorker.prefix+'getNext')
	}
	clear() {
		relayMsg(LogWorker.prefix+'clear')
	}
	purgeBefore(date) {
		relayMsg(LogWorker.prefix+'purgeBefore', date)
	}
	getEvents(startId, limit, doSort) {
		return relayMsg(LogWorker.prefix+'getEvents', startId, limit, doSort)
	}
}

class LogWorker extends LogBase {
	static prefix = 'log.'
	constructor(currentLevel) {
		super(currentLevel)
		this.db = null
		this.createDB()
		addRelayHandler(this)
	}
	createDB(opts = {}) {
		try {
			const req = indexedDB.open("plog", 1)
			req.onerror = ev => {
				opts._retry = (opts._retry || 0) + 1
				if (opts._retry < 100) {
					setTimeout(() => this.createDB(opts), 50)
				} else {
					console.warn("WARNING: COULDN'T CREATE DATABASE FOR LOGGING! " + ev)
					stats.inc('LogErr')
				}
			}
			req.onsuccess = ev => this.db = ev.target.result
			req.onupgradeneeded = ev => {
				const db = ev.target.result
				const store = db.createObjectStore("plog", {keyPath: "id", autoIncrement: true})
				store.createIndex("d", "d", {unique: false})
			}
		} catch (err) {
			console.warn("WARNING: COULDN'T CREATE DATABASE FOR LOGGING! " + err.message)
			stats.inc('LogErr')
		}
	}
	closeDB() {
		this.db?.close()
	}
	isReady() {
		return this.db !== null
	}
	append(event, fromRelay) {
		if (!this.db) return
		try {
			const tx = this.db.transaction("plog", "readwrite")
			tx.onerror = ev => {
				event._retry = (event._retry || 0) + 1
				if (event._retry < 100) {
					setTimeout(() => this.append(event), 50)
				} else {
					console.error("Error writing log entry to DB: ", ev)
					stats.inc('LogErr')
				}
			}
			const req = tx.objectStore("plog").add({l: event[0], d: event[1].getTime ? event[1].getTime() : event[1], m: event[2]})
			req.onsuccess = ev => this.next = ev.target.result + 1
		} catch (ex) {}

		if (fromRelay && event[0] >= this.getLevel()) {
			console.log(`${formatDate(event[1], true)}: ${this.levelName[event[0]]}: ${event[2]}`)
		}
	}
	getNext() {
		return this.next || 1
	}
	clear() {
		if (!this.db) return
		try {
			const store = this.db.transaction("plog", "readwrite").objectStore("plog")
			const req = store.clear()
			req.onsuccess = () => this.next = 0
		} catch (ex) {}
	}
	purgeBefore(date) {
		if (!this.db) return
		try {
			const store = this.db.transaction("plog", "readwrite").objectStore("plog")
			const req = store.index("d").getKey(IDBKeyRange.lowerBound(date.getTime ? date.getTime() : date))
			req.onsuccess = ev => {
				const id = ev.target.result
				if (id) store.delete(IDBKeyRange.upperBound(id, true))
			}
		} catch (ex) {}
	}
	async getEvents(startId, limit, doSort) {
		if (!this.db) return
		try {
			const tx = this.db.transaction("plog", "readonly")
			const req = tx.objectStore("plog").getAll(startId ? IDBKeyRange.lowerBound(startId) : undefined, limit)
			return new Promise((res, rej) => {
				req.onsuccess = ev => res(ev.target.result)
				req.onerror = ev => rej(ev.target.error)
			})
		} catch (ex) {}
	}
}
