package ru.dbotthepony.mc.otm.capability

import com.google.common.collect.ImmutableSet
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import java.util.function.Predicate

/**
 * Represents possible flow direction, be it matter, energy, fluids, etc
 *
 * To dynamically get this enum by two booleans (by input and output states), use [FlowDirection.of]
 *
 * Also, this enum implements [Predicate], you can check incoming flow direction for "compatibility" with
 * this flow direction, more precisely:
 * [test] will return true and only true if [input] and [output] of incoming flow are assignable to this flow
 *
 * Example:
 * * `INPUT.test(OUTPUT)` = `false`
 * * `OUTPUT.test(INPUT)` = `false`
 * * `INPUT.test(BI_DIRECTIONAL)` = `true`
 * * `OUTPUT.test(BI_DIRECTIONAL)` = `true`
 * * `BI_DIRECTIONAL.test(BI_DIRECTIONAL)` = `true`
 * * `BI_DIRECTIONAL.test(OUTPUT)` = `false`
 * * `BI_DIRECTIONAL.test(INPUT)` = `false`
 */
enum class FlowDirection(val input: Boolean, val output: Boolean, val translationKey: String) : Predicate<FlowDirection> {
	/**
	 * Can only be inputted (consumer)
	 */
	INPUT(true, false, "otm.gui.side_mode.input"),

	/**
	 * Can only be outputted/transmitted (producer)
	 */
	OUTPUT(false, true, "otm.gui.side_mode.output"),

	/**
	 * Can both consume and produce (capacitor)
	 */
	BI_DIRECTIONAL(true, true, "otm.gui.side_mode.input_output"),

	/**
	 * No flow possible
	 */
	NONE(false, false, "otm.gui.side_mode.disabled");

	/**
	 * All values that pass [isSupertype]
	 */
	val family: ImmutableSet<FlowDirection> by lazy {
		ImmutableSet.Builder<FlowDirection>().also {
			when (this) {
				INPUT -> it.add(INPUT)
				OUTPUT -> it.add(OUTPUT)
				BI_DIRECTIONAL -> {
					it.add(INPUT)
					it.add(OUTPUT)
					it.add(BI_DIRECTIONAL)
				}
				else -> {}
			}

			it.add(NONE)
		}.build()
	}

	val translation: Component
		get() = TranslatableComponent(translationKey)

	/**
	 * Subtype test (returns true if we can assign [t] to this, for example if we can assign [BI_DIRECTIONAL] to [INPUT])
	 */
	override fun test(t: FlowDirection): Boolean {
		return t === this || (!input || t.input) && (!output || t.output)
	}

	/**
	 * Subtype test (returns true if we can assign [value] to this, for example if we can assign [BI_DIRECTIONAL] to [INPUT])
	 */
	fun isSubtype(value: FlowDirection) = test(value)

	/**
	 * Supertype test (e.g. [INPUT] is supertype to [BI_DIRECTIONAL], so if this is [BI_DIRECTIONAL] then calling [isSupertype] with [INPUT] will return `true`)
	 */
	fun isSupertype(value: FlowDirection): Boolean {
		return value === this || value in family
	}

	fun intersect(other: FlowDirection): FlowDirection {
		return of(other.input && input, other.output && output)
	}

	fun withInput(flag: Boolean): FlowDirection {
		if (flag == input)
			return this

		return of(flag, output)
	}

	fun withOutput(flag: Boolean): FlowDirection {
		if (flag == output)
			return this

		return of(input, flag)
	}

	companion object {
		@JvmStatic
		fun of(input: Boolean, output: Boolean): FlowDirection {
			if (input && output) {
				return BI_DIRECTIONAL
			} else if (input) {
				return INPUT
			} else if (output) {
				return OUTPUT
			} else {
				return NONE
			}
		}

		/**
		 * @return [INPUT] if [flag] is true, [OUTPUT] otherwise
		 */
		@JvmStatic
		fun input(flag: Boolean): FlowDirection {
			return of(flag, !flag)
		}

		/**
		 * @return [OUTPUT] if [flag] is true, [INPUT] otherwise
		 */
		@JvmStatic
		fun output(flag: Boolean): FlowDirection {
			return of(!flag, flag)
		}

		/**
		 * @return [INPUT] if [flag] is true, [NONE] otherwise
		 */
		@JvmStatic
		fun orInput(flag: Boolean): FlowDirection {
			return of(flag, false)
		}

		/**
		 * @return [OUTPUT] if [flag] is true, [NONE] otherwise
		 */
		@JvmStatic
		fun orOutput(flag: Boolean): FlowDirection {
			return of(false, flag)
		}
	}
}
