extends Control

var mainmenu_visible : bool = false #used to test if mainmenu is open
var effect_position = Vector2(40,40) #tracks mouse position for node placement offset
@onready var graph_edit = $GraphEdit
var cdpprogs_location #stores the cdp programs location from user prefs for easy access
var delete_intermediate_outputs # tracks state of delete intermediate outputs toggle
@onready var console_output: RichTextLabel = $Console/ConsoleOutput
var undo_redo := UndoRedo.new() 
var output_audio_player #tracks the node that is the current output player for linking
var input_audio_player #potentially unused, remove? tracks node that is the current input player for linking
var currentfile = "none" #tracks dir of currently loaded file for saving
var changesmade = false #tracks if user has made changes to the currently loaded save file
var savestate # tracks what the user is trying to do when savechangespopup is called
var helpfile #tracks which help file the user was trying to load when savechangespopup is called
var outfilename #links to the user name for outputfile field
var foldertoggle #links to the reuse folder button
#var lastoutputfolder = "none" #tracks last output folder, this can in future be used to replace global.outfile but i cba right now
var uiscale = 0.0 # tracks the overal ui scale after hidpi adjustment and user offset
var retina_scaling = 1.0 #tracks scaling for retina screens
var use_anyway #used to store the folder selected for cdprogs when it appears the wrong folder is selected but the user wants to use it anyway
var main_theme = preload("res://theme/main_theme.tres") #load the theme
var default_input_node #stores a reference to the input node created on launch to allow auto loading a wav file
var output_folder_label
@onready var fft_size_option_button = $FFTSize
@onready var fft_overlap_option_button = $FFTOverlap


#scripts
var open_help
var run_thread
var save_load

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	$mainmenu.hide()
	$NoLocationPopup.hide()
	$Console.hide()
	$NoInputPopup.hide()
	$MultipleConnectionsPopup.hide()
	$AudioSettings.hide()
	$AudioDevicePopup.hide()
	$SearchMenu.hide()
	$Settings.hide()
	$ProgressWindow.hide()
	$WrongFolderPopup.hide()
	$SaveChangesPopup.hide()
	
	$SaveDialog.access = FileDialog.ACCESS_FILESYSTEM
	$SaveDialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
	$SaveDialog.filters = ["*.thd"]
	
	$LoadDialog.access = FileDialog.ACCESS_FILESYSTEM
	$LoadDialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
	$LoadDialog.filters = ["*.thd"]
	
	undo_redo.max_steps = 40
	
	get_tree().set_auto_accept_quit(false) #disable closing the app with the x and instead handle it internally
	
	
	load_scripts()
	make_signal_connections()
	hidpi_adjustment()
	check_user_preferences()
	new_patch()
	await get_tree().process_frame
	load_from_filesystem()
	check_cdp_location_set()
	
func load_scripts():
	#load and initialise scripts
	open_help = preload("res://scenes/main/scripts/open_help.gd").new()
	open_help.init(self)
	add_child(open_help)
	
	run_thread = preload("res://scenes/main/scripts/run_thread.gd").new()
	run_thread.init(self, $ProgressWindow, $ProgressWindow/ProgressLabel, $ProgressWindow/ProgressBar, $GraphEdit, $Console, $Console/ConsoleOutput)
	add_child(run_thread)
	
	graph_edit.init(self, $GraphEdit, Callable(open_help, "show_help_for_node"), $MultipleConnectionsPopup)
	
	save_load = preload("res://scenes/main/scripts/save_load.gd").new()
	save_load.init(self, $GraphEdit, Callable(open_help, "show_help_for_node"), Callable(graph_edit, "_register_node_movement"), Callable(graph_edit, "_register_inputs_in_node"), Callable(self, "link_output"))
	add_child(save_load)

