package ru.dbotthepony.mc.otm.core.chart

import it.unimi.dsi.fastutil.objects.ObjectIterators
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import net.minecraft.network.RegistryFriendlyByteBuf
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.collect.toList
import ru.dbotthepony.mc.otm.core.util.ITickable
import ru.dbotthepony.mc.otm.network.syncher.DynamicSynchableGroup
import ru.dbotthepony.mc.otm.network.syncher.IRemoteState
import ru.dbotthepony.mc.otm.network.syncher.ISynchable
import java.util.function.Supplier

abstract class AbstractCombinedHistoryChart<V : Any, CHART : AbstractHistoryChart<V>>(
	protected val provider: Supplier<Iterator<CHART>>,
	final override val width: Int,
	final override val resolution: Int
) : IHistoryChart<V>, ISynchable, ITickable {
	protected abstract fun factory(stream: RegistryFriendlyByteBuf): CHART
	protected abstract fun networkMetadata(stream: RegistryFriendlyByteBuf, chart: CHART)
	private val group = DynamicSynchableGroup(::factory) { networkMetadata(it, this) }
	protected abstract fun sum(values: List<V>): V

	override val isRemote: Boolean
		get() = group.isRemote
	override val hasRemotes: Boolean
		get() = group.hasRemotes

	override fun tick() {
		if (group.hasRemotes) {
			val current = ObjectOpenHashSet(provider.get())
			group.retainAll(current)

			for (v in current) {
				if (v !in group) {
					group.add(v)
				}
			}
		} else {
			group.clear()
		}
	}

	final override fun iterator(): Iterator<V> {
		val iterators = if (group.isRemote)
			group.iterator().map { it.iterator() }.toList()
		else
			provider.get()
				.map { it.iterator() }
				.filter { it.hasNext() }
				.toList()

		if (iterators.isEmpty()) {
			return ObjectIterators.emptyIterator()
		} else {
			val buffer = ArrayList<V>(iterators.size)

			return object : Iterator<V> {
				override fun hasNext(): Boolean {
					return iterators.isNotEmpty()
				}

				override fun next(): V {
					buffer.clear()

					iterators.removeIf {
						buffer.add(it.next())
						!it.hasNext()
					}

					return sum(buffer)
				}
			}
		}
	}

	final override fun get(index: Int): V {
		if (group.isRemote) {
			return sum(group.map { it[index] })
		} else {
			return sum(provider.get().map { it[index] }.toList())
		}
	}

	final override fun read(stream: RegistryFriendlyByteBuf) {
		group.read(stream)
	}

	final override fun createRemoteState(listener: Runnable): IRemoteState {
		return group.createRemoteState(listener)
	}
}
