class_name NotePool extends Node

@export_group("Start")
## Tap notes to spawn on [method _ready].
@export var tap_size: int = 30

## Hold notes to spawn on [method _ready].
@export var hold_size: int = 10

@export_group("Skin")
## [StringName] of the tap note skin to use.
@export var tap_skin: StringName = "default"

## [StringName] of the hold note skin to use.
@export var hold_skin: StringName = "default"

func get_note(type: NoteVisual.TYPE) -> NoteVisual:
	match type:
		NoteVisual.TYPE.TAP:
			return get_tap()
		NoteVisual.TYPE.HOLD:
			return get_hold()
		_:
			return null

func return_note(note: NoteVisual) -> void:
	if note is TapNote:
		note.reset()
		return_tap(note as TapNote)
	elif note is HoldNote:
		note.reset()
		return_hold(note as HoldNote)

## Get a tap note. Instantiates one if no free nodes are available.
func get_tap() -> TapNote:
	var tap: TapNote = _free_taps.pop_back()
	if tap == null:
		tap = _instantiate_tap()
	_used_taps[tap] = true
	return tap

## Return a tap note.
func return_tap(tap: TapNote) -> void:
	var parent: Node = tap.get_parent()
	if parent:
		parent.remove_child(tap)
		
	if not _used_taps.erase(tap):
		push_warning(
			"Returning tap note ", tap,
			" that was not spawned by pool ", self, "."
			)
	_free_taps.append(tap)

## Get a hold note. Instantiates one if no free nodes are available.
func get_hold() -> HoldNote:
	var hold: HoldNote = _free_holds.pop_back()
	if hold == null:
		hold = _instantiate_hold()
	_used_holds[hold] = true
	return hold

## Return a hold note.
func return_hold(hold: HoldNote) -> void:
	var parent: Node = hold.get_parent()
	if parent:
		parent.remove_child(hold)
		
	if not _used_holds.erase(hold):
		push_warning(
			"Returning hold note ", hold,
			" that was not spawned by pool ", self, "."
			)
	_free_holds.append(hold)

## Spawn the given number of notes.
func reserve(taps: int, holds: int) -> void:
	for i: int in range(taps):
		_free_taps.append(_instantiate_tap())
	
	for i: int in range(holds):
		_free_holds.append(_instantiate_hold())


# ========= IMPLEMENTATION ========= #
var _free_taps: Array[TapNote]
var _used_taps: Dictionary[TapNote, bool]

var _free_holds: Array[HoldNote]
var _used_holds: Dictionary[HoldNote, bool]

func _instantiate_tap() -> TapNote:
	return NoteScenes.get_tap(tap_skin).instantiate() as TapNote

func _instantiate_hold() -> HoldNote:
	return NoteScenes.get_tap(tap_skin).instantiate() as HoldNote
