package ru.dbotthepony.mc.otm.core.math

import com.google.common.collect.ImmutableMap
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Vec3i
import net.minecraft.util.StringRepresentable
import net.minecraft.world.level.block.Rotation
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.common.util.LazyOptional
import java.util.Collections
import java.util.EnumMap

internal inline val Direction.blockRotation
	get() = BlockRotation.of(this)

fun <T> ICapabilityProvider.getCapability(capability: Capability<T>, side: BlockRotation?): LazyOptional<T> {
	return getCapability(capability, side?.front)
}

operator fun Vec3i.plus(other: BlockRotation): Vec3i {
	return this + other.normal
}

operator fun BlockPos.plus(other: BlockRotation): BlockPos {
	return this + other.normal
}

enum class RelativeSide {
	FRONT, BACK, LEFT, RIGHT, TOP, BOTTOM
}

/**
 * Represents unique block orientation in space, by providing [front] and [top] directions
 *
 * Allows to get relative faces through [left], [right], [bottom] and [back]
 *
 * @see BlockRotationFreedom
 */
enum class BlockRotation(
	val front: Direction,
	val top: Direction,
) : StringRepresentable {
	DOWN(Direction.DOWN, Direction.NORTH),
	UP(Direction.UP, Direction.NORTH),
	NORTH(Direction.NORTH, Direction.UP),
	SOUTH(Direction.SOUTH, Direction.UP),
	WEST(Direction.WEST, Direction.UP),
	EAST(Direction.EAST, Direction.UP),

	DOWN_SOUTH(Direction.DOWN, Direction.SOUTH),
	DOWN_WEST(Direction.DOWN, Direction.WEST),
	DOWN_EAST(Direction.DOWN, Direction.EAST),

	UP_SOUTH(Direction.UP, Direction.SOUTH),
	UP_WEST(Direction.UP, Direction.WEST),
	UP_EAST(Direction.UP, Direction.EAST),

	/**
	 * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis)
	 */
	NORTH_DOWN(Direction.NORTH, Direction.DOWN),

	/**
	 * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis)
	 */
	SOUTH_DOWN(Direction.SOUTH, Direction.DOWN),

	/**
	 * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis)
	 */
	WEST_DOWN(Direction.WEST, Direction.DOWN),

	/**
	 * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis)
	 */
	EAST_DOWN(Direction.EAST, Direction.DOWN),
	;

	val right: Direction

	init {
		val crossproduct = front.normal.cross(top.normal)
		right = Direction.values().first { it.normal == crossproduct }
	}

	val left: Direction = right.opposite
	val bottom: Direction = top.opposite
	val back: Direction = front.opposite

	private val _dir2Side = EnumMap<Direction, RelativeSide>(Direction::class.java)
	private val _side2Dir = EnumMap<RelativeSide, Direction>(RelativeSide::class.java)

	val dir2Side: Map<Direction, RelativeSide> = Collections.unmodifiableMap(_dir2Side)
	val side2Dir: Map<RelativeSide, Direction> = Collections.unmodifiableMap(_side2Dir)

	init {
		_side2Dir[RelativeSide.FRONT] = front
		_side2Dir[RelativeSide.BACK] = back
		_side2Dir[RelativeSide.LEFT] = left
		_side2Dir[RelativeSide.RIGHT] = right
		_side2Dir[RelativeSide.TOP] = top
		_side2Dir[RelativeSide.BOTTOM] = bottom

		_dir2Side[front] = RelativeSide.FRONT
		_dir2Side[back] = RelativeSide.BACK
		_dir2Side[left] = RelativeSide.LEFT
		_dir2Side[right] = RelativeSide.RIGHT
		_dir2Side[top] = RelativeSide.TOP
		_dir2Side[bottom] = RelativeSide.BOTTOM
	}

	fun dir2Side(direction: Direction): RelativeSide {
		return _dir2Side[direction]!!
	}

	fun side2Dir(side: RelativeSide): Direction {
		return _side2Dir[side]!!
	}

	operator fun component1() = front
	operator fun component2() = top
	operator fun component3() = left

	val lowercaseName = name.lowercase()

	override fun getSerializedName(): String {
		return lowercaseName
	}

	fun rotate(by: Rotation): BlockRotation {
		return of(by.rotate(front), top)
	}

	operator fun times(other: Int): Vec3i {
		return normal * other
	}

	operator fun plus(other: Vec3i): Vec3i {
		return normal + other
	}

	operator fun plus(other: Direction): Vec3i {
		return normal + other
	}

	operator fun plus(other: BlockRotation): Vec3i {
		return normal + other.normal
	}

	operator fun unaryMinus() = opposite

	// more performant
	val opposite by lazy { of(front.opposite, top.opposite) }
	val normal: Vec3i get() = front.normal

	val oppositeFront by lazy { of(front.opposite, top) }

	companion object {
		@JvmStatic
		fun of(direction: Direction): BlockRotation {
			return when (direction) {
				Direction.DOWN -> DOWN
				Direction.UP -> UP
				Direction.NORTH -> NORTH
				Direction.SOUTH -> SOUTH
				Direction.WEST -> WEST
				Direction.EAST -> EAST
			}
		}

		private val mapped = EnumMap<Direction, EnumMap<Direction, BlockRotation>>(Direction::class.java)

		init {
			for (value in values()) {
				val (front, top) = value
				mapped.computeIfAbsent(front) { EnumMap(Direction::class.java) }.put(top, value)
			}
		}

		@JvmStatic
		fun of(front: Direction, top: Direction?): BlockRotation {
			val f = mapped[front]!!
			return f[top ?: (if (front == Direction.DOWN || front == Direction.UP) Direction.NORTH else Direction.UP)] ?: throw IllegalArgumentException("Impossible block rotation: $front - $top")
		}

		@JvmStatic
		fun ofSafe(front: Direction, top: Direction?): BlockRotation {
			val f = mapped[front]!!
			val computeTop = if (front == Direction.DOWN || front == Direction.UP) Direction.NORTH else Direction.UP
			return f[top ?: computeTop] ?: f[computeTop] ?: f.values.first()
		}
	}
}
