using VPlasma.Core.Device;

namespace VPlasma.App;

/// <summary>
/// The glass: renders the device's 128×32 frame as a dot-matrix panel —
/// neon-orange plasma dots on dark glass, with a faint unlit grid so the
/// matrix reads as hardware. Half-intensity dots draw dimmer; flashing dots
/// and the flashing cursor blink on a shared phase timer that only runs
/// invalidations while something on screen actually blinks.
/// </summary>
internal sealed class PlasmaCanvas : Control
{
    private const int Bezel = 4;

    // Dot geometry: an N px pitch renders (N−1) px dots with a 1 px gap. The
    // standalone glass uses the default pitch 5 (4 px dots, a 640×160 dot
    // field); vRIO embeds the same control at pitch 3 to fit its encoder strip.
    private readonly int _dotPitch;
    private readonly int _dotSize;

    private static readonly Color BezelColor = Color.FromArgb(20, 18, 16);
    private static readonly Color GlassColor = Color.FromArgb(26, 14, 6);
    private static readonly Color UnlitDot = Color.FromArgb(46, 24, 10);
    private static readonly Color LitDot = Color.FromArgb(255, 106, 26);
    private static readonly Color HalfDot = Color.FromArgb(150, 62, 15);

    private readonly byte[] _frame = new byte[VPlasmaDevice.Width * VPlasmaDevice.Height];
    private readonly System.Windows.Forms.Timer _blinkTimer = new() { Interval = 266 };
    private bool _blinkPhase = true;
    private bool _anythingBlinks;

    private VPlasmaDevice.Point _cursor;
    private PlasmaCursorMode _cursorMode;
    private int _cellWidth = 6, _cellHeight = 8;

    public PlasmaCanvas(int dotPitch = 5)
    {
        _dotPitch = dotPitch;
        _dotSize = dotPitch - 1;
        SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint |
                 ControlStyles.OptimizedDoubleBuffer | ControlStyles.FixedWidth |
                 ControlStyles.FixedHeight, true);
        Size = SizeFor(dotPitch);
        BackColor = BezelColor;

        _blinkTimer.Tick += (_, _) =>
        {
            _blinkPhase = !_blinkPhase;
            if (_anythingBlinks)
                Invalidate();
        };
        _blinkTimer.Start();
    }

    /// <summary>Control size at a given dot pitch: the dot field plus bezel.</summary>
    public static Size SizeFor(int dotPitch) => new(
        VPlasmaDevice.Width * dotPitch + 2 * Bezel,
        VPlasmaDevice.Height * dotPitch + 2 * Bezel);

    /// <summary>Snapshot the device state and repaint. Call on the UI thread.</summary>
    public void UpdateFrom(VPlasmaDevice device)
    {
        device.CopyFrame(_frame);
        _cursor = device.CursorCell;
        _cursorMode = device.CursorMode;
        _cellWidth = device.CellWidth;
        _cellHeight = device.CellHeight;

        _anythingBlinks = _cursorMode == PlasmaCursorMode.Flashing;
        if (!_anythingBlinks)
            foreach (byte dot in _frame)
                if ((dot & VPlasmaDevice.PixelFlash) != 0)
                {
                    _anythingBlinks = true;
                    break;
                }

        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        g.Clear(BezelColor);

        using (var glass = new SolidBrush(GlassColor))
            g.FillRectangle(glass, Bezel - 4, Bezel - 4,
                VPlasmaDevice.Width * _dotPitch + 7, VPlasmaDevice.Height * _dotPitch + 7);

        using var unlit = new SolidBrush(UnlitDot);
        using var lit = new SolidBrush(LitDot);
        using var half = new SolidBrush(HalfDot);

        for (int y = 0; y < VPlasmaDevice.Height; ++y)
        {
            int rowOffset = y * VPlasmaDevice.Width;
            int py = Bezel + y * _dotPitch;
            for (int x = 0; x < VPlasmaDevice.Width; ++x)
            {
                byte dot = _frame[rowOffset + x];
                Brush brush = unlit;
                if ((dot & VPlasmaDevice.PixelLit) != 0
                    && ((dot & VPlasmaDevice.PixelFlash) == 0 || _blinkPhase))
                {
                    brush = (dot & VPlasmaDevice.PixelHalf) != 0 ? half : lit;
                }
                g.FillRectangle(brush, Bezel + x * _dotPitch, py, _dotSize, _dotSize);
            }
        }

        // Underline cursor on the bottom dot-row of its cell.
        if (_cursorMode == PlasmaCursorMode.Steady
            || (_cursorMode == PlasmaCursorMode.Flashing && _blinkPhase))
        {
            int cx = _cursor.Col * _cellWidth;
            int cy = _cursor.Row * _cellHeight + _cellHeight - 1;
            if (cy < VPlasmaDevice.Height)
                for (int i = 0; i < _cellWidth && cx + i < VPlasmaDevice.Width; ++i)
                    g.FillRectangle(lit, Bezel + (cx + i) * _dotPitch, Bezel + cy * _dotPitch, _dotSize, _dotSize);
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            _blinkTimer.Dispose();
        base.Dispose(disposing);
    }
}
