package ru.dbotthepony.mc.otm.graph.matter

import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import net.minecraft.world.item.Item
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.matter.*
import ru.dbotthepony.mc.otm.core.filterNotNull
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.graph.Abstract6Graph
import java.util.*
import java.util.function.Predicate
import java.util.stream.Stream

@Suppress("unused")
class MatterGraph : Abstract6Graph<MatterNode, MatterGraph>(), IMatterGraphListener {
	private val listeners = ObjectOpenHashSet<IMatterGraphListener>()

	fun addListener(listener: IMatterGraphListener) = listeners.add(listener)
	fun removeListener(listener: IMatterGraphListener) = listeners.remove(listener)

	override fun onNodeRemoved(node: MatterNode) {
		val patterns = node.getPatternHandler()

		if (patterns != null) {
			for (pattern in patterns.patterns) {
				onPatternRemoved(pattern)
			}
		}

		val tasks = node.getTaskHandler()

		if (tasks != null) {
			for (task in tasks.replicationTasks) {
				onMatterTaskRemoved(task)
			}
		}

		for (pattern in this.patterns) {
			node.onPatternRemoved(pattern)
		}

		for (task in this.tasks) {
			node.onMatterTaskRemoved(task)
		}
	}

	override fun onNodeAdded(node: MatterNode) {
		for (pattern in this.patterns) {
			node.onPatternAdded(pattern)
		}

		for (task in this.tasks) {
			node.onMatterTaskCreated(task)
		}

		val patterns = node.getPatternHandler()

		if (patterns != null) {
			for (pattern in patterns.patterns) {
				onPatternAdded(pattern)
			}
		}

		val tasks = node.getTaskHandler()

		if (tasks != null) {
			for (task in tasks.replicationTasks) {
				onMatterTaskCreated(task)
			}
		}
	}

	fun getMatterStorageLevel(): Decimal {
		var level = Decimal.ZERO

		for (node in nodes) {
			val matter = node.getMatterHandler()

			if (matter != null && matter.matterFlow == FlowDirection.BI_DIRECTIONAL) {
				level += matter.storedMatter
			}
		}

		return level
	}

	fun getMatterStorageMaxLevel(): Decimal {
		var level = Decimal.ZERO

		for (node in nodes) {
			val matter = node.getMatterHandler()

			if (matter != null && matter.matterFlow == FlowDirection.BI_DIRECTIONAL) {
				level += matter.maxStoredMatter
			}
		}

		return level
	}

	fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal {
		if (howMuch <= Decimal.ZERO)
			return Decimal.ZERO

		@Suppress("name_shadowing")
		var howMuch = howMuch
		var extracted = Decimal.ZERO

		for (node in nodes) {
			val matter = node.getMatterHandler()

			if (matter != null) {
				val value = matter.extractMatterChecked(howMuch, simulate)
				howMuch -= value
				extracted += value

				if (howMuch <= Decimal.ZERO)
					return extracted
			}
		}

		return extracted
	}

	fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal {
		if (howMuch <= Decimal.ZERO)
			return Decimal.ZERO

		@Suppress("name_shadowing")
		var howMuch = howMuch
		var received = Decimal.ZERO

		for (node in nodes) {
			val matter = node.getMatterHandler()

			if (matter != null && matter.matterFlow == FlowDirection.BI_DIRECTIONAL) {
				val value = matter.receiveMatterChecked(howMuch, simulate)
				howMuch -= value
				received += value

				if (howMuch <= Decimal.ZERO)
					return received
			}
		}

		return received
	}

	fun receiveMatterForce(howMuch: Decimal, simulate: Boolean): Decimal {
		if (howMuch <= Decimal.ZERO)
			return Decimal.ZERO

		@Suppress("name_shadowing")
		var howMuch = howMuch
		var received = Decimal.ZERO

		for (node in nodes) {
			val matter = node.getMatterHandler()

			if (matter != null && matter.matterFlow != FlowDirection.OUTPUT) {
				val value = matter.receiveMatterChecked(howMuch, simulate)
				howMuch -= value
				received += value

				if (howMuch <= Decimal.ZERO)
					return received
			}
		}

		return received
	}

