@page "/remote"
@using Insight.Web.Network.Remote;
@using Vaitr.Bus;
@using Insight.Domain.Network.Remote.Messages;
@using Insight.Domain.Network;
@using Insight.Web.Network;
@using MessagePack;
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.SignalR.Client
@using Vaitr.Network;
@using Insight.Web.Network.Broker;
@using static Insight.Web.Messages.RemoteMessages;
@inherits ComponentBase
@implements IDisposable

@inject ILogger<Index> Logger
@inject ISnackbar Snackbar
@inject IJSRuntime JSRuntime
@inject ISessionPool<RemoteSession, IMessage> SessionPool
@inject Bus Bus

<style>
    #screenViewerWrapper {
        display: flex;
        /* height: 80vh; */
        /* width: 80vw; */
        background-color: lightblue;
    }

    #screenViewer {
        -ms-touch-action: pan-x pan-y;
        touch-action: pan-x pan-y;
    }

    .virtualScreen{
        height: 100%;
        width: 100%;
        /* max-width: 99%;
        max-height: 99%; */
        object-fit: contain;
    }

    #cursorViewer.fit {
        z-index: 1;
    }
</style>

<MudGrid Justify="Justify.Center" Class="mt-5">
    <MudItem>
        <p>Status: @_status</p>
        <p>Casting: @_casting</p>
    </MudItem>
    @if (_casting)
    {
        <MudItem sm="12" md="12" lg="12">
            <div id="screenViewerWrapper">
                <canvas id="screenViewer" class="virtualScreen"></canvas>
                <canvas id="cursorViewer" class="virtualScreen fit"></canvas>
            </div>
        </MudItem>
    }
    <MudItem sm="12" md="12" lg="12">
        <MudGrid>
            <MudItem xs="12" md="12" lg="12">
                <MudStack Row Style="height: 42px;">
                     <MudInput T="string" @bind-Value="_id" Placeholder="ID" Variant="Variant.Outlined" FullWidth />
                     @if (_casting is false)
                    {
                        <MudButton OnClick="()=>OnRequestAsync()" Variant="Variant.Outlined" DisableElevation Color="Color.Info" Size="Size.Large" Style="background-color: white;">
                             Connect
                         </MudButton>
                    }
                    else
                    {
                        <MudButton OnClick="()=>_readOnly = !_readOnly" Variant="Variant.Outlined" DisableElevation Color="Color.Primary" Size="Size.Large" Style="background-color: white;">
                             ReadOnly
                         </MudButton>
                        <MudButton OnClick="()=>_stats = !_stats" Variant="Variant.Outlined" DisableElevation Color="Color.Primary" Size="Size.Large" Style="background-color: white;">
                             Stats
                         </MudButton>
                        <MudButton OnClick="()=>OnAbortAsync()" Variant="Variant.Outlined" DisableElevation Color="Color.Error" Size="Size.Large" Style="background-color: white;">
                             Disconnect
                         </MudButton>
                    }
                </MudStack>
            </MudItem>
            <MudSpacer />
        </MudGrid>
    </MudItem>
    <MudItem sm="12" md="12" lg="12">
        @if (_casting && _stats)
        {
            <MudItem sm="4" md="4" lg="4">
                <p>Timestamp: @_metric?.Timestamp</p>
                <p>Mbps: @_metric?.Mbps</p>
                <p>Fps: @_metric?.Fps</p>
                <p>RTT: @_metric?.RTT</p>
                @* <p>GPU: @_metric?.IsGpuAccelerated</p> *@
            </MudItem>
            <MudItem sm="4" md="4" lg="4">
                <p>Width: @_screen?.ViewWidth</p>
                <p>Height: @_screen?.ViewHeight</p>
                <p>Client Cursor X: @_cursor?.X | Client Cursor Y: @_cursor?.Y</p>
                <p>Canvas Cursor X: @_canvasCursorX | Canvas Cursor Y: @_canvasCursorY</p>
                @* <p>CB: @_clipboard?.Text</p> *@
            </MudItem>
            <MudItem sm="4" md="4" lg="4">
                <p>Status: @_status</p>
                <p>ReadOnly: @_readOnly</p>
            </MudItem>
        }
        @foreach (var client in SessionPool)
        {
            <p>@client.Value.Id</p>
        }
    </MudItem>
</MudGrid>

