using System;
using System.Net;
using Munga.Net;
namespace VPod;
///
/// The launch options a real game client understands, parsed from the command
/// line the console/launcher starts it with. vPOD accepts the same shape so it
/// is a drop-in stand-in:
/// -net <port> Munga control port (default 1501)
/// -lc live-camera role (cosmetic here; state model is identical)
/// -mr mission-review role
/// -res W H resolution (ignored)
/// -app rp|bt which ApplicationID to report (RP by default; also
/// switchable live in the UI)
/// -host <id> the responding host id reported in state responses
///
/// vPOD-only (not a real game-client option):
/// -nomanage disable the virtual launcher / site-management side
/// (no provisioning beacons, no TCP 53290 listener)
/// -bind <ip> listen on ONE address instead of every interface.
/// Both listeners default to IPAddress.Any, which means
/// the second vPOD on a machine loses the port and the
/// console can only ever see one fake pod. Binding each
/// instance to its own address lets a whole roster run
/// side by side -- 200.0.0.111..118 are the internet
/// session's slot addresses, so an eight-pod session can
/// be exercised end to end with no cockpits and nobody
/// else in the room. Add the aliases first, e.g.
/// netsh interface ipv4 add address "Loopback" 200.0.0.113 255.255.255.0
///
/// DO NOT leave an unbound vPOD running alongside a bound
/// roster. Verified on this box 2026-07-25: Windows lets
/// IPAddress.Any bind the SAME port while specific
/// addresses already hold it (Linux would refuse). The
/// specific listeners still win for their own addresses,
/// but the unbound one silently swallows every address no
/// one claimed -- so a slot you thought was absent answers
/// anyway, which is precisely the stale-pod confusion the
/// console's claim gate exists to catch. Bind all of them
/// or none of them.
///
internal sealed class PodArguments
{
public int Port { get; private set; } = MungaSocket.ConsolePort; // 1501
/// Address both listeners bind to; null = every interface (default).
public IPAddress Bind { get; private set; }
public ApplicationID Application { get; private set; } = ApplicationID.RPL4;
public HostType HostType { get; private set; } = HostType.GameMachineHostType;
public int HostId { get; private set; } = 1;
public bool NoManage { get; private set; }
public static PodArguments Parse(string[] args)
{
PodArguments result = new PodArguments();
for (int i = 0; i < args.Length; i++)
{
string token = args[i].ToLowerInvariant();
switch (token)
{
case "-net":
if (i + 1 < args.Length && int.TryParse(args[i + 1], out int port))
{
result.Port = port;
i++;
}
break;
case "-bind":
// Unparseable means "every interface" rather than a hard failure:
// vPOD is a test tool and a typo here should not stop the run, but
// it must be visible -- Program logs the resolved bind at startup.
if (i + 1 < args.Length && IPAddress.TryParse(args[i + 1], out IPAddress bind))
{
result.Bind = bind;
i++;
}
break;
case "-lc":
result.HostType = HostType.MissionReviewHostType;
break;
case "-mr":
result.HostType = HostType.MissionReviewHostType;
break;
case "-res":
i += 2; // width height, ignored
break;
case "-app":
if (i + 1 < args.Length)
{
result.Application = ParseApp(args[i + 1]);
i++;
}
break;
case "-host":
if (i + 1 < args.Length && int.TryParse(args[i + 1], out int host))
{
result.HostId = host;
i++;
}
break;
case "-nomanage":
result.NoManage = true;
break;
}
}
return result;
}
private static ApplicationID ParseApp(string value)
{
switch (value.ToLowerInvariant())
{
case "bt":
case "btl4":
case "battletech":
return ApplicationID.BTL4;
case "nd":
case "ndl4":
return ApplicationID.NDL4;
default:
return ApplicationID.RPL4;
}
}
}
///
/// Mirror of the console's HostType (GameMachine=0, MissionReview=2, Console=3).
/// Duplicated here so vPOD does not depend on the console assembly.
///
internal enum HostType
{
GameMachineHostType = 0,
MissionReviewHostType = 2,
ConsoleHostType = 3
}