using VPlasma.Core.Device;

namespace VPlasma.App;

/// <summary>
/// vPLASMA main window: just the plasma glass, sized to the display. The
/// device end is hardwired to <see cref="PortName"/> (the plasma side of the
/// second com0com pair) and opened automatically — a retry timer keeps
/// trying while the port is missing or busy, and reopens it if it dies. The
/// DOSBox-X fork's named-pipe transport (<c>\\.\pipe\vplasma</c>) is served
/// permanently alongside it (if vRIO's built-in glass already holds the
/// pipe name, the server just retries until it frees up). The
/// title bar carries the port status; double-clicking the glass cycles the
/// self-test pages (banner, charset, graphics) through the wire parser, and
/// a right-click resets the display to its power-on state.
/// </summary>
internal sealed class MainForm : Form
{
    /// <summary>The plasma's fixed port: the device end of the COM2 pair.</summary>
    private const string PortName = "COM12";

    private readonly VPlasmaDevice _device = new();
    private readonly VPlasmaSerialService _service;
    private readonly VPlasmaPipeService _pipeService;
    private readonly PlasmaCanvas _canvas = new();

    private readonly System.Windows.Forms.Timer _reconnectTimer = new() { Interval = 3000 };
    private int _selfTestPage;

    public MainForm()
    {
        Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
        FormBorderStyle = FormBorderStyle.FixedSingle; // the glass is a fixed-size device
        MaximizeBox = false;
        ClientSize = _canvas.Size;
        StartPosition = FormStartPosition.CenterScreen;

        _service = new VPlasmaSerialService(_device);
        _pipeService = new VPlasmaPipeService(_device);

        _canvas.Location = new Point(0, 0);
        Controls.Add(_canvas);

        // Device / service events arrive on the serial reader thread; marshal to the UI.
        _device.Updated += () => RunOnUi(() => _canvas.UpdateFrom(_device));
        _service.ConnectionChanged += _ => RunOnUi(UpdateTitle);
        _pipeService.ClientChanged += _ => RunOnUi(UpdateTitle);

        _canvas.DoubleClick += (_, _) => RunSelfTest();
        _canvas.MouseClick += (_, e) =>
        {
            if (e.Button == MouseButtons.Right)
                _device.Reset();
        };

        // Open at startup, retry while closed (port missing/busy, host restarts).
        _reconnectTimer.Tick += (_, _) => EnsureOpen();
        _reconnectTimer.Start();

        FormClosed += (_, _) =>
        {
            _reconnectTimer.Dispose();
            _service.Dispose();
            _pipeService.Dispose();
        };

        _canvas.UpdateFrom(_device);
        EnsureOpen();
        _pipeService.Start();
    }

    private void EnsureOpen()
    {
        if (_service.IsOpen)
            return;

        try
        {
            _service.Open(PortName);
        }
        catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException or ArgumentException)
        {
            UpdateTitle(); // stays closed; the timer tries again
        }
    }

    private void UpdateTitle()
    {
        // ProductVersion carries the git stamp (see StampGitVersion in the csproj).
        string status = _service.IsOpen
            ? $"{PortName} @ 9600 8N1"
            : $"{PortName} unavailable — retrying";
        if (_pipeService.IsClientConnected)
            status += "  •  pipe host connected";
        Text = $"vPLASMA v{Application.ProductVersion} — {status}";
    }

    private void RunSelfTest()
    {
        byte[] bytes = PlasmaSelfTest.BuildPage(_selfTestPage);
        _device.OnReceived(bytes, bytes.Length);
        _selfTestPage = (_selfTestPage + 1) % PlasmaSelfTest.PageCount;
    }

    private void RunOnUi(Action action)
    {
        if (IsDisposed || Disposing)
            return;
        if (InvokeRequired)
            BeginInvoke(action);
        else
            action();
    }
}
