package ru.dbotthepony.mc.otm.recipe

import net.minecraft.core.NonNullList
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.crafting.Ingredient
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.collect.allEqual
import ru.dbotthepony.mc.otm.core.collect.any
import ru.dbotthepony.mc.otm.core.collect.flatMap
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.isActuallyEmpty
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.util.countingLazy
import java.util.function.Predicate

interface IIngredientMatrix : Predicate<CraftingContainer>, Iterable<Ingredient> {
	val width: Int
	val height: Int
	val isEmpty: Boolean
		get() = width == 0 || height == 0

	val isIncomplete: Boolean
		get() = iterator().any { it.isActuallyEmpty }

	operator fun get(column: Int, row: Int): Ingredient

	operator fun get(column: Int, row: Int, flop: Boolean): Ingredient {
		return if (flop)
			get(width - column - 1, row)
		else
			get(column, row)
	}

	override fun iterator(): Iterator<Ingredient> {
		return (0 until width).iterator().flatMap { x ->
			(0 until height).iterator().map { y ->
				get(x, y)
			}
		}
	}

	val ingredients: NonNullList<Ingredient> get() {
		val result = NonNullList.createWithCapacity<Ingredient>(width * height)

		for (x in 0 until width) {
			for (y in 0 until height) {
				result.add(get(x, y))
			}
		}

		return result
	}

	fun test(t: CraftingContainer, fromColumn: Int, fromRow: Int, flop: Boolean): Boolean {
		if (t.width - fromColumn < width || t.height - fromRow < height)
			return false

		for (column in 0 until width)
			for (row in 0 until height)
				if (!this[column, row, flop].test(t[fromColumn + column, fromRow + row, flop]))
					return false

		return true
	}

	fun preemptiveTest(t: CraftingContainer, fromColumn: Int, fromRow: Int, flop: Boolean): Boolean {
		if (t.width - fromColumn < width || t.height - fromRow < height)
			return false

		for (column in 0 until width) {
			for (row in 0 until height) {
				val item = t[fromColumn + column, fromRow + row, flop]
				val ingredient = this[column, row, flop]

				if (!ingredient.test(item) && item.isNotEmpty) {
					return false
				}
			}
		}

		return true
	}

	override fun test(t: CraftingContainer): Boolean {
		if (t.width < width || t.height < height)
			return false

		for (column in 0 .. t.width - width)
			for (row in 0 .. t.height - height)
				if (test(t, column, row, false) || test(t, column, row, true))
					return true

		return false
	}

	fun preemptiveTest(t: CraftingContainer): Boolean {
		if (t.width < width || t.height < height)
			return false

		for (column in 0 .. t.width - width)
			for (row in 0 .. t.height - height)
				if (preemptiveTest(t, column, row, false) || preemptiveTest(t, column, row, true))
					return true

		return false
	}

	companion object : IIngredientMatrix {
		override val width: Int
			get() = 0
		override val height: Int
			get() = 0

		override fun get(column: Int, row: Int): Ingredient {
			return Ingredient.EMPTY
		}
	}
}

class IngredientMatrix(override val width: Int, override val height: Int) : IIngredientMatrix {
	private val data = Array(width * height) { Ingredient.EMPTY }

	private val lazy = countingLazy(OverdriveThatMatters.INGREDIENT_CACHE_INVALIDATION_COUNTER) {
		super.isIncomplete
	}

	override val isIncomplete by lazy

	override fun iterator(): Iterator<Ingredient> {
		return data.iterator()
	}

	override fun get(column: Int, row: Int): Ingredient {
		require(column in 0 until width) { "Column out of bounds: $column (matrix width: $width)" }
		require(row in 0 until height) { "Row out of bounds: $row > (matrix height: $height)" }
		return data[column + row * width]
	}

	operator fun set(column: Int, row: Int, value: Ingredient) {
		require(column in 0 until width) { "Column out of bounds: $column (matrix width: $width)" }
		require(row in 0 until height) { "Row out of bounds: $row > (matrix height: $height)" }
		data[column + row * width] = value
		lazy.invalidate()
	}

	companion object {
		fun of(vararg values: Collection<Ingredient>): IngredientMatrix {
			if (!values.iterator().map { it.size }.allEqual()) {
				throw IllegalArgumentException("One or more rows have different number of columns than the rest")
			}

			val result = IngredientMatrix(values.first().size, values.size)

			for ((y, l) in values.withIndex()) {
				for ((x, i) in l.withIndex()) {
					result[x, y] = i
				}
			}

			return result
		}
	}
}