func make_signal_connections():
	get_node("SearchMenu").make_node.connect(graph_edit._make_node)
	get_node("SearchMenu").swap_node.connect(graph_edit._swap_node)
	get_node("SearchMenu").connect_to_clicked_node.connect(graph_edit._connect_to_clicked_node)
	get_node("mainmenu").make_node.connect(graph_edit._make_node)
	get_node("mainmenu").open_help.connect(open_help.show_help_for_node)
	get_node("Settings").open_cdp_location.connect(show_cdp_location)
	get_node("Settings").console_on_top.connect(change_console_settings)
	get_node("Settings").invert_ui.connect(invert_theme_toggled)
	get_node("Settings").swap_zoom_and_move.connect(swap_zoom_and_move)
	get_node("Settings").ui_scale_multiplier_changed.connect(scale_ui)
	get_window().files_dropped.connect(on_files_dropped)
	
func hidpi_adjustment():
	#checks if display is hidpi and scales ui accordingly hidpi - 144
	if DisplayServer.screen_get_dpi(0) >= 144:
		retina_scaling = 2.0
	else:
		retina_scaling = 1.0
		

func scale_ui(scale_multiplier: float):
	var old_uiscale = uiscale
	uiscale = retina_scaling * scale_multiplier
	get_window().content_scale_factor = uiscale
	#goes through popup_windows group and scales all popups and resizes them
	for window in get_tree().get_nodes_in_group("popup_windows"):
		if old_uiscale != 0: #if ui scale = 0 this is the first time this is being adjusted so no need to revert values back to default first
			window.size = (window.size / old_uiscale) * uiscale
		else:
			window.size = window.size * uiscale
		window.content_scale_factor = uiscale
	

func load_from_filesystem():
	#checks if user has opened a file from the system file menu and loads it
	var args = OS.get_cmdline_args()
	for arg in args:
		var path = arg.strip_edges()
		if FileAccess.file_exists(path) and path.get_extension().to_lower() == "thd":
			save_load.load_graph_edit(path)
			break
		if FileAccess.file_exists(path) and path.get_extension().to_lower() == "wav":
			default_input_node.get_node("AudioPlayer")._on_file_selected(path)
			break

func new_patch():
	#clear old patch
	graph_edit.clear_connections()

	for node in graph_edit.get_children():
		if node is GraphNode:
			node.queue_free()
	
	await get_tree().process_frame  # Wait for nodes to actually be removed
	
	graph_edit.scroll_offset = Vector2(0, 0)
	
		#Generate input and output nodes
	var effect = Utilities.nodes["inputfile"].instantiate()
	effect.name = "inputfile"
	get_node("GraphEdit").add_child(effect, true)
	effect.connect("open_help", Callable(open_help, "show_help_for_node"))
	if effect.has_signal("node_moved"):
		effect.node_moved.connect(graph_edit._auto_link_nodes)
	effect.dragged.connect(graph_edit.node_position_changed.bind(effect))
	effect.position_offset = Vector2(20,80)
	default_input_node = effect #store a reference to this node to allow for loading into it directly if software launched with a wav file argument
	
	effect = Utilities.nodes["outputfile"].instantiate()
	effect.name = "outputfile"
	get_node("GraphEdit").add_child(effect, true)
	effect.init() #initialise ui from user prefs
	effect.connect("open_help", Callable(open_help, "show_help_for_node"))
	if effect.has_signal("node_moved"):
		effect.node_moved.connect(graph_edit._auto_link_nodes)
	effect.dragged.connect(graph_edit.node_position_changed.bind(effect))
	effect.position_offset = Vector2((DisplayServer.screen_get_size().x - 480) / uiscale, 80)
	graph_edit._register_node_movement() #link nodes for tracking position changes for changes tracking
	
	#set label for last output folder
	var interface_settings = ConfigHandler.load_interface_settings()
	output_folder_label = effect.get_node("OutputFolderMargin/OutputFolderLabel")
	if output_folder_label != null and interface_settings.last_used_output_folder != "no_file":
		output_folder_label.text = interface_settings.last_used_output_folder
		output_folder_label.get_parent().tooltip_text = interface_settings.last_used_output_folder
	
	changesmade = false #so it stops trying to save unchanged empty files
	get_window().title = "SoundThread"
	link_output()
	
	#set fft size and overlap to default
	$FFTSize.select(9)
	_on_fft_size_item_selected(9)
	$FFTOverlap.select(2)
	_on_fft_overlap_item_selected(2)
	
	
func link_output():
	#links various buttons and function in the input nodes - this is called after they are created so that it still works on new and loading files
	for control in get_tree().get_nodes_in_group("outputnode"): #check all items in outputnode group
		if control.has_meta("outputfunction"):
			if control.get_meta("outputfunction") == "deleteintermediate": #link delete intermediate files toggle to script
				control.toggled.connect(_toggle_delete)
				_toggle_delete(control.button_pressed)
				#control.button_pressed = interface_settings.get("delete_intermediate", true)

			elif control.get_meta("outputfunction") == "runprocess": #link runprocess button
				control.button_down.connect(_run_process)
			elif control.get_meta("outputfunction") == "audioplayer": #link output audio player
				output_audio_player = control
			elif control.get_meta("outputfunction") == "filename":
				control.text = "outfile"
				outfilename = control
			elif control.get_meta("outputfunction") == "reusefolder":
				foldertoggle = control
				#foldertoggle.button_pressed = interface_settings.get("reuse_output_folder", true)
			elif control.get_meta("outputfunction") == "openfolder":
				control.button_down.connect(_open_output_folder)


	#for control in get_tree().get_nodes_in_group("inputnode"):
		#if control.get_meta("inputfunction") == "audioplayer": #link input for recycle function
			#print("input player found")
			#input_audio_player = control

func check_user_preferences():
	var interface_settings = ConfigHandler.load_interface_settings()
	var audio_settings = ConfigHandler.load_audio_settings()
	var audio_devices = AudioServer.get_output_device_list()
	$Console.always_on_top = interface_settings.console_on_top
	if audio_devices.has(audio_settings.device):
		AudioServer.set_output_device(audio_settings.device)
	else:
		$AudioDevicePopup.popup_centered()
	
	match interface_settings.theme:
		0:
			RenderingServer.set_default_clear_color(Color("#2f4f4e"))
		1:
			RenderingServer.set_default_clear_color(Color("#000807"))
		2:
			RenderingServer.set_default_clear_color(Color("#98d4d2"))
		3:
			RenderingServer.set_default_clear_color(Color(interface_settings.theme_custom_colour))
			
	#set the theme to either the main theme or inverted theme depending on user preferences
	invert_theme_toggled(interface_settings.invert_theme)
	swap_zoom_and_move(interface_settings.swap_zoom_and_move)
	
	#scale ui
	scale_ui(interface_settings.ui_scale_multiplier)

		
func show_cdp_location():
	$CdpLocationDialog.show()
	
func check_cdp_location_set():
	#checks if the location has been set and prompts user to set it
	var cdpprogs_settings = ConfigHandler.load_cdpprogs_settings()
	if cdpprogs_settings.location == "no_location":
		$NoLocationPopup.popup_centered()
	else:
		#if location is set, stores it in a variable
		cdpprogs_location = str(cdpprogs_settings.location)
		print(cdpprogs_location)

func _on_ok_button_button_down() -> void:
	#after user has read dialog on where to find cdp progs this loads the file browser
	$NoLocationPopup.hide()
	if OS.get_name() == "Windows":
		$CdpLocationDialog.current_dir = "C:/"
	else:
		$CdpLocationDialog.current_dir = OS.get_environment("HOME")
	$CdpLocationDialog.show()

func _on_cdp_location_dialog_dir_selected(dir: String) -> void:
	var is_windows = OS.get_name() == "Windows"
	var cdprogs_correct
	
	#check if the selected folder contains the hilite program as it has a reasonably unique name and will indicate that the CDP processes do exist in that folder
	if is_windows:
		cdprogs_correct = FileAccess.file_exists(dir + "/distort.exe")
	else:
		cdprogs_correct = FileAccess.file_exists(dir + "/distort")
	
	
	if cdprogs_correct:
		#if this location does seem to contain cdp programs
		#saves default location for cdp programs in config file
		ConfigHandler.save_cdpprogs_settings(dir)
		cdpprogs_location = dir
	else:
		#if it doesn't seem to contain the programs then try and extrapolate the correct folder from the one selected
		var selected_folder = dir.get_slice("/", (dir.get_slice_count("/") - 1))
		print(selected_folder)
		if selected_folder.to_lower() == "cdpr8":
			dir = dir + "/_cdp/_cdprogs"
			#run this function recursively to check if the programs do exist
			_on_cdp_location_dialog_dir_selected(dir)
		elif selected_folder.to_lower() == "_cdp":
			dir = dir + "/_cdprogs"
			#run this function recursively to check if the programs do exist
			_on_cdp_location_dialog_dir_selected(dir)
		else:
			#can't find them
			use_anyway = dir
			$WrongFolderPopup.popup_centered()

func _on_cdp_location_dialog_canceled() -> void:
	#cycles around the set location prompt if user cancels the file dialog
	check_cdp_location_set()
	
func _on_select_folder_button_button_down() -> void:
	$WrongFolderPopup.hide()
	_on_ok_button_button_down()

func _on_use_anyway_button_button_down() -> void:
	$WrongFolderPopup.hide()
	ConfigHandler.save_cdpprogs_settings(use_anyway)
	cdpprogs_location = use_anyway

func _input(event):
	if event.is_action_pressed("undo"):
		simulate_mouse_click()
		await get_tree().process_frame
		undo_redo.undo()
	elif event.is_action_pressed("redo"):
		undo_redo.redo()
	elif event.is_action_pressed("save"):
		if currentfile == "none":
			savestate = "saveas"
			$SaveDialog.popup_centered()
		else:
			save_load.save_graph_edit(currentfile)
	elif event.is_action_pressed("open_explore"):
		open_explore()
	elif event.is_action_pressed("search"):
		var pos = graph_edit.get_local_mouse_position()
		_on_graph_edit_popup_request(pos)
	elif event.is_action_pressed("run_thread"):
		_run_process()
	elif event.is_action_pressed("new"):
		if changesmade == true:
			savestate = "newfile"
			$SaveChangesPopup.popup_centered()
		else:
			new_patch()
			currentfile = "none" #reset current file to none for save tracking
	elif event.is_action_pressed("save_as"):
		savestate = "saveas"
		$SaveDialog.popup_centered()
	
func simulate_mouse_click():
	#simulates clicking the middle mouse button in order to hide any visible tooltips
	var click_pos = get_viewport().get_mouse_position()

	var down_event := InputEventMouseButton.new()
	down_event.button_index = MOUSE_BUTTON_MIDDLE
	down_event.pressed = true
	down_event.position = click_pos
	Input.parse_input_event(down_event)

	var up_event := InputEventMouseButton.new()
	up_event.button_index = MOUSE_BUTTON_MIDDLE
	up_event.pressed = false
	up_event.position = click_pos
	Input.parse_input_event(up_event)


func _run_process() -> void:
	#check if any of the inputfile nodes don't have files loaded
	var interface_settings = ConfigHandler.load_interface_settings()
	for node in graph_edit.get_children():
		if node.get_meta("command") == "inputfile" and node.get_node("AudioPlayer").has_meta("inputfile") == false:
			$NoInputPopup.popup_centered()
			return
	#check if the reuse folder toggle is set and a folder has been previously chosen
	var output_folder = interface_settings.last_used_output_folder
	if foldertoggle.button_pressed == true and output_folder != "no_file" and DirAccess.open(output_folder) != null:
		_on_file_dialog_dir_selected(output_folder)
	else:
		$FileDialog.show()
			

