package ru.dbotthepony.mc.otm.registry

import com.google.common.collect.ImmutableSet
import com.google.common.collect.Streams
import net.minecraft.advancements.CriteriaTriggers
import net.minecraft.client.renderer.item.ItemProperties
import net.minecraft.core.BlockPos
import net.minecraft.core.Registry
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.entity.ai.village.poi.PoiTypes
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.material.MapColor
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
import net.minecraftforge.fml.loading.FMLEnvironment
import net.minecraftforge.registries.NewRegistryEvent
import net.minecraftforge.registries.RegisterEvent
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.android.AndroidResearchResult
import ru.dbotthepony.mc.otm.android.AndroidResearchResults
import ru.dbotthepony.mc.otm.android.AndroidFeatureType
import ru.dbotthepony.mc.otm.android.AndroidResearchDescription
import ru.dbotthepony.mc.otm.android.AndroidResearchDescriptions
import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock
import ru.dbotthepony.mc.otm.block.decorative.TritaniumPressurePlate
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.data.DecimalProvider
import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem
import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction
import ru.dbotthepony.mc.otm.matter.IMatterFunction
import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock
import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock
import ru.dbotthepony.mc.otm.registry.objects.StripedColoredDecorativeBlock
import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger
import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger
import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger
import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger
import ru.dbotthepony.mc.otm.triggers.BlackHoleTrigger
import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger
import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger
import ru.dbotthepony.mc.otm.triggers.ExopackGainedCraftingTrigger
import ru.dbotthepony.mc.otm.triggers.ExopackGainedSmeltingTrigger
import ru.dbotthepony.mc.otm.triggers.ExopackObtainedTrigger
import ru.dbotthepony.mc.otm.triggers.ExopackSlotsExpandedTrigger
import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger
import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger
import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger
import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger
import ru.dbotthepony.mc.otm.triggers.ShockwaveTrigger
import ru.dbotthepony.mc.otm.triggers.TakeItemOutOfReplicatorTrigger

object MRegistry {
	private val features = RegistryDelegate<AndroidFeatureType<*>>("android_features")
	val ANDROID_FEATURES by features
	val ANDROID_FEATURES_LOCATION get() = features.location
	val ANDROID_FEATURES_KEY get() = features.key

	private fun register(event: NewRegistryEvent) {
		features.build(event)
	}

	val CARGO_CRATES = DecorativeBlock(MNames.CARGO_CRATE, ::CargoCrateBlock)

