// TODO(chris): load events that are longer than the interval without doubling it

export function useEventLoader(rangeInterval, getPromiseFunc) {
	let loading_id = 0;
	const events = Vue.ref([]);
	const loadingEvents = Vue.ref([]);
	const allEvents = Vue.computed(() => events.value.concat(loadingEvents.value));
	const lv = Vue.ref(null);
	const eventsLoaded = [];

	const mergePromiseArr = (n, o) => {
		if (Array.isArray(n))
			return o.concat(n);
		return o.push(n), o;
	};

	const markEventsLoaded = (start, end) => {
		let result = [];
		if (!eventsLoaded.length) {
			// empty: add new chunk
			eventsLoaded.push(start.ts, end.ts);
		} else {
			if (eventsLoaded[eventsLoaded.length-1] + 1 == start.ts) {
				// add to the end of last chunk
				eventsLoaded[eventsLoaded.length-1] = end.ts;
			} else if (eventsLoaded[eventsLoaded.length-1] < start.ts) {
				// add new chunk after the last chunk
				eventsLoaded.push(start.ts, end.ts);
			} else if (eventsLoaded[0] == end.ts + 1) {
				// add to the start of first chunk
				eventsLoaded[0] = start.ts;
			} else if (eventsLoaded[0] > end.ts) {
				eventsLoaded.unshift(start.ts, end.ts);
			} else {
				let index = eventsLoaded.findIndex(e => e >= start.ts);

				if (index % 2) {
					// starts inside an existing chunk
					if (eventsLoaded[index] >= end.ts)
						return []; // Already loaded

					let indexIsLast = (index == eventsLoaded.length - 1);

					if (indexIsLast || eventsLoaded[index + 1] > end.ts) {
						// extend an existing chunk
						// and merge with the next if necessary
						let nStart = eventsLoaded[index] + 1;
						start = start.plus(nStart - start.ts);
						if (!indexIsLast && eventsLoaded[index + 1] == end.ts + 1)
							eventsLoaded.splice(index, 2);
						else
							eventsLoaded[index] = end.ts;
					} else {
						// merge exising chunks
						// and load the rest if necessary
						if (eventsLoaded[index + 2] < end.ts) {
							let rStart = eventsLoaded[index + 2] + 1;
							result = mergePromiseArr(markEventsLoaded(start.plus(rStart - start.ts), end), result);
						}

						let nStart = eventsLoaded[index] + 1;
						start = start.plus(nStart - start.ts);
						let nEnd = eventsLoaded[index + 1] - 1;
						end = end.plus(nEnd - end.ts);
						eventsLoaded.splice(index, 2);
					}
				} else {
					// starts between two chunks or before the first
					if (!index) {
						// extend the first chunk
						// and load the rest if necessary
						if (eventsLoaded[1] < end.ts) {
							let rStart = eventsLoaded[1] + 1;
							result = mergePromiseArr(markEventsLoaded(start.plus(rStart - start.ts), end), result);
						}
						let nEnd = eventsLoaded[0] - 1;
						end = end.plus(nEnd - end.ts);
						eventsLoaded[0] = start.ts;
					} else if (eventsLoaded[index] == start.ts) {
						// starts at the same position as an existing chunk
						if (eventsLoaded[index + 1] >= end.ts)
							return []; // Already loaded
						// load the rest
						let rStart = eventsLoaded[index + 1] + 1;
						result = mergePromiseArr(markEventsLoaded(start.plus(rStart - start.ts), end), result);
					} else {
						// extend an existing chunk
						// and load the rest if necessary
						if (eventsLoaded[index + 1] < end.ts) {
							let rStart = eventsLoaded[index + 1] + 1;
							result = mergePromiseArr(markEventsLoaded(start.plus(rStart - start.ts), end), result);
						}
						let nEnd = eventsLoaded[index] - 1;
						end = end.plus(nEnd - end.ts);
						eventsLoaded[index] = start.ts;
					}
				}
			}
		}

		if (start.ts >= end.ts)
			return result;

		loadingEvents.value.push({
			loading_id: loading_id++,
			type: "loading",
			isostart: start.toISODate() + 'T' + start.toISOTime(),
			isoend: end.toISODate() + 'T' + end.toISOTime()
		});

		return mergePromiseArr(getPromiseFunc(start, end), result);
	};

	Vue.watchEffect(() => {
		const range = Vue.toValue(rangeInterval);
		if (!(range instanceof luxon.Interval))
			return;
		const promises = markEventsLoaded(range.start, range.end);
		Promise
			.allSettled(promises)
			.then(results => {
				results.forEach(res => {
					if (
						res.status === 'fulfilled'
						&& res.value.meta.status === "success"
					) {
						if (res.value.meta.lv)
							lv.value = res.value.meta.lv;

						events.value = events.value.concat(res.value.data);
						loadingEvents.value = [];
					}
				})
			});
	})

	return { events: allEvents, lv }
}