func _on_file_dialog_dir_selected(dir: String) -> void:
	ConfigHandler.save_interface_settings("last_used_output_folder", dir)
	if output_folder_label != null:
		output_folder_label.text = dir
		output_folder_label.tooltip_text = dir
	console_output.clear()
	var interface_settings = ConfigHandler.load_interface_settings()
	if interface_settings.disable_progress_bar == false:
		$ProgressWindow.show()
	else:
		if $Console.is_visible():
			$Console.hide()
			await get_tree().process_frame  # Wait a frame to allow hide to complete
			$Console.popup_centered()
		else:
			$Console.popup_centered()
	await get_tree().process_frame
	run_thread.log_console("Generating processing queue", true)
	await get_tree().process_frame

	#get the current time in hh-mm-ss format as default : causes file name issues
	var time_dict = Time.get_time_dict_from_system()
	# Pad with zeros to ensure two digits for hour, minute, second
	var hour = str(time_dict.hour).pad_zeros(2)
	var minute = str(time_dict.minute).pad_zeros(2)
	var second = str(time_dict.second).pad_zeros(2)
	var time_str = hour + "-" + minute + "-" + second
	Global.outfile = dir + "/" + outfilename.text.get_basename() + "_" + Time.get_date_string_from_system() + "_" + time_str
	
	#check path and file name do not contain special characters
	var check_file_name = Global.check_for_invalid_chars(Global.outfile)
	
	if check_file_name["contains_invalid_characters"] == false:
		run_thread.log_console("Output directory and file name(s):" + Global.outfile, true)
		await get_tree().process_frame
		
		run_thread.run_thread_with_branches()
	else:
		run_thread.log_console("[color=#9c2828][b]Error:[/b][/color] Chosen file name or folder path " + Global.outfile.get_basename() + " contains invalid characters.", true)
		run_thread.log_console("File names and paths can only contain A-Z a-z 0-9 - _ + and space.", true)
		run_thread.log_console("Chosen file name/path contains the following invalid characters: " + " ".join(check_file_name["invalid_characters_found"]), true)
		if $ProgressWindow.visible:
			$ProgressWindow.hide()
		if !$Console.visible:
			$Console.popup_centered()

func _toggle_delete(toggled_on: bool):
	delete_intermediate_outputs = toggled_on
	print(toggled_on)

func _on_console_close_requested() -> void:
	$Console.hide()



func _on_console_open_folder_button_down() -> void:
	$Console.hide()
	var interface_settings = ConfigHandler.load_interface_settings()
	var output_folder = interface_settings.last_used_output_folder
	if output_folder != "no_file" and DirAccess.open(output_folder) != null:
		OS.shell_open(output_folder)


func _on_ok_button_2_button_down() -> void:
	$NoInputPopup.hide()


func _on_ok_button_3_button_down() -> void:
	$MultipleConnectionsPopup.hide()



func _on_settings_button_index_pressed(index: int) -> void:
	
	match index:
		0:
			$Settings.cdpprogs_location = cdpprogs_location
			$Settings.popup_centered()
		1:
			$AudioSettings.popup_centered()
		2:
			if $Console.is_visible():
				$Console.hide()
				await get_tree().process_frame  # Wait a frame to allow hide to complete
				$Console.popup_centered()
			else:
				$Console.popup_centered()

func _on_file_button_index_pressed(index: int) -> void:
	match index:
		0:
			if changesmade == true:
				savestate = "newfile"
				$SaveChangesPopup.popup_centered()
			else:
				new_patch()
				currentfile = "none" #reset current file to none for save tracking
			
		1:
			if currentfile == "none":
				savestate = "saveas"
				$SaveDialog.popup_centered()
			else:
				save_load.save_graph_edit(currentfile)
			
			print("save pressed, changes made =")
			print(changesmade)
			print("current file =")
			print(currentfile)
		2:
			savestate = "saveas"
			$SaveDialog.popup_centered()
		3:
			if changesmade == true:
				savestate = "load"
				$SaveChangesPopup.popup_centered()
			else:
				$LoadDialog.popup_centered()



func _on_save_dialog_file_selected(path: String) -> void:
	save_load.save_graph_edit(path) #save file
	#check what the user was trying to do before save and do that action
	if savestate == "newfile":
		new_patch()
		currentfile = "none" #reset current file to none for save tracking
	elif savestate == "load":
		$LoadDialog.popup_centered()
	elif savestate == "helpfile":
		currentfile = "none" #reset current file to none for save tracking so user cant save over help file
		save_load.load_graph_edit(helpfile)
	elif savestate == "quit":
		undo_redo.free()
		await get_tree().create_timer(0.25).timeout #little pause so that it feels like it actually saved even though it did
		get_tree().quit()
	elif savestate == "saveas":
		currentfile = path
		
	savestate = "none" #reset save state, not really needed but feels good


func _on_load_dialog_file_selected(path: String) -> void:
	currentfile = path #tracking path here only means "save" only saves patches the user has loaded rather than overwriting help files
	save_load.load_graph_edit(path)

func _on_help_button_index_pressed(index: int) -> void:
	match index:
		0:
			pass
		1:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/getting_started.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/getting_started.thd")
		2:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/navigating.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/navigating.thd")
		3:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/building_a_thread.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/building_a_thread.thd")
		4:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/frequency_domain.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/frequency_domain.thd")
		5:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/automation.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/automation.thd")
		6:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/trimming.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/trimming.thd")
		7:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/multiple_inputs.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/multiple_inputs.thd")
		8:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/preview_nodes.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/preview_nodes.thd")
		9:
			pass
		10:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/wetdry.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/wetdry.thd")
		11:
			if changesmade == true:
				savestate = "helpfile"
				helpfile = "res://examples/resonant_filters.thd"
				$SaveChangesPopup.popup_centered()
			else:
				currentfile = "none" #reset current file to none for save tracking so user cant save over help file
				save_load.load_graph_edit("res://examples/resonant_filters.thd")
		12:
			pass
		13:
			OS.shell_open("https://www.composersdesktop.com/docs/html/ccdpndex.htm")
		14:
			OS.shell_open("https://github.com/j-p-higgins/SoundThread/issues")

#func _recycle_outfile():
	#if outfile != "no file":
		#input_audio_player.recycle_outfile(outfile)



func _on_save_changes_button_down() -> void:
	$SaveChangesPopup.hide()
	if currentfile == "none":
		$SaveDialog.show()
	else:
		save_load.save_graph_edit(currentfile)
		if savestate == "newfile":
			new_patch()
			currentfile = "none" #reset current file to none for save tracking
		elif savestate == "load":
			$LoadDialog.popup_centered()
		elif savestate == "helpfile":
			currentfile = "none" #reset current file to none for save tracking so user cant save over help file
			save_load.load_graph_edit(helpfile)
		elif savestate == "quit":
			undo_redo.free()
			await get_tree().create_timer(0.25).timeout #little pause so that it feels like it actually saved even though it did
			get_tree().quit()
			
		savestate = "none"


func _on_dont_save_changes_button_down() -> void:
	$SaveChangesPopup.hide()
	if savestate == "newfile":
		new_patch()
		currentfile = "none" #reset current file to none for save tracking
	elif savestate == "load":
		$LoadDialog.popup_centered()
	elif savestate == "helpfile":
		currentfile = "none" #reset current file to none for save tracking so user cant save over help file
		save_load.load_graph_edit(helpfile)
	elif savestate == "quit":
		undo_redo.free()
		get_tree().quit()
	
	savestate = "none"
	
func _on_cancel_changes_button_down() -> void:
	$SaveChangesPopup.hide()
	savestate = "none"
	
func _notification(what):
	if what == NOTIFICATION_WM_CLOSE_REQUEST:
		run_thread._on_kill_process_button_down()
		$Console.hide()
		if changesmade == true:
			savestate = "quit"
			$SaveChangesPopup.popup_centered()
			#$HelpWindow.hide()
		else:
			undo_redo.free()
			get_tree().quit() # default behavior
			
func _open_output_folder():
	var interface_settings = ConfigHandler.load_interface_settings()
	var output_folder = interface_settings.last_used_output_folder
	if output_folder != "no_file" and DirAccess.open(output_folder) != null:
		OS.shell_open(output_folder)
		

func _on_rich_text_label_meta_clicked(meta: Variant) -> void:
	print(str(meta))
	OS.shell_open(str(meta))


func _on_graph_edit_popup_request(at_position: Vector2) -> void:

	effect_position = graph_edit.get_local_mouse_position()
	
	#give the search menu the ui scale
	$SearchMenu.uiscale = uiscale

	#get the mouse position in screen coordinates
	var mouse_screen_pos = DisplayServer.mouse_get_position()  
	#get the window position in screen coordinates
	var window_screen_pos = get_window().position
	#get the window size relative to its scaling for retina displays
	var window_size = get_window().size * DisplayServer.screen_get_scale()

	#see if it was empty space or a node that was right clicked
	var clicked_node
	for child in graph_edit.get_children():
		if child is GraphNode:
			if Rect2(child.position, child.size).has_point(effect_position):
				clicked_node = child
				break
	
	if clicked_node and clicked_node.get_meta("command") != "outputfile":
		var title = clicked_node.title
		if Input.is_action_pressed("auto_link_nodes"):
			$SearchMenu/VBoxContainer/ReplaceLabel.text = "Connect to " + title
			$SearchMenu/VBoxContainer/ReplaceLabel.show()
			$SearchMenu.replace_node = false
			$SearchMenu.connect_to_node = true
			$SearchMenu.node_to_connect_to = clicked_node
		else:
			$SearchMenu/VBoxContainer/ReplaceLabel.text = "Replace " + title
			$SearchMenu/VBoxContainer/ReplaceLabel.show()
			$SearchMenu.replace_node = true
			$SearchMenu.connect_to_node = false
			$SearchMenu.node_to_replace = clicked_node
	else:
		var interface_settings = ConfigHandler.load_interface_settings()
		if interface_settings.right_click_opens_explore:
			open_explore()
			return
		else:
			$SearchMenu/VBoxContainer/ReplaceLabel.hide()
			$SearchMenu.replace_node = false
			$SearchMenu.connect_to_node = false
	
	#calculate the xy position of the mouse clamped to the size of the window and menu so it doesn't go off the screen
	var clamped_x = clamp(mouse_screen_pos.x, window_screen_pos.x, window_screen_pos.x + window_size.x - $SearchMenu.size.x)
	var clamped_y = clamp(mouse_screen_pos.y, window_screen_pos.y, window_screen_pos.y + window_size.y - (420 * DisplayServer.screen_get_scale()))
	
	#position and show the menu
	$SearchMenu.position = Vector2(clamped_x, clamped_y)
	$SearchMenu.popup()

func _on_audio_settings_close_requested() -> void:
	$AudioSettings.hide()


func _on_open_audio_settings_button_down() -> void:
	$AudioDevicePopup.hide()
	$AudioSettings.popup_centered()


func _on_audio_device_popup_close_requested() -> void:
	$AudioDevicePopup.hide()

func _on_mainmenu_close_requested() -> void:
	#closes menu if click is anywhere other than the menu as it is a window with popup set to true
	$mainmenu.hide()

func open_explore():
	effect_position = graph_edit.get_local_mouse_position()
	
	#get the mouse position in screen coordinates
	var mouse_screen_pos = DisplayServer.mouse_get_position()  
	#get the window position in screen coordinates
	var window_screen_pos = get_window().position
	#get the window size relative to its scaling for retina displays
	var window_size = get_window().size * DisplayServer.screen_get_scale()
	#get the size of the popup menu
	var popup_size = $mainmenu.size

	#calculate the xy position of the mouse clamped to the size of the window and menu so it doesn't go off the screen
	var clamped_x = clamp(mouse_screen_pos.x, window_screen_pos.x, window_screen_pos.x + window_size.x - popup_size.x)
	var clamped_y = clamp(mouse_screen_pos.y, window_screen_pos.y, window_screen_pos.y + window_size.y - popup_size.y)
	
	#position and show the menu
	$mainmenu.position = Vector2(clamped_x, clamped_y)
	$mainmenu.popup()
	