	val DECORATIVE_CRATE = DecorativeBlock.simple(MNames.DECORATIVE_CRATE) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.SNOW)
			.sound(SoundType.METAL)
			.requiresCorrectToolForDrops()
			.explosionResistance(10f)
			.destroyTime(1f)
	}

	val TRITANIUM_BLOCK = DecorativeBlock.simple(MNames.TRITANIUM_BLOCK) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.COLOR_LIGHT_BLUE)
			.sound(SoundType.BASALT)
			.requiresCorrectToolForDrops()
			.explosionResistance(80f)
			.destroyTime(2.5f)
	}

	val TRITANIUM_STAIRS = DecorativeBlock(MNames.TRITANIUM_STAIRS) {
		StairBlock(
			{ TRITANIUM_BLOCK.allBlocks[it]!!.defaultBlockState() },
			BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!)
		)
	}

	val TRITANIUM_SLAB = DecorativeBlock(MNames.TRITANIUM_SLAB) {
		SlabBlock(BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!))
	}

	val TRITANIUM_WALL = DecorativeBlock(MNames.TRITANIUM_WALL) {
		WallBlock(BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!))
	}

	val TRITANIUM_PRESSURE_PLATE = DecorativeBlock(MNames.TRITANIUM_PRESSURE_PLATE, ::TritaniumPressurePlate)

	val VENT = DecorativeBlock.simple(MNames.VENT) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.COLOR_LIGHT_BLUE)
			.sound(SoundType.BASALT)
			.requiresCorrectToolForDrops()
			.explosionResistance(20f)
			.destroyTime(1.5f)
	}

	val VENT_ALTERNATIVE = DecorativeBlock.simple(MNames.VENT_ALTERNATIVE) {
		BlockBehaviour.Properties.of()
			.mapColor(it?.mapColor ?: MapColor.COLOR_LIGHT_BLUE)
			.sound(SoundType.BASALT)
			.requiresCorrectToolForDrops()
			.explosionResistance(20f)
			.destroyTime(1.5f)
	}

	val FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.FLOOR_TILES) {
		BlockBehaviour.Properties.of()
			.mapColor(it.mapColor)
			.sound(SoundType.STONE)
			.requiresCorrectToolForDrops()
			.strength(1.5f, 6f)
	}

	val FLOOR_TILES_STAIRS = ColoredDecorativeBlock(MNames.FLOOR_TILES_STAIRS) {
		StairBlock(
			{ FLOOR_TILES.blocks[it]!!.defaultBlockState() },
			BlockBehaviour.Properties.copy(FLOOR_TILES.blocks[it]!!)
		)
	}

	val FLOOR_TILES_SLAB = ColoredDecorativeBlock(MNames.FLOOR_TILES_SLAB) {
		SlabBlock(BlockBehaviour.Properties.copy(FLOOR_TILES.blocks[it]!!))
	}

	val UNREFINED_FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.UNREFINED_FLOOR_TILES) {
		BlockBehaviour.Properties.of()
			.mapColor(it.mapColor)
			.sound(SoundType.GRAVEL)
			.strength(1f, 2f)
	}

	val INDUSTRIAL_GLASS = DecorativeBlock(MNames.INDUSTRIAL_GLASS) { color ->
		val properties =
			BlockBehaviour.Properties.of()
				.mapColor(if (color != null) color.mapColor else MapColor.NONE)
				.instrument(NoteBlockInstrument.HAT)
				.destroyTime(1.5f)
				.explosionResistance(40f)
				.requiresCorrectToolForDrops()
				.sound(SoundType.GLASS)
				.noOcclusion()
				.isValidSpawn { _: BlockState, _: BlockGetter, _: BlockPos, _: EntityType<*>? -> false }
				.isRedstoneConductor { _: BlockState, _: BlockGetter, _: BlockPos -> false }
				.isSuffocating { _: BlockState, _: BlockGetter, _: BlockPos -> false }
				.isViewBlocking { _: BlockState, _: BlockGetter, _: BlockPos -> false }

		if (color != null) {
			return@DecorativeBlock StainedGlassBlock(color, properties)
		}

		return@DecorativeBlock GlassBlock(properties)
	}

	val INDUSTRIAL_GLASS_PANE = DecorativeBlock(MNames.INDUSTRIAL_GLASS_PANE) { color ->
		val properties =
			BlockBehaviour.Properties.of()
				.mapColor(if (color != null) color.mapColor else MapColor.NONE)
				.instrument(NoteBlockInstrument.HAT)
				.strength(1.25f, 5.0f)
				.requiresCorrectToolForDrops()
				.sound(SoundType.GLASS)
				.noOcclusion()

		if (color != null) {
			return@DecorativeBlock StainedGlassPaneBlock(color, properties)
		}

		return@DecorativeBlock IronBarsBlock(properties)
	}

	val TRITANIUM_STRIPED_BLOCK = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_BLOCK, { colorA, _ ->
		Block(BlockBehaviour.Properties.of()
			.mapColor(colorA.mapColor)
			.sound(SoundType.BASALT)
			.requiresCorrectToolForDrops()
			.explosionResistance(80f)
			.strength(4f))
	})

	val TRITANIUM_STRIPED_STAIRS = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_STAIRS, { colorA, colorB ->
		StairBlock({ TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB).defaultBlockState() }, BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
	})

	val TRITANIUM_STRIPED_SLAB = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_SLAB, { colorA, colorB ->
		SlabBlock(BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
	})

	val TRITANIUM_STRIPED_WALL = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_WALL, { colorA, colorB ->
		WallBlock(BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB)))
	})

	private fun registerEvent(event: RegisterEvent) {
		// mojang moment

		if (event.registryKey == Registries.POINT_OF_INTEREST_TYPE) {
			val reg = event.getVanillaRegistry<PoiType>() ?: throw IllegalStateException("POI registry is not a vanilla registry")

			event.register(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.BUTCHER.location()) {
				val old = reg[PoiTypes.BUTCHER] ?: throw IllegalStateException("POI with type ${PoiTypes.ARMORER} does not exist")

				if (old.`is`(MBlocks.POWERED_SMOKER.defaultBlockState())) {
					old
				} else {
					PoiType(Streams.concat(old.matchingStates.stream(), MBlocks.POWERED_SMOKER.stateDefinition.possibleStates.stream()).collect(ImmutableSet.toImmutableSet()), old.maxTickets, old.validRange)
				}
			}

			event.register(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.ARMORER.location()) {
				val old = reg[PoiTypes.ARMORER] ?: throw IllegalStateException("POI with type ${PoiTypes.ARMORER} does not exist")

				if (old.`is`(MBlocks.POWERED_BLAST_FURNACE.defaultBlockState())) {
					old
				} else {
					PoiType(Streams.concat(old.matchingStates.stream(), MBlocks.POWERED_BLAST_FURNACE.stateDefinition.possibleStates.stream()).collect(ImmutableSet.toImmutableSet()), old.maxTickets, old.validRange)
				}
			}
		}
	}

	fun initialize(bus: IEventBus) {
		bus.addListener(this::register)
		bus.addListener(this::initializeClient)
		bus.addListener(this::initializeCommon)
		bus.addListener(MStats::registerVanilla)
		bus.addListener(this::registerEvent)

		MCreativeTabs.initialize(bus)

		DecimalProvider.register(bus)
		AndroidResearchDescription.register(bus)
		AndroidResearchDescriptions.register(bus)
		AndroidResearchResult.register(bus)
		AndroidResearchResults.register(bus)

		AbstractRegistryAction.register(bus)
		IMatterFunction.register(bus)

		MBlocks.register(bus)
		MBlockEntities.register(bus)
		MEntityTypes.register(bus)
		MMenus.register(bus)
		MItems.register(bus)
		AndroidFeatures.register(bus)
		MSoundEvents.register(bus)
		LootModifiers.register(bus)
		MItemFunctionTypes.register(bus)
		MLootItemConditions.register(bus)

		if (FMLEnvironment.dist == Dist.CLIENT) {
			MBlockColors.register(bus)
		}

		MRecipes.register(bus)

		// call static constructors
		NanobotsArmorFeature.Companion
		EnderTeleporterFeature.Companion
	}

	private fun initializeCommon(event: FMLCommonSetupEvent) {
		event.enqueueWork {
			CriteriaTriggers.register(BlackHoleTrigger)
			CriteriaTriggers.register(BecomeAndroidTrigger)
			CriteriaTriggers.register(BecomeAndroidDeathTrigger)
			CriteriaTriggers.register(BecomeAndroidSleepTrigger)
			CriteriaTriggers.register(BecomeHumaneTrigger)
			CriteriaTriggers.register(AndroidResearchTrigger)
			CriteriaTriggers.register(ShockwaveDamageMobTrigger)
			CriteriaTriggers.register(ShockwaveTrigger)
			CriteriaTriggers.register(AndroidBatteryTrigger)
			CriteriaTriggers.register(NanobotsArmorTrigger)
			CriteriaTriggers.register(FallDampenersSaveTrigger)
			CriteriaTriggers.register(EnderTeleporterFallDeathTrigger)
			CriteriaTriggers.register(KillAsAndroidTrigger)
			CriteriaTriggers.register(AndroidTravelUnderwater)
			CriteriaTriggers.register(NailedEntityTrigger)
			CriteriaTriggers.register(ExopackObtainedTrigger)
			CriteriaTriggers.register(ExopackGainedSmeltingTrigger)
			CriteriaTriggers.register(ExopackGainedCraftingTrigger)
			CriteriaTriggers.register(ExopackSlotsExpandedTrigger)
			CriteriaTriggers.register(ExopackBatterySlotTrigger)
			CriteriaTriggers.register(TakeItemOutOfReplicatorTrigger)
		}
	}

	private fun initializeClient(event: FMLClientSetupEvent) {
		event.enqueueWork {
			ItemProperties.register(MItems.TRITANIUM_SHIELD, ResourceLocation(OverdriveThatMatters.MOD_ID, "blocking")) { stack, level, entity, _ ->
				if (entity?.isUsingItem == true && entity.useItem == stack) {
					1f
				} else {
					0f
				}
			}

			ItemProperties.register(MItems.ENERGY_SWORD, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_powered")) { stack, _, _, _ ->
				if ((stack.matteryEnergy?.batteryLevel ?: Decimal.ZERO) >= EnergySwordItem.ENERGY_PER_SWING) {
					1f
				} else {
					0f
				}
			}

			ItemProperties.register(MItems.EXPLOSIVE_HAMMER, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_primed")) { stack, _, _, _ ->
				if (MItems.EXPLOSIVE_HAMMER.isPrimed(stack)) 1f else 0f
			}
		}
	}
}
