
(() => {
    const INDEXEDCACHE_STORAGE_NAME = "databank";
    const INDEXEDCACHE_DATABASE_NAME = "media";
    const INDEXEDCACHE_DBVERSION_COLUMN = "dbVersion";
    IDBTransaction.READ_WRITE = "readwrite";
    IDBTransaction.READ = "readonly";

    class IndexedCache
    {
        #version = parseInt(localStorage?.getItem(INDEXEDCACHE_DBVERSION_COLUMN)) || 0;
        #openReq = null;
        #db = null;
        #mediaStore = null;

        constructor()
        {
            this.#openReq = new Promise((ok, ko) => {
                const DBOpenRequest = window.indexedDB.open(INDEXEDCACHE_STORAGE_NAME, 1);
                DBOpenRequest.onerror = evt => {
                    console.error("Failed to open db: ", evt);
                    this.onDbReady();
                };
                DBOpenRequest.onupgradeneeded = evt => {
                    let mediaStore = evt.target.result.createObjectStore(INDEXEDCACHE_DATABASE_NAME, {
                        autoIncrement: true,
                    });
                };
                DBOpenRequest.onsuccess = evt => {
                    this.onDbReady(evt.target.result);
                };
            });
            MediaStorage.Instance.addEventListener("doneLoading", async evt => {
                await this.pushMedias(MediaStorage.Instance.medias, MediaStorage.Instance.getDbVersion());
                let removed = await this.removeObsolete();
                removed.length && MediaStorage.Instance.removeMedia(removed);
            });
        }

        onDbReady(db, mediaStore) {
            this.#openReq = null;
            this.#db = db;
            this.#mediaStore = mediaStore;
        }

        #reduceMedia(media) {
            let mediaRawData = {
                date: media.getDateTs(),
                md5sum: media.md5sum,
                fixedSum: media.fixedSum,
                path: media.path,
                fileName: media.fileName,
                meta: media.meta,
                fixedTags: Array.from(media.fixedTags),
                tags: Array.from(media.tags),
                version: media.version,
                writeAccess: media.writeAccess
            };

            for (let i in mediaRawData.meta) {
                if (mediaRawData.meta[i].value instanceof Date)
                    mediaRawData.meta[i].value = mediaRawData.meta[i].value.getTime();
            }

            return JSON.stringify(mediaRawData);
        }

        async listMedias() {
            await this.#openReq;
            if (!this.#db)
                return { medias: [], version: 0 };
            let data = [];
            try {
                await new Promise(ok => {
                    let transaction = this.#db.transaction([INDEXEDCACHE_DATABASE_NAME], IDBTransaction.READ);
                    transaction.oncomplete = ok;
                    transaction.onerror = ok;
                    let storage = transaction.objectStore(INDEXEDCACHE_DATABASE_NAME);
                    let req = storage.getAll();
                    req.onsuccess = () => {
                        data = req.result.map(x => JSON.parse(x));
                    }
                });
            }
            catch (err) {
                console.error(err);
                return { medias: [], version: 0 };
            }
            return { medias: data, version: data.length ? this.#version : 0 };
        }

        allKeyCount() {
            return new Promise(async ok => {
                let count = 0;
                let transaction = this.#db.transaction([INDEXEDCACHE_DATABASE_NAME], IDBTransaction.READ);
                transaction.oncomplete = () => ok(count);
                transaction.onerror = () => ok(count);
                count = (await (new Promise(okKey => {
                    let req = transaction.objectStore(INDEXEDCACHE_DATABASE_NAME).getAllKeys();
                    req.onsuccess = evt => {
                        okKey(req?.result || []);
                    }
                }))).length;
            });
        }

        #getObsoleteKeys() {
            return new Promise((ok, ko) => {
                $.get(`/api/media/sumlist`, async srvKeys => {
                    let transaction = this.#db.transaction([INDEXEDCACHE_DATABASE_NAME], IDBTransaction.READ);
                    let obsoleteIndexes = [];
                    transaction.oncomplete = () => ok(obsoleteIndexes);
                    transaction.onerror = () => ok(obsoleteIndexes);
                    obsoleteIndexes = (await (new Promise((okLocalKey) => {
                        let req = transaction.objectStore(INDEXEDCACHE_DATABASE_NAME).getAllKeys();
                        req.onsuccess = evt => {
                            okLocalKey(req?.result || []);
                        }
                    }))).filter(i => srvKeys.data.indexOf(i) === -1);
                });
            });
        }

        async removeItems(md5Sums) {
            await this.#openReq;
            if (!this.#db)
                return;
            for (let i of md5Sums)
                await new Promise((ok, ko) => {
                    let transaction = this.#db.transaction([INDEXEDCACHE_DATABASE_NAME], IDBTransaction.READ_WRITE);
                    transaction.oncomplete = ok;
                    transaction.onerror = ko;
                    transaction.objectStore(INDEXEDCACHE_DATABASE_NAME).delete(i);
                });
        }

        async removeObsolete() {
            await this.#openReq;
            if (!this.#db)
                return;

            let obsoleteIndexes = await this.#getObsoleteKeys();
            await this.removeItems(obsoleteIndexes);
            return obsoleteIndexes;
        }

        async pushMedias(mediaArr, version) {
            await this.#openReq;
            if (!this.#db)
                return;
            await new Promise(ok => {
                let transaction = this.#db.transaction([INDEXEDCACHE_DATABASE_NAME], IDBTransaction.READ_WRITE);
                transaction.oncomplete = ok;
                transaction.onerror = ok;
                let storage = transaction.objectStore(INDEXEDCACHE_DATABASE_NAME);
                mediaArr.forEach(x => storage.put(this.#reduceMedia(x), x.fixedSum));
            });
            localStorage?.setItem(INDEXEDCACHE_DBVERSION_COLUMN, version);
            this.#version = version;
            console.log("Done updating db");
        }

        async removeAll() {
            await this.#openReq;
            if (!this.#db)
                return;
            await new Promise(ok => {
                let transaction = this.#db.transaction([INDEXEDCACHE_DATABASE_NAME], IDBTransaction.READ_WRITE);
                transaction.oncomplete = ok;
                transaction.onerror = ok;
                let storage = transaction.objectStore(INDEXEDCACHE_DATABASE_NAME);
                storage.clear();
            });
        }

        async clean() {
            await this.#openReq;
            if (!this.#db)
                return;
            this.#db.transaction(INDEXEDCACHE_DATABASE_NAME, IDBTransaction.READ_WRITE);
            localStorage?.removeItem(INDEXEDCACHE_DBVERSION_COLUMN);
            this.#version = 0;
        }
    }

    window.indexedData = new IndexedCache();
})();