	private fun doInsertPattern(state: IPatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus {
		for (node in nodes) {
			val storage = node.getPatternHandler()

			if (storage != null) {
				val status = storage.insertPattern(state, onlyUpdate, simulate)

				if (!status.isFailed) {
					return status
				}
			}
		}

		return PatternInsertFailure
	}

	fun insertPattern(state: IPatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus {
		if (onlyUpdate) return doInsertPattern(state, true, simulate)
		val status = doInsertPattern(state, true, simulate)
		if (!status.isFailed) return status
		return doInsertPattern(state, false, simulate)
	}

	val tasks: Stream<IReplicationTask<*>> get() {
		return nodes.stream().map { it.getTaskHandler()?.replicationTasks }.filterNotNull().flatMap { it }
	}

	val allTasks: Stream<IReplicationTask<*>> get() {
		return nodes.stream().map { it.getTaskHandler()?.allReplicationTasks }.filterNotNull().flatMap { it }
	}

	val patterns: Stream<IPatternState> get() {
		return nodes.stream().map { it.getPatternHandler()?.patterns }.filterNotNull().flatMap { it }
	}

	val patternCount: Long get() {
		var value = 0L

		for (node in nodes) {
			val storage = node.getPatternHandler()

			if (storage != null) {
				value += storage.storedPatterns
			}
		}

		return value
	}

	val patternCapacity: Long get() {
		var value = 0L

		for (node in nodes) {
			val storage = node.getPatternHandler()

			if (storage != null) {
				value += storage.patternCapacity
			}
		}

		return value
	}

	fun getPattern(id: UUID): IPatternState? {
		for (node in nodes) {
			val storage = node.getPatternHandler()

			if (storage != null) {
				val get = storage.getPattern(id)

				if (get != null) {
					return get
				}
			}
		}

		return null
	}

	fun getPattern(state: PatternState) = getPattern(state.id)
	fun hasPattern(state: PatternState) = getPattern(state.id) != null
	fun hasPattern(id: UUID) = getPattern(id) != null

	fun findPattern(predicate: Predicate<IPatternState>): IPatternState? {
		for (node in nodes) {
			val storage = node.getPatternHandler()

			if (storage != null) {
				val find = storage.patterns.filter(predicate).findAny()

				if (find.isPresent) {
					return find.get()
				}
			}
		}

		return null
	}

	fun findPattern(predicate: Item) = findPattern { it.item == predicate }

	fun findPatterns(predicate: Predicate<IPatternState>): List<IPatternState> {
		return patterns.filter(predicate).toList()
	}

	fun findPatterns(predicate: Item) = findPatterns { it.item == predicate }

	fun allocateTask(simulate: Boolean): ReplicationTaskAllocation? {
		for (node in nodes) {
			val tasks = node.getTaskHandler()

			if (tasks != null) {
				val allocated = tasks.allocateTask(simulate)

				if (allocated != null) {
					return allocated
				}
			}
		}

		return null
	}

	fun notifyTaskCompletion(taskId: UUID): Boolean {
		return nodes.any { it.getTaskHandler()?.notifyTaskCompletion(taskId) == true }
	}

	override fun onPatternAdded(state: IPatternState) {
		for (node in nodes) node.onPatternAdded(state)
		for (node in listeners) node.onPatternAdded(state)
	}

	override fun onPatternRemoved(state: IPatternState) {
		for (node in nodes) node.onPatternRemoved(state)
		for (node in listeners) node.onPatternRemoved(state)
	}

	override fun onPatternUpdated(newState: IPatternState, oldState: IPatternState) {
		for (node in nodes) node.onPatternUpdated(newState, oldState)
		for (node in listeners) node.onPatternUpdated(newState, oldState)
	}

	override fun onMatterTaskCreated(task: IReplicationTask<*>) {
		for (node in nodes) node.onMatterTaskCreated(task)
		for (node in listeners) node.onMatterTaskCreated(task)
	}

	override fun <T : IReplicationTask<*>> onMatterTaskUpdated(newState: T, oldState: T) {
		for (node in nodes) node.onMatterTaskUpdated(newState, oldState)
		for (node in listeners) node.onMatterTaskUpdated(newState, oldState)
	}

	override fun onMatterTaskFinished(state: IReplicationTask<*>) {
		for (node in nodes) node.onMatterTaskFinished(state)
		for (node in listeners) node.onMatterTaskFinished(state)
	}

	override fun onMatterTaskRemoved(state: IReplicationTask<*>) {
		for (node in nodes) node.onMatterTaskRemoved(state)
		for (node in listeners) node.onMatterTaskRemoved(state)
	}
}
