# Hurry Curry! - a game about cooking
# Copyright (C) 2025 Hurry Curry! contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License only.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
class_name Multiplayer
extends Node

signal packet(packet: Dictionary)
signal connection_closed()

static var VERSION_MAJOR: int = 11
static var VERSION_MINOR: int = 0

var connected := false
var socket := WebSocketPeer.new()

func _ready():
	print("Multiplayer connect");
	socket.inbound_buffer_size = 1024 * 1024 * 4

func connect_to_url(url):
	socket.connect_to_url(url)
	connected = true

func _notification(what):
	if what == NOTIFICATION_PREDELETE:
		print("Multiplayer disconnect");
		socket.close()
		connected = false

func _process(_delta):
	if connected:
		socket.poll()
		var state = socket.get_ready_state()
		if state == WebSocketPeer.STATE_OPEN:
			while socket.get_available_packet_count():
				handle_packet(socket.get_packet())
		elif state == WebSocketPeer.STATE_CLOSED:
			var code = socket.get_close_code()
			var reason = socket.get_close_reason() if code == socket.STATE_CLOSED else tr("c.error.websocket.unavailable")
			connection_closed.emit(tr("c.error.websocket").format([code, reason, code != -1]))
			self.queue_free()

func fix_packet_types(val):
	match typeof(val):
		TYPE_FLOAT: return val
		TYPE_STRING: return val
		TYPE_BOOL: return val
		TYPE_ARRAY: return val.map(fix_packet_types)
		TYPE_DICTIONARY:
			var newval = {}
			for k in val.keys():
				if typeof(val[k]) == TYPE_ARRAY and val[k].size() == 2 and typeof(val[k][0]) == TYPE_FLOAT and typeof(val[k][1]) == TYPE_FLOAT:
					if k in ["tile"]: newval[k] = Vector2i(val[k][0], val[k][1])
					elif k in ["pos", "position"]: newval[k] = Vector2(val[k][0], val[k][1])
					else: newval[k] = val[k]
				# TODO reenable when fixed
				# elif k in ["player", "id"] and typeof(val[k]) == TYPE_FLOAT:
				# 	newval[k] = int(val[k])
				else:
					newval[k] = fix_packet_types(val[k])
			return newval

func handle_packet(coded):
	var p = decode_packet(coded)
	if p == null:
		return
	
	p = fix_packet_types(p)

	match p["type"]:
		"version":
			var major = p["major"]
			var minor = p["minor"]
			if major != VERSION_MAJOR or minor > VERSION_MINOR:
				socket.close()
				connected = false
				connection_closed.emit(tr("c.error.version_mismatch").format([major, minor, VERSION_MAJOR, VERSION_MINOR]))
		_: packet.emit(p)

func send_join(player_name: String, character_style: Dictionary):
	send_packet({
		"type": "join",
		"name": player_name,
		"character": character_style
	})

func send_movement(player, pos: Vector2, direction: Vector2, boost: bool):
	send_packet({
		"type": "movement",
		"player": player,
		"pos": [pos.x, pos.y],
		"dir": [direction.x, direction.y],
		"boost": boost
	})

func send_tile_interact(player, pos: Vector2i, edge: bool, hand: int):
	@warning_ignore("incompatible_ternary")
	send_packet({
		"type": "interact",
		"player": player,
		"hand": hand,
		"pos": [pos.x, pos.y] if edge else null,
	})

func send_player_interact(_player, _edge: bool):
	push_error("not yet implemented")

func send_chat(player, message: String):
	send_packet({
		"type": "communicate",
		"player": player,
		"persist": false,
		"message": {
			"text": message
		}
	})

func send_replay_tick(dt: float):
	send_packet({
		"type": "replay_tick",
		"dt": dt
	})

func send_idle(paused: bool):
	send_packet({
		"type": "idle",
		"paused": paused,
	})

func send_leave(player):
	send_packet({
		"type": "leave",
		"player": player,
	})

func send_ready():
	send_packet({
		"type": "ready"
	})

func send_packet(p):
	var json = JSON.stringify(p)
	socket.send_text(json)

func decode_packet(bytes: PackedByteArray):
	var json = JSON.new()
	var in_str = bytes.get_string_from_utf8()
	var error = json.parse(in_str)
	if error == OK:
		return json.data
	else:
		print("Decode of packet failed: %s in %s" % [json.get_error_message(), in_str])
		return null
