Option Strict On
Option Explicit On

Imports System
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports SkiaSharp

Namespace Nexamas.UI.Application

    ''' <summary>
    ''' Internal presenter that renders Skia content into a Win32 layered Form.
    ''' Application code uses MASApplication.CreateLayeredSceneHost / MASApplicationLayeredSceneHost instead of owning this machinery.
    ''' </summary>
    Friend NotInheritable Class MASLayeredWindowPresenter

        Friend Delegate Sub LayeredDrawCallback(canvas As SKCanvas,
                                                width As Integer,
                                                height As Integer)

        Private Shared ReadOnly _imageSampling As New SKSamplingOptions(SKCubicResampler.Mitchell)

        Private ReadOnly _owner As Form
        Private _renderPadding As Integer
        Private _supersampleScale As Integer

        Friend Sub New(owner As Form,
                       Optional renderPadding As Integer = 2,
                       Optional supersampleScale As Integer = 2)

            If owner Is Nothing Then Throw New ArgumentNullException(NameOf(owner))

            _owner = owner
            _renderPadding = Math.Max(0, renderPadding)
            _supersampleScale = Math.Max(1, supersampleScale)
        End Sub

        Friend Property RenderPadding As Integer
            Get
                Return _renderPadding
            End Get
            Set(value As Integer)
                _renderPadding = Math.Max(0, value)
            End Set
        End Property

        Friend Property SupersampleScale As Integer
            Get
                Return _supersampleScale
            End Get
            Set(value As Integer)
                _supersampleScale = Math.Max(1, value)
            End Set
        End Property

        Friend Sub Render(drawCallback As LayeredDrawCallback)

            If drawCallback Is Nothing Then Throw New ArgumentNullException(NameOf(drawCallback))
            If _owner.IsDisposed Then Return
            If Not _owner.IsHandleCreated Then Return
            If _owner.ClientSize.Width <= 0 OrElse _owner.ClientSize.Height <= 0 Then Return

            Dim contentWidth As Integer = _owner.ClientSize.Width
            Dim contentHeight As Integer = _owner.ClientSize.Height

            Dim paddedWidth As Integer = contentWidth + (_renderPadding * 2)
            Dim paddedHeight As Integer = contentHeight + (_renderPadding * 2)
            Dim stride As Integer = paddedWidth * 4

            Dim supersampledWidth As Integer = paddedWidth * _supersampleScale
            Dim supersampledHeight As Integer = paddedHeight * _supersampleScale

            Dim bmi As MASLayeredWindowNativeMethods.BITMAPINFO =
                MASLayeredWindowNativeMethods.CreateTopDown32BitBitmapInfo(paddedWidth, paddedHeight)

            Dim screenDc As IntPtr = IntPtr.Zero
            Dim memDc As IntPtr = IntPtr.Zero
            Dim hBitmap As IntPtr = IntPtr.Zero
            Dim oldBitmap As IntPtr = IntPtr.Zero
            Dim bits As IntPtr = IntPtr.Zero

            Try
                screenDc = MASLayeredWindowNativeMethods.GetDC(IntPtr.Zero)
                If screenDc = IntPtr.Zero Then Throw New InvalidOperationException("GetDC failed.")

                memDc = MASLayeredWindowNativeMethods.CreateCompatibleDC(screenDc)
                If memDc = IntPtr.Zero Then Throw New InvalidOperationException("CreateCompatibleDC failed.")

                hBitmap = MASLayeredWindowNativeMethods.CreateDIBSection(
                    memDc,
                    bmi,
                    MASLayeredWindowNativeMethods.DIB_RGB_COLORS,
                    bits,
                    IntPtr.Zero,
                    0UI)

                If Not MASLayeredWindowNativeMethods.IsValidGdiHandle(hBitmap) OrElse bits = IntPtr.Zero Then
                    Throw New InvalidOperationException("CreateDIBSection failed.")
                End If

                oldBitmap = MASLayeredWindowNativeMethods.SelectObject(memDc, hBitmap)
                If MASLayeredWindowNativeMethods.IsNullOrInvalidGdiHandle(oldBitmap) Then
                    Throw New InvalidOperationException("SelectObject failed.")
                End If

                Dim finalInfo As New SKImageInfo(
                    paddedWidth,
                    paddedHeight,
                    SKColorType.Bgra8888,
                    SKAlphaType.Premul)

                Using finalSurface As SKSurface = SKSurface.Create(finalInfo, bits, stride)
                    If finalSurface Is Nothing Then Throw New InvalidOperationException("Final surface creation failed.")

                    Dim finalCanvas As SKCanvas = finalSurface.Canvas
                    finalCanvas.Clear(SKColors.Transparent)

                    If _supersampleScale <= 1 Then
                        finalCanvas.Save()
                        finalCanvas.Translate(_renderPadding, _renderPadding)
                        drawCallback.Invoke(finalCanvas, contentWidth, contentHeight)
                        finalCanvas.Restore()
                        finalCanvas.Flush()
                        finalSurface.Flush()
                    Else
                        Dim hiInfo As New SKImageInfo(
                            supersampledWidth,
                            supersampledHeight,
                            SKColorType.Bgra8888,
                            SKAlphaType.Premul)

                        Using hiSurface As SKSurface = SKSurface.Create(hiInfo)
                            If hiSurface Is Nothing Then Throw New InvalidOperationException("Supersampled surface creation failed.")

                            Dim hiCanvas As SKCanvas = hiSurface.Canvas
                            hiCanvas.Clear(SKColors.Transparent)
                            hiCanvas.Save()
                            hiCanvas.Scale(CSng(_supersampleScale), CSng(_supersampleScale))
                            hiCanvas.Translate(_renderPadding, _renderPadding)
                            drawCallback.Invoke(hiCanvas, contentWidth, contentHeight)
                            hiCanvas.Restore()
                            hiCanvas.Flush()
                            hiSurface.Flush()

                            Using hiImage As SKImage = hiSurface.Snapshot()
                                If hiImage Is Nothing Then Throw New InvalidOperationException("Snapshot failed.")

                                Dim destRect As New SKRect(0.0F, 0.0F, paddedWidth, paddedHeight)

                                Using paint As New SKPaint()
                                    paint.IsAntialias = True
                                    paint.IsDither = True
                                    finalCanvas.DrawImage(hiImage, destRect, _imageSampling, paint)
                                End Using
                            End Using
                        End Using

                        finalCanvas.Flush()
                        finalSurface.Flush()
                    End If
                End Using

                Dim dstPos As New MASLayeredWindowNativeMethods.POINT(_owner.Left, _owner.Top)
                Dim srcPos As New MASLayeredWindowNativeMethods.POINT(_renderPadding, _renderPadding)
                Dim winSize As MASLayeredWindowNativeMethods.SIZE =
                    MASLayeredWindowNativeMethods.CreateWindowSize(contentWidth, contentHeight)
                Dim blend As MASLayeredWindowNativeMethods.BLENDFUNCTION =
                    MASLayeredWindowNativeMethods.BLENDFUNCTION.CreatePerPixel(255)

                Dim ok As Boolean = MASLayeredWindowNativeMethods.UpdateLayeredWindow(
                    _owner.Handle,
                    screenDc,
                    dstPos,
                    winSize,
                    memDc,
                    srcPos,
                    0,
                    blend,
                    MASLayeredWindowNativeMethods.ULW_ALPHA)

                If Not ok Then Throw New Win32Exception(Marshal.GetLastWin32Error())

            Finally
                If memDc <> IntPtr.Zero AndAlso MASLayeredWindowNativeMethods.IsValidGdiHandle(oldBitmap) Then
                    MASLayeredWindowNativeMethods.SelectObject(memDc, oldBitmap)
                End If

                If MASLayeredWindowNativeMethods.IsValidGdiHandle(hBitmap) Then
                    MASLayeredWindowNativeMethods.DeleteObject(hBitmap)
                End If

                If memDc <> IntPtr.Zero Then MASLayeredWindowNativeMethods.DeleteDC(memDc)
                If screenDc <> IntPtr.Zero Then MASLayeredWindowNativeMethods.ReleaseDC(IntPtr.Zero, screenDc)
            End Try
        End Sub

    End Class

End Namespace
