
# Imports
from __future__ import annotations

from stouputils.print import debug, error

from ..__memory__ import Mem
from ..constants import official_lib_used
from ..utils.io import write_function


class CustomOreGeneration:
	def __init__(
		self,
		dimensions: list[str],
		maximum_height: int = 70,
		minimum_height: int | None = None,
		veins_per_region: int = 4,
		vein_size_logic: float = 0.4,
		provider: str | list[str] = "#minecraft:overworld_carver_replaceables",
		placer_command: str = ""
	) -> None:
		""" Creates a custom ore generation configuration.

		Args:
			dimensions (list[str]):			The dimensions to generate the ore in (ex: ["minecraft:overworld"])
			maximum_height (int):			The maximum height the ore can generate at
			minimum_height (int|None):		The minimum height the ore can generate at (default: None for bedrock to maximum_height)
			veins_per_region (int):			The number of veins per region
			vein_size_logic (float):		The logic to calculate the vein size, higher = more ores
			provider (str|list[str]):		The block or block tag to check for the ore to generate (default is same as vanilla ores in overworld), can be either a string or a list of strings
			placer_command (str):			The placer command to use for the ore generation, usually a function to place the custom ore but can be anything, default is autogenerated
		"""
		self.dimensions: list[str] = dimensions
		self.maximum_height: int = maximum_height
		self.minimum_height: int|None = minimum_height
		self.veins_per_region: int = veins_per_region
		self.vein_size_logic: float = vein_size_logic
		self.provider: str|list[str] = provider
		self.placer_command: str = placer_command
		if not official_lib_used("smart_ore_generation"):
			debug("Found custom ore generation, adding 'smart_ore_generation' dependency")
		self.check_validity()

	def check_validity(self) -> None:
		""" Check if the custom ore generation configuration is valid """
		if not isinstance(self.dimensions, list) or not all(isinstance(dimension, str) for dimension in self.dimensions):
			error("Custom ore generation 'dimensions' must be a list of strings")
		if not isinstance(self.maximum_height, int):
			error("Custom ore generation 'maximum_height' must be an integer")
		if self.minimum_height is not None and not isinstance(self.minimum_height, int):
			error("Custom ore generation 'minimum_height' must be an integer or None")
		elif self.minimum_height is not None and self.minimum_height > self.maximum_height:
			error("Custom ore generation 'minimum_height' must be less or equal to 'maximum_height'")
		if not isinstance(self.veins_per_region, int):
			error("Custom ore generation 'veins_per_region' must be an integer")
		if not isinstance(self.vein_size_logic, float) or self.vein_size_logic < 0:
			error("Custom ore generation 'vein_size_logic' must be a float >= 0")
		if not isinstance(self.provider, str) and not isinstance(self.provider, list):
			error("Custom ore generation 'provider' must be a string or a list of strings")
		if isinstance(self.provider, list) and not all(isinstance(provider, str) for provider in self.provider):
			error("Custom ore generation 'provider' must be a list of strings")
		if not isinstance(self.placer_command, str):
			error("Custom ore generation 'placer_command' must be a string")

	def generate_files(self, custom_ore: str, number: int | None = None) -> None:
		""" Generate the files for the custom ore generation.

		Args:
			custom_ore (str):	The custom ore to generate, ex: "adamantium_ore"
			number (int|None):	(optional) The number of the custom ore to generate, ex: 1 for "adamantium_ore_1", used when having multiple configurations for the same ore
		"""
		# Setup basic variables
		beautify_ore: str = custom_ore.replace("_", " ").title()
		vein_name: str = custom_ore + ('' if number is None else f"_{number}")
		vein_path: str = f"{Mem.ctx.project_id}:calls/smart_ore_generation/veins/{vein_name}"

		# Setup placer command
		self._setup_placer_command(custom_ore)

		# Generate main ore generation function
		self._generate_main_function(beautify_ore, vein_path)

		# Generate vein function
		self._generate_vein_function(beautify_ore, vein_path)

	def _setup_placer_command(self, custom_ore: str) -> None:
		""" Setup the placer command for the ore generation.

		Args:
			custom_ore (str):	The custom ore to generate
		"""
		if not self.placer_command:
			self.placer_command = f"run function {Mem.ctx.project_id}:custom_blocks/{custom_ore}/place_main"

			# Handle deepslate variants
			if custom_ore.startswith("deepslate_") and custom_ore.replace("deepslate_", "") in Mem.definitions:
				self.placer_command = "unless block ~ ~ ~ minecraft:stone " + self.placer_command
			elif f"deepslate_{custom_ore}" in Mem.definitions:
				self.placer_command = "unless block ~ ~ ~ minecraft:deepslate " + self.placer_command

	def _generate_main_function(self, beautify_ore: str, vein_path: str) -> None:
		""" Generate the main ore generation function.

		Args:
			beautify_ore (str):	The beautified ore name
			vein_path (str):	The path to the vein function
		"""
		# Generate dimensions check
		dimensions_check: str = ""
		for index, dimension in enumerate(self.dimensions):
			dimensions_check += f"\nexecute if dimension {dimension} run scoreboard players set #dimension smart_ore_generation.data {index}"

		# Generate minimum height operation
		op_mini: str = (
			"scoreboard players operation #min_height smart_ore_generation.data = _OVERWORLD_BOTTOM smart_ore_generation.data"
			if self.minimum_height is None
			else f"scoreboard players set #min_height smart_ore_generation.data {self.minimum_height}"
		)

		# Generate content
		content: str = f"""
# Generate {beautify_ore} (x{self.veins_per_region})
scoreboard players set #dimension smart_ore_generation.data -1{dimensions_check}
{op_mini}
scoreboard players set #max_height smart_ore_generation.data {self.maximum_height}
"""
		place_vein: str = f"execute if score #dimension smart_ore_generation.data matches 0.. run function {vein_path}\n"
		content += self.veins_per_region * place_vein

		# Write file
		write_function(f"{Mem.ctx.project_id}:calls/smart_ore_generation/generate_ores", content)

	def _generate_vein_function(self, beautify_ore: str, vein_path: str) -> None:
		""" Generate the vein function for ore generation.

		Args:
			beautify_ore (str):	The beautified ore name
			vein_path (str):	The path to the vein function
		"""
		# Generate base content
		content: str = f"""
# Try to find a random position adjacent to air in the region to generate the ore
function #smart_ore_generation:v1/slots/random_position

# Placing {beautify_ore} patch
execute at @s if block ~ ~ ~ {self.provider} {self.placer_command}
"""

		# Add vein size logic if needed
		if self.vein_size_logic > 0:
			radius: float = self.vein_size_logic % 1
			while radius <= self.vein_size_logic:
				# Make a sphere of radius
				for x in range(-1, 2):
					for y in range(-1, 2):
						for z in range(-1, 2):
							content += f"execute at @s positioned ~{x*radius} ~{y*radius} ~{z*radius} if block ~ ~ ~ {self.provider} {self.placer_command}\n"
				radius += 1

		# Write file
		write_function(vein_path, content)

	@staticmethod
	def all_with_config(ore_configs: dict[str, list[CustomOreGeneration]]) -> None:
		""" Generate all custom ore generation files with the configurations provided

		Args:
			ore_configs (dict[str, list[CustomOreGeneration]]):	The custom ore generation configurations, ex:

		```py
		{
			"super_iron_ore": [
				CustomOreGeneration(
					dimensions = ["minecraft:overworld","stardust:cavern","some_other:dimension"],
					maximum_height = 50,
					minimum_height = 0,
					veins_per_region = 2,
					vein_size_logic = 0.4,
				)
			],
			"deepslate_super_iron_ore": [
				CustomOreGeneration(
					dimensions = ["minecraft:overworld"],
					maximum_height = 0,
					veins_per_region = 2,
					vein_size_logic = 0.4,
				),
				CustomOreGeneration(
					dimensions = ["stardust:cavern"],
					maximum_height = 0,
					veins_per_region = 8,
					vein_size_logic = 0.8,
				)
			], ...
		}
		```
		"""
		for ore, config_list in ore_configs.items():
			for i, gen_config in enumerate(config_list):
				if len(config_list) > 1:
					gen_config.generate_files(ore, i)
				else:
					gen_config.generate_files(ore)
		return

