using Insight.Domain.Network.Remote.Messages;
using Insight.Remote.Shared.Abstractions;
using Insight.Remote.Shared.Native.Windows;
using Microsoft.Extensions.Logging;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

namespace Insight.Remote.Windows.Services;

internal class WinCursorProvider : ICursorProvider
{
    private readonly SemaphoreSlim _semaphore = new(1, 1);
    private readonly ILogger<WinCursorProvider> _logger;

    private User32.CursorInfo _cursor = new();
    private CastCursor _lastCursor = new();
    private nint _lastHandle = 0;
    private byte[] _lastIconBytes = Array.Empty<byte>();

    public WinCursorProvider(ILogger<WinCursorProvider> logger)
    {
        _logger = logger;
    }

    public async Task<CastCursor?> GetAsync(CancellationToken cancellationToken)
    {
        try
        {
            await _semaphore.WaitAsync(cancellationToken);

            _cursor.cbSize = Marshal.SizeOf(_cursor);

            if (User32.GetCursorInfo(out _cursor) is false) return null;

            if (_cursor.flags != User32.CURSOR_SHOWING)
            {
                _lastCursor = EmptyCursor;
                return _lastCursor;
            }

            if (_cursor.ptScreenPos.x == _lastCursor.X &&
                _cursor.ptScreenPos.y == _lastCursor.Y &&
                _cursor.hCursor == _lastHandle) return _lastCursor;

            var iconBytes = Array.Empty<byte>();

            if (_cursor.hCursor == _lastHandle) iconBytes = _lastIconBytes;
            else
            {
                using var ms = new MemoryStream();
                using var icon = Icon.FromHandle(_cursor.hCursor);
                icon.ToBitmap().Save(ms, ImageFormat.Png);

                iconBytes = ms.ToArray();
                _lastIconBytes = iconBytes;
            }

            _lastHandle = _cursor.hCursor;

            _lastCursor = new CastCursor
            {
                X = _cursor.ptScreenPos.x,
                Y = _cursor.ptScreenPos.y,
                Icon = iconBytes
            };

            return _lastCursor;
        }
        catch (TaskCanceledException)
        {
            return null;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
            return null;
        }
        finally
        {
            _semaphore.Release();
        }
    }

    public async Task SetAsync(int x, int y, CancellationToken cancellationToken)
    {
        if (_semaphore.CurrentCount == 0) return;

        try
        {
            await _semaphore.WaitAsync(cancellationToken);

            User32.SetCursorPos(x, y);
        }
        catch (TaskCanceledException) { }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
        finally
        {
            _semaphore.Release();
        }
    }

    private static CastCursor EmptyCursor => new()
    {
        X = 0,
        Y = 0,
        Icon = Array.Empty<byte>()
    };
}