@code{
    private string? _id = null;
    private bool _rendered = false;
    private bool _casting = false;
    private bool _stats = false;
    private bool _readOnly = true;
    private string _status = "";

    private int _canvasCursorX;
    private int _canvasCursorY;

    private RemoteSession? _cachedSession;

    private CastMetric? _metric;
    private CastScreen? _screen;
    private CastCursor? _cursor;
    private CastClipboardReceived? _clipboard;

    private List<IDisposable> _subs = new();

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _rendered = true;
            _status = "rendered";

            _subs.Add(Bus.SubscribeAsync<RemoteConnected>(OnClientConnectedAsync, null));
            _subs.Add(Bus.SubscribeAsync<RemoteDisconnected>(OnClientDisconnectedAsync, null));
        }
    }

    [JSInvokable]
    public async Task HandleCursorEventAsync(double x, double y)
    {
        _canvasCursorX = (int)Math.Truncate(x);
        _canvasCursorY = (int)Math.Truncate(y);

        await InvokeAsync(StateHasChanged);

        // if not disabled, send cursor position
        if (_readOnly) return;

        if (SessionPool.FirstOrDefault().Value is not RemoteSession session) return;

        await session.SendAsync(new Domain.Network.Remote.Messages.CastCursorReceived
            {
                Id = _id,
                X = _canvasCursorX,
                Y = _canvasCursorY
            }, default);
    }

    private async ValueTask OnClientConnectedAsync(RemoteConnected message, CancellationToken cancellationToken)
    {
        Snackbar.Add($"{message.RemoteSession.Id} connected");

        await Task.Delay(1000);

        await InvokeAsync(StateHasChanged);
    }

    private async ValueTask OnClientDisconnectedAsync(RemoteDisconnected message, CancellationToken cancellationToken)
    {
        Snackbar.Add($"{message.RemoteSession.Id} disconnected");

        _casting = false;

        await Task.Delay(1000);

        await InvokeAsync(StateHasChanged);
    }

    private async ValueTask OnRequestAsync()
    {
        if (SessionPool.Where(p => p.Value.Id == _id).FirstOrDefault().Value is not RemoteSession session)
        {
            _status = $"client not available";

            Snackbar.Add("client not available", Severity.Error);
            return;
        }

        _cachedSession = session;

        _subs.Add(Bus.SubscribeAsync<CastRequestResponse>(OnRequestResponseAsync, p => p.Id == _id));

        _status = $"request to ({_id})";
        Snackbar.Add($"request to ({_id})");

        await _cachedSession.SendAsync(new Domain.Network.Remote.Messages.CastRequest
            {
                Id = _id,
                RequesterName = "Web",
                Mode = Domain.Enums.RemoteControlMode.Attended
            }, default);
    }

    private async ValueTask OnRequestResponseAsync(CastRequestResponse response, CancellationToken cancellationToken)
    {
        if (response.Accepted is false)
        {
            _status = "denied";
            _casting = false;

            Snackbar.Add($"client ({_id}) denied");
        }
        else
        {
            _subs.Add(Bus.SubscribeAsync<CastMetric>(OnMetricDataAsync, p => p.Id == _id));
            _subs.Add(Bus.SubscribeAsync<CastScreen>(OnScreenDataAsync, p => p.Id == _id));
            _subs.Add(Bus.SubscribeAsync<CastCursor>(OnCursorDataAsync, p => p.Id == _id));

            _status = $"client ({_id}) authorized";
            _casting = true;

            await JSRuntime.InvokeVoidAsync("setupCursorEvents", DotNetObjectReference.Create(this), "cursorViewer", "HandleCursorEventAsync");

            Snackbar.Add($"client ({_id}) authorized");
        }

        await InvokeAsync(StateHasChanged);
    }

    private async ValueTask OnMetricDataAsync(CastMetric metricData, CancellationToken cancellationToken)
    {
        _metric = metricData;
        await InvokeAsync(StateHasChanged);
    }

    private async ValueTask OnScreenDataAsync(CastScreen screenData, CancellationToken cancellationToken)
    {
        _screen = screenData;

        if (_rendered is false) return;

        try
        {
            await JSRuntime.InvokeVoidAsync(
                "displayScreenData", 
                "screenViewer", 
                screenData.ViewWidth,
                screenData.ViewHeight,
                screenData.Left,
                screenData.Top,
                screenData.Width,
                screenData.Height,
                screenData.Image);

            await _cachedSession.ScreenDataAckAsync(screenData, cancellationToken);
        }
        catch (Exception ex)
        {
            Snackbar.Add(ex.Message, Severity.Warning);
        }
    }

    private async ValueTask OnCursorDataAsync(CastCursor cursorData, CancellationToken cancellationToken)
    {
        if (_rendered is false) return;

        if (_cursor?.X == cursorData.X ||
            _cursor?.Y == cursorData.Y ||
            _cursor?.Icon == cursorData.Icon) return;

        _cursor = cursorData;
        await InvokeAsync(StateHasChanged);

        try
        {
            await JSRuntime.InvokeVoidAsync(
                "updateCursor",
                "screenViewer",
                "cursorViewer",
                cursorData.X,
                cursorData.Y,
                cursorData.Icon);
        }
        catch (Exception ex)
        {
            Snackbar.Add(ex.Message, Severity.Warning);
        }
    }

    private async ValueTask OnAbortAsync()
    {
        if (SessionPool.Where(p => p.Value.Id == _id).FirstOrDefault().Value is not RemoteSession session)
        {
            _status = $"client not available";

            Snackbar.Add("client not available", Severity.Error);
            return;
        }

        _status = $"abort to ({_id})";

        Snackbar.Add($"abort to ({_id})");

        //await session.SendAsync(new Domain.Network.Remote.Messages.CastAbort(), default);
        session.Disconnect();

        _casting = false;

        await InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
        foreach (var sub in _subs) sub?.Dispose();
        _subs.Clear();

        Logger.LogWarning("Remote Page disposed");
    }
}