func change_console_settings(toggled: bool):
	$Console.always_on_top = toggled


func _on_kill_process_button_down() -> void:
	run_thread._on_kill_process_button_down()

func invert_theme_toggled(toggled: bool):
	if toggled:
		var inverted = invert_theme(main_theme)
		get_tree().root.theme = inverted # force refresh
		$MenuBarBackground.color = Color(0.934, 0.934, 0.934)
		for color_rect in get_tree().get_nodes_in_group("invertable_background"):
			if color_rect is ColorRect:
				color_rect.color = Color(0.898, 0.898, 0.898, 0.6)
		
	else:
		get_tree().root.theme = main_theme # force refresheme = main_theme
		$MenuBarBackground.color = Color(0.065, 0.065, 0.065)
		
		for color_rect in get_tree().get_nodes_in_group("invertable_background"):
			if color_rect is ColorRect:
				color_rect.color = Color(0.102, 0.102, 0.102, 0.6)
		
func invert_theme(theme: Theme) -> Theme:
	var inverted_theme = theme.duplicate(true) # deep copy

	# Check all types and color names in the theme
	var types = inverted_theme.get_type_list()
	for type in types:
		var color_names = inverted_theme.get_color_list(type)
		for cname in color_names:
			var col = inverted_theme.get_color(cname, type)
			var inverted = Color(1.0 - col.r, 1.0 - col.g, 1.0 - col.b, col.a)
			inverted_theme.set_color(cname, type, inverted)

		var style_names = inverted_theme.get_stylebox_list(type)
		for sname in style_names:
			if type == "GraphEdit" and sname == "panel":
				continue
			var sb = inverted_theme.get_stylebox(sname, type)
			var new_sb = sb.duplicate()
			if new_sb is StyleBoxFlat:
				var col = new_sb.bg_color
				new_sb.bg_color = Color(1.0 - col.r, 1.0 - col.g, 1.0 - col.b, col.a)
			inverted_theme.set_stylebox(sname, type, new_sb)
	
	return inverted_theme

func swap_zoom_and_move(toggled: bool):
	if toggled:
		graph_edit.set_panning_scheme(1)
	else:
		graph_edit.set_panning_scheme(0)

func on_files_dropped(files):
	var mouse_pos = graph_edit.get_local_mouse_position()
	
	#see if files were dropped on an input node
	var dropped_node
	for child in graph_edit.get_children():
		if child is GraphNode:
			if Rect2(child.position, child.size).has_point(mouse_pos):
				dropped_node = child
				break
				
	#if they were dropped on a node and the first file in the array is a wav file replace the file in the input node
	if dropped_node and dropped_node.has_meta("command") and dropped_node.get_meta("command") == "inputfile":
		if files[0].get_extension().to_lower() == "wav":
			dropped_node.get_node("AudioPlayer")._on_file_selected(files[0])
	else:
		#else make a new input node at the mouse position and load it in
		if files[0].get_extension().to_lower() == "wav":
			var new_input_node = graph_edit._make_node("inputfile")
			new_input_node.position_offset = mouse_pos
			new_input_node.get_node("AudioPlayer")._on_file_selected(files[0])
	
	#remove first element from the array
	files.remove_at(0)
	
	#check if there are any other files
	if files.size() > 0:
		var position_plus_offset = Vector2(mouse_pos.x, mouse_pos.y + 250) #apply a vertical offset from the mouse position so nodes dont overlap
		for file in files:
			if file.get_extension().to_lower() == "wav":
				var new_input_node = graph_edit._make_node("inputfile")
				new_input_node.position_offset = position_plus_offset
				new_input_node.get_node("AudioPlayer")._on_file_selected(file)
				position_plus_offset.y = position_plus_offset.y + 250


func _on_fft_size_item_selected(index: int) -> void:
	var fft_size
	if index == 13:
		fft_size = 16380
	else:
		fft_size = 1 << (index + 1)
	run_thread.fft_size = fft_size



func _on_fft_overlap_item_selected(index: int) -> void:
	run_thread.fft_overlap = index + 1
