# Hurry Curry! - a game about cooking
# Copyright (C) 2026 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/>.
#
extends Node
class_name Cli

enum Mode { FLAG, OPTION, MULTI_OPTION, POSITIONAL }
class Option:
	var short #: String?
	var long: String
	var mode: Mode
	var help: String
	func _init(s, l: String, m: Mode, h: String):
		short = s; long = l; mode = m; help = h

static var OPTIONS := [
	Option.new("h", "help", Mode.FLAG, "Show help"),
	Option.new("s", "setting", Mode.MULTI_OPTION, "Per-launch setting override"),
	Option.new("c", "join-command", Mode.OPTION, "Message to send right after initial joining"),
	Option.new(null, "render-items", Mode.OPTION, "Render items from text file to images"),
	Option.new(null, "render-tiles", Mode.OPTION, "Render tiles from text file to images"),
	Option.new(null, "render-resolution", Mode.OPTION, "Resolution for rendering items or tiles"),
	Option.new(null, "render-output", Mode.OPTION, "Output directory for rendering items or tiles"),
	Option.new(null, "render-format", Mode.OPTION, "Render output format (possible values: png, gltf)"),
	Option.new(null, "render-include-tool-base", Mode.FLAG, "Add counters to cutting board and such"),
	Option.new(null, "record-camera", Mode.OPTION, "Record camera movement to file"),
	Option.new(null, "replay-camera", Mode.OPTION, "Replay camera movement"),
	Option.new(null, "timescale", Mode.OPTION, "Time scaling factor for replay playback and camera recorder"),
	Option.new(null, "render-output", Mode.OPTION, "Output directory for rendering items or tiles"),
	Option.new(null, "connect_address", Mode.POSITIONAL, "Connect to a server directly without menu interaction")
]

static var opts = {} #: Dictionary[String, Variant]

static func init() -> bool:
	if not parse(): return false
	if opts.has("help"):
		print_help()
		return false
	return true

static func print_help():
	print("OPTIONS:\n")
	for opt in OPTIONS:
		var line = ""
		if opt.mode == Mode.POSITIONAL:
			line += "<" + opt.long.to_upper() + ">"
		else:
			if opt.short: line += "-" + opt.short + ", "
			line += "--" + opt.long
			if opt.mode == Mode.OPTION or opt.mode == Mode.MULTI_OPTION:
				line += " <VALUE>"
		while line.length() < 25: line += " "
		line += " " + opt.help
		print(line)

static var args: Array
static func parse() -> bool:
	args = Array(OS.get_cmdline_user_args())
	while not args.is_empty():
		var arg = args[0]
		args.remove_at(0)
		if arg.begins_with("--"):
			var long = arg.trim_prefix("--")
			var opt_index = OPTIONS.find_custom(func(x): return x.long == long)
			if opt_index == -1:
				push_error("unknown long option \"%s\"" % long)
				return false
			if not _parse_opt(OPTIONS[opt_index]): return false
		elif arg.begins_with("-"):
			for short in arg.trim_prefix("-"):
				var opt_index = OPTIONS.find_custom(func(x): return x.short == short)
				if opt_index == -1:
					push_error("unknown short option \"%s\"" % short)
					return false
				if not _parse_opt(OPTIONS[opt_index]): return false
		else:
			var opt_index = OPTIONS.find_custom(func(x): return x.mode == Mode.POSITIONAL)
			if opt_index == -1:
				push_error("no positional arguments")
				return false
			var opt = OPTIONS[opt_index]
			opts[opt.long] = arg

	print("Parsed options: ", opts)
	return true

static func _parse_opt(opt: Option) -> bool:
	match opt.mode:
		Mode.FLAG:
			opts[opt.long] = true
			return true
		Mode.OPTION:
			if args.is_empty():
				push_error("missing option value")
				return false
			opts[opt.long] = trim_apostrophes(args[0])
			args.remove_at(0)
			return true
		Mode.MULTI_OPTION:
			if args.is_empty():
				push_error("missing option value")
				return false
			if not opts.has(opt.long): opts[opt.long] = []
			opts[opt.long].push_back(trim_apostrophes(args[0]))
			args.remove_at(0)
			return true
		Mode.POSITIONAL:
			push_error("positional arg doesnt need flag")
			return false
	push_error("unreachable")
	return false

static func trim_apostrophes(s: String) -> String:
	# Godot doesn't remove apostrophes when adding custom arguments with
	# customize run instances in the editor. We'll have to remove them manually.
	if OS.has_feature("editor_runtime"):
		return s.trim_prefix("\"").trim_prefix("\'").trim_suffix("\"").trim_suffix("\'")
	return s # Not running in editor
