extends Node

class_name Inventory

@export var open_on_top: bool
var inventory: Dictionary[StringName,RuntimeSubstanceData]

var mean_temperature: float:
	set(new_temperature):
		mean_temperature = new_temperature
		temperature_changed.emit(new_temperature)
		if new_temperature != 0:
			update_temperature()

var total_amount: float:
	set(value):
		pass
	get:
		var result: float = 0
		for sub in inventory:
			result += inventory[sub].amount
		return result

signal inventory_changed(substances: Array[RuntimeSubstanceData])
signal temperature_changed(to: float)

func update_solutions():
	var has_water = inventory.has("Вода")
	if has_water:
		for sub in inventory:
			inventory[sub].is_solution = ReactionRegistry.is_water_solution(inventory[sub].substance)

func add_substance(data: RuntimeSubstanceData) -> void:
	if data.substance.scientific_name in inventory:
		inventory[data.substance.scientific_name].add(data)
	else:
		inventory[data.substance.scientific_name] = data
	check_for_reactions()
	recalculate_temperature()
	update_solutions()
	inventory_changed.emit(inventory.values())

func add_substance_no_callback(data: RuntimeSubstanceData) -> void:
	if data.substance.scientific_name in inventory:
		inventory[data.substance.scientific_name].add(data)
	else:
		inventory[data.substance.scientific_name] = data
	

func add_inventory(substances: Array[RuntimeSubstanceData]) -> void:
	for sub in substances:
		add_substance_no_callback(sub)
	check_for_reactions()
	recalculate_temperature()
	update_solutions()
	inventory_changed.emit(inventory.values())

func extract(amount: float) -> Array[RuntimeSubstanceData]:
	if len(inventory) == 0:
		return []
	var result: Array[RuntimeSubstanceData] = []
	var extract_amount = amount/len(inventory)
	var sorted_by_amount: Array[RuntimeSubstanceData] = inventory.values()
	sorted_by_amount.sort_custom(sort_by_amount)
	
	for substance: RuntimeSubstanceData in sorted_by_amount:
		var result_substance: RuntimeSubstanceData = substance.duplicate()
		if substance.amount >= extract_amount:
			result_substance.amount = extract_amount
			inventory[result_substance.substance.scientific_name].amount -= extract_amount
		else:
			extract_amount += (extract_amount - result_substance.amount)/len(sorted_by_amount)
			inventory[result_substance.substance.scientific_name].amount = 0
		result.append(result_substance)
		if inventory[result_substance.substance.scientific_name].amount == 0:
			inventory.erase(result_substance.substance.scientific_name)
	
	update_solutions()
	inventory_changed.emit(inventory.values())
	
	return result

func extract_solid(amount: float) -> Array[RuntimeSubstanceData]:
	if len(inventory) == 0:
		return []
	var result: Array[RuntimeSubstanceData] = []
	var extract_amount = amount/len(inventory)
	var sorted_by_amount: Array[RuntimeSubstanceData] = inventory.values()
	sorted_by_amount.sort_custom(sort_by_amount)
	sorted_by_amount = sorted_by_amount.filter(filter_solids)
	
	for substance: RuntimeSubstanceData in sorted_by_amount:
		var result_substance: RuntimeSubstanceData = substance.duplicate()
		if substance.amount >= extract_amount:
			result_substance.amount = extract_amount
			inventory[result_substance.substance.scientific_name].amount -= extract_amount
		else:
			extract_amount += (extract_amount - result_substance.amount)/len(sorted_by_amount)
			inventory[result_substance.substance.scientific_name].amount = 0
		result.append(result_substance)
		if inventory[result_substance.substance.scientific_name].amount == 0:
			inventory.erase(result_substance.substance.scientific_name)
	
	update_solutions()
	inventory_changed.emit(inventory.values())
	return result

func extract_substance(key: String, extract_amount: float) -> RuntimeSubstanceData:
	var substance: RuntimeSubstanceData = inventory[key]
	var result_substance: RuntimeSubstanceData = substance.duplicate()
	if substance.amount >= extract_amount:
		result_substance.amount = extract_amount
		inventory[result_substance.substance.scientific_name].amount -= extract_amount
	else:
		inventory[result_substance.substance.scientific_name].amount = 0
	if inventory[result_substance.substance.scientific_name].amount == 0:
		inventory.erase(result_substance.substance.scientific_name)
	
	update_solutions()
	inventory_changed.emit(inventory.values())
	
	return result_substance

func sort_by_amount(a: RuntimeSubstanceData, b: RuntimeSubstanceData) -> bool:
	return a.amount < b.amount 

func filter_solids(a: RuntimeSubstanceData) -> bool:
	return a.substance.melting_point > a.temperature

func update_temperature() -> void:
	if len(inventory) == 0:
		mean_temperature = 0
		return
	for sub in inventory:
		inventory[sub].temperature = mean_temperature
	check_for_reactions()

func recalculate_temperature() -> void:
	if len(inventory) == 0:
		mean_temperature = 0
		return
	var temperature_sum: float = 0 
	for sub in inventory:
		temperature_sum += inventory[sub].temperature
	mean_temperature = temperature_sum / len(inventory)

func check_for_reactions() -> void:
	var substances: Array[Substance] = []
	substances.resize(len(inventory))
	var temp_inv: Array[RuntimeSubstanceData] = inventory.values()
	for i in range(len(temp_inv)):
		substances[i] = temp_inv[i].substance
	
	var found_reaction = ReactionRegistry.find_appropriate_reaction(substances,mean_temperature)
	
	if found_reaction == null:
		return
	
	var reagents: Dictionary[StringName, ReactionSubstance] = found_reaction.input_substances_dict()
	var reaction_mass: float = INF
	for key in reagents:
		var reagent_mass: float = inventory[key].amount/reagents[key].coefficient
		reaction_mass = min(reagent_mass,reaction_mass)
	
	for key in reagents:
		extract_substance(key,reaction_mass * reagents[key].coefficient)
	
	for sub in found_reaction.output_substances:
		var data: RuntimeSubstanceData = RuntimeSubstanceData.new()
		
		data.amount = sub.coefficient * reaction_mass
		data.substance = sub.substance
		data.temperature = mean_temperature
		
		add_substance_no_callback(data)
	check_for_reactions()
	recalculate_temperature()
	update_solutions()
	inventory_changed.emit(inventory.values())
	GuiSignalBus.push("Произошла реакция: " + found_reaction.formula())
