package ru.dbotthepony.mc.otm.graph.storage

import net.minecraft.world.level.block.entity.BlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.storage.IStorage
import ru.dbotthepony.mc.otm.storage.IStorageStack
import ru.dbotthepony.mc.otm.storage.StorageStackType
import java.util.*

open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : Graph6Node<StorageNode, StorageGraph>(::StorageGraph) {
	protected val components = ArrayList<IStorage<*>>()

	/**
	 * Setting this to true will render non functional default [attachComponents],
	 * make [addStorageComponent] and [removeStorageComponent] not add/remove components
	 * to attached graph, and [revive] not re-attach components.
	 *
	 * [invalidate] and [removeComponents] still detach all components
	 */
	protected var manualAttaching = false
	private var demandingEnergy = false

	/**
	 * Called by storage graph on node being attached to it
	 */
	open fun attachComponents(to: StorageGraph) {
		if (energyDemander != null) {
			if (energyDemander.missingPower.isPositive) {
				demandingEnergy = true
				to.powerDemandingNodes.add(energyDemander)
			}
		}

		if (manualAttaching) {
			return
		}

		if (components != null)
			for (component in components) {
				to.add(component)
			}
	}

	/**
	 * Called by storage graph on node being detached from it
	 *
	 * This is NOT called when graph is being destroyed (e.g. even by merging
	 * with another graph).
	 */
	open fun removeComponents(from: StorageGraph) {
		for (component in components) {
			from.remove(component)
		}

		if (energyDemander != null) {
			from.powerDemandingNodes.remove(energyDemander)
			demandingEnergy = false
		}
	}

	fun tickEnergyDemanding() {
		energyDemander ?: throw IllegalStateException("No energy demander")

		if (!demandingEnergy && energyDemander.missingPower.isPositive) {
			demandingEnergy = true
			graph.powerDemandingNodes.add(energyDemander)
		} else if (demandingEnergy && !energyDemander.missingPower.isPositive) {
			demandingEnergy = false
			graph.powerDemandingNodes.remove(energyDemander)
		}
	}

	@Suppress("unchecked_cast")
	fun <T : IStorageStack, U : IStorage<T>> computeIfAbsent(identity: StorageStackType<T>, provider: (StorageStackType<T>) -> U): U {
		for (component in components) {
			if (component.storageType === identity) {
				return component as U
			}
		}

		val factory = provider(identity)
		addStorageComponent(factory)
		return factory
	}

	fun addStorageComponent(component: IStorage<*>) {
		for (component1 in components) {
			if (component === component1 || component1.storageType === component.storageType) {
				return
			}
		}

		components.add(component)

		if (isValid && !manualAttaching)
			graph.add(component)
	}

	fun removeStorageComponent(component: IStorage<*>) {
		var indexOf = -1

		for ((i, component1) in components.withIndex()) {
			if (component === component1 || component1.storageType === component.storageType) {
				indexOf = i
				break
			}
		}

		if (indexOf == -1) {
			return
		}

		val self = components[indexOf]
		components.removeAt(indexOf)

		if (isValid)
			graph.remove(self)
	}

	fun removeStorageComponent(component: StorageStackType<*>) {
		var indexOf = -1

		for ((i, component1) in components.withIndex()) {
			if (component1.storageType === component) {
				indexOf = i
				break
			}
		}

		if (indexOf == -1) {
			return
		}

		val self = components[indexOf]
		components.removeAt(indexOf)

		if (isValid && !manualAttaching)
			graph.remove(self)
	}

	override fun invalidate() {
		for (component in components) {
			graph.remove(component)
		}
	}

	override fun revive() {
		if (!manualAttaching) {
			for (component in components) {
				graph.add(component)
			}
		}
	}

	fun discover(blockEntity: BlockEntity) {
		discover(blockEntity, MatteryCapability.STORAGE_NODE)
	}
}
