#
#  Ham'n'Cheese
#  Copyright (C) 2023-2024 Scott Duensing <scott@kangaroopunch.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>
#


extends Node


const MODE_READ  := "r"
const MODE_WRITE := "w"


var _edge_pid        := -1
var _edge_running    := false
var _udp             := PacketPeerUDP.new()
var _sequence        := 1
var _password        := "n2n"
var _data_buffer     := {}
var _data_type       := {}
var _timer           : Timer
var _my_port         := 0
var _management_port := 0
var _community       := ""
var _key             := ""
var _supernode       := ""
var _super_port      := 0
var _local_port      := 0
var _local_ip        := ""
var _kill_timer      := 10


signal data_recieved(type: String, data: Array)
signal network_died


func _on_timer_timeout():
	if _edge_running:
		_kill_timer = 10
		if _udp.is_socket_connected():
			_send("info")
			_send("edges")
		else:
			_udp.bind(_my_port, "127.0.0.1")
			_udp.connect_to_host("127.0.0.1", _management_port)
	else:
		if _kill_timer > 0:
			_kill_timer -= 1
		else:
			if _edge_pid >= 0:
				if OS.is_process_running(_edge_pid):
					print("Timer killing edge pid ", _edge_pid)
					OS.kill(_edge_pid)
				_edge_pid = -1


func _process(_delta):
	if _udp.is_socket_connected():
		var data = _udp.get_packet().get_string_from_ascii()
		if _udp.get_packet_error() == OK:
			var json = JSON.new()
			var error = json.parse(data)
			if error == OK:
				var received = json.data
				# Is this the start of a sequence?
				if received["_type"] == "begin":
					# Nuke anything currently in this sequence's slot.
					_data_buffer[received["_tag"]] = []  # This is a dictionary of tags->arrays of dictionaries.
					_data_type[received["_tag"]] = received["cmd"]  # This is a dictionary of tags->strings
				# Is this the end of a squence?
				elif received["_type"] == "end":
					data_recieved.emit(_data_type[received["_tag"]], _data_buffer[received["_tag"]])
				elif received["_type"] == "error":
					_data_buffer[received["_tag"]] = []
					_data_buffer[received["_tag"]].append(received)
					data_recieved.emit("error", _data_buffer[received["_tag"]])
				else:
					_data_buffer[received["_tag"]].append(received)


func _send(what: String, mode: String = MODE_READ):
	if _udp.is_socket_connected():
		var packet = mode + " " + str(_sequence) + ":1:" + _password + " " + what
		_udp.put_packet(packet.to_ascii_buffer())
		_sequence += 1
		if _sequence > 999:
			_sequence = 1


func bind(local_ip: String = "", local_port: int = 10000):
	_local_ip   = local_ip
	_local_port = local_port


func set_management(management_port: int = 5644, my_port: int = 5643):
	_my_port         = my_port
	_management_port = management_port


func set_network(community: String, key: String):
	_community = community
	_key       = key


func set_supernode(supernode: String, port: int = 7777):
		_supernode  = supernode
		_super_port = port


func shutdown():
	if _edge_pid >= 0:
		print("Edge PID is ", _edge_pid)
		if OS.is_process_running(_edge_pid):
			print("Edge is running - killing ", _edge_pid)
			OS.kill(_edge_pid)
		_edge_pid = -1
		_edge_running = false


func start_edge():
	var binary = OS.get_user_data_dir() + "/edge"
	var arguments = ["-t", _management_port, "--management-password", _password, "-c", _community, "-k", _key, "-l", _supernode + ":" + str(_super_port), "-p"]
	if _local_ip != "":
		arguments.append(_local_ip + ":" + str(_local_port))
	else:
		arguments.append(str(_local_port))
	if OS.get_name() == "Windows":
		binary = binary + ".exe"
	else:
		arguments.append("-f")
		_password = Util.generate_password()
	_edge_pid = await Process.elevate(binary, arguments)
	if _edge_pid < 0:
		await Dialog.alert("Error", "Unable to start network layer!")
		return false
	Engine.get_main_loop().current_scene.add_child(_timer)
	_edge_running = true
	return true


func startup():
	_timer           = Timer.new()
	_timer.wait_time = 3
	_timer.autostart = true
	_timer.timeout.connect(_on_timer_timeout)
	# Extract the edge binary.
	var binary = OS.get_user_data_dir() + "/edge"
	if OS.get_name() == "Windows":
		binary = binary + ".exe"
	if !FileAccess.file_exists(binary):
		print("Extracting edge")
		var extract = Embedded.new()
		extract.save_edge(binary)
	if OS.get_name() != "Windows":
		OS.execute("chmod", ["+x", binary])


func stop_edge():
	_send("stop", MODE_WRITE)
	_udp.close()
	_edge_running = false
