<html>
<head><meta charset="utf-8"></head>
<body>
	<ul id="messages"></ul>
	<script type="text/javascript">
		class Key {
			constructor() {
				this._keys = new Int32Array(2);
			}

			get gapKey() {
				return this._keys[0];
			}

			set gapKey(value) {
				this._keys[0] = value;
			}

			get eventKey() {
				return this._keys[1];
			}

			set eventKey(value) {
				this._keys[1] = value;
			}

			buffer() {
				return this._keys.buffer;
			}

			nextKeyWithGap() {
				const k = new Key();
				k.gapKey = this.gapKey + 1;
				k.eventKey = 0;
				return k;
			}

			nextKey() {
				const k = new Key();
				k.gapKey = this.gapKey;
				k.eventKey = this.eventKey + 1;
				return k;
			}

			previousKey() {
				const k = new Key();
				k.gapKey = this.gapKey;
				k.eventKey = this.eventKey - 1;
				return k;
			}

			clone() {
				const k = new Key();
				k.gapKey = this.gapKey;
				k.eventKey = this.eventKey;	
				return k;
			}
		}

		function reqAsPromise(req) {
		    return new Promise((resolve, reject) => {
		        req.onsuccess = () => resolve(req);
		        req.onerror = (err) => reject(err);
		    });
		}

		function fetchResults(cursor, isDone, resultMapper) {
		    return new Promise((resolve, reject) => {
		        const results = [];
		        cursor.onerror = (event) => {
		            reject(new Error("Query failed: " + event.target.errorCode));
		        };
		        // collect results
		        cursor.onsuccess = (event) => {
		        	console.log("got a result");
		            const cursor = event.target.result;
		            if (!cursor) {
		                resolve(results);
		                return; // end of results
		            }
		            results.push(resultMapper(cursor));
		            if (!isDone(results)) {
		            	cursor.continue();
		            } else {
		            	resolve(results);
		            }
		        };
		    });
		}

		class Storage {
			constructor(databaseName) {
				this._databaseName = databaseName;
				this._database = null;
			}

			async open() {
				const req = window.indexedDB.open(this._databaseName);
				req.onupgradeneeded = (ev) => {
					const db = ev.target.result;
					const oldVersion = ev.oldVersion;
					this._createStores(db, oldVersion);
				}; 
				await reqAsPromise(req);
				this._database = req.result;
			}

			_createStores(db) {
				db.createObjectStore("timeline", {keyPath: ["roomId", "sortKey"]});
			}

			async insert(value) {
				const tx = this._database.transaction(["timeline"], "readwrite");
				const store = tx.objectStore("timeline");
				await reqAsPromise(store.add(value));
			}

			async selectAt(roomId, sortKey, amount) {
				const tx = this._database.transaction(["timeline"], "readonly");
				const store = tx.objectStore("timeline");
				const range = IDBKeyRange.lowerBound([roomId, sortKey.buffer()]);
				const cursor = store.openCursor(range);
				return await fetchResults(cursor, 
					(results) => results.length === amount,
					(cursor) => cursor.value);
			}
		}

		(async () => {
			const initialSortKey = new Key();
			initialSortKey.gapKey = 1000;
			const roomId = "!abc:hs.tld";
			const storage = new Storage("mysession");
			await storage.open();

			let records = await storage.selectAt(roomId, initialSortKey, 15);
			if (!records.length) {
				// insert first batch backwards,
				// to see we're not assuming insertion order to sort
				let sortKey = initialSortKey.clone();
				sortKey.eventKey = 10;
				for (var i = 10; i > 0; i--) {
					await storage.insert({
						roomId,
						sortKey: sortKey.buffer(),
						message: `message ${i} before gap`
					});
					sortKey = sortKey.previousKey();
				}
				sortKey = sortKey.nextKeyWithGap();
				await storage.insert({
					roomId,
					sortKey: sortKey.buffer(),
					message: `event to represent gap!`
				});
				for (var i = 1; i <= 10; i++) {
					sortKey = sortKey.nextKey();
					await storage.insert({
						roomId,
						sortKey: sortKey.buffer(),
						message: `message ${i} after gap`
					});
				}
				records = await storage.selectAt(roomId, initialSortKey, 15);
			}
			console.log(records, "records");
			const nodes = records.map(r => {
				const li = document.createElement("li");
				li.appendChild(document.createTextNode(r.message));
				return li;
			});
			const parentNode = document.getElementById("messages");
			nodes.forEach(n => parentNode.appendChild(n));
		})();

	</script>
</body>
</html>