text:
function New-NevergreenApp {
    <#
    .SYNOPSIS
        Returns a PSCustomObject to output.

    .DESCRIPTION
        Returns a PSCustomObject to output.

    .NOTES
        Site: https://packageology.com
        Author: Dan Gough
        Twitter: @packageologist

    .LINK
        https://github.com/DanGough/Nevergreen

    .PARAMETER Name
        Mandatory. The name of the application.

    .PARAMETER Uri
        Mandatory. The download URI for the application.

    .PARAMETER Version
        Mandatory. The application version.

    .PARAMETER Architecture
        Mandatory. Must match x86, x64, ARM32, ARM64 or Multi.

    .PARAMETER Type
        Mandatory. Must match Msi, Msp, Exe, Zip, Msix, AppX, AppV, 7z if supplied.

    .PARAMETER Language
        Optional. The language of the application installer, e.g. 'en'.

    .PARAMETER Ring
        Optional. The deployment ring, e.g. 'General', 'Preview'.

    .PARAMETER Channel
        Optional. The channel, e.g. 'Enterprise'.

    .PARAMETER Platform
        Optional. The platform, e.g. 'Windows Server'.

    .PARAMETER MD5
        Optional. The MD5 hash of the file.

    .PARAMETER SHA256
        Optional. The SHA256 hash of the file.

    .EXAMPLE
        New-NevergreenApp -Uri 'http://somewhere.com/something.exe' -Version '1.0' -Architecture 'x64' -Type 'Exe'

        Description:
        Outputs a PSCustomObject with the chosen properties.
    #>
    [CmdletBinding(SupportsShouldProcess = $False)]
    param (
        [Parameter(
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String] $Name,
        [Parameter(
            Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String] $Version,
        [Parameter(
            Mandatory = $true)]
        [ValidatePattern('^(http|https)://')]
        [Alias('Url')]
        [String] $Uri,
        [Parameter(
            Mandatory = $true)]
        [ValidateSet('x86', 'x64', 'ARM32', 'ARM64', 'Multi')]
        [String] $Architecture,
        [Parameter(
            Mandatory = $true)]
        [ValidateSet('Msi', 'Msp', 'Exe', 'Zip', 'Msix', 'AppX', 'AppV', '7z')]
        [String] $Type,
        [Parameter(
            Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String] $Language,
        [Parameter(
            Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String] $Ring,
        [Parameter(
            Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String] $Channel,
        [Parameter(
            Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String] $Platform,
        [Parameter(
            Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String] $MD5,
        [Parameter(
            Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String] $SHA256
    )

    $Output = [PSCustomObject]@{
        Name    = $Name
    }

    if ($Ring) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Ring' -Value $Ring
    }

    if ($Channel) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Channel' -Value $Channel
    }

    if ($Language) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Language' -Value $Language
    }

    if ($Platform) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Platform' -Value $Platform
    }

    if ($Architecture) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Architecture' -Value $Architecture
    }

    if ($Type) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Type' -Value $Type
    }

    Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Version' -Value $Version
    Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Uri' -Value $Uri

    if ($MD5) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'MD5' -Value $MD5
    }

    if ($SHA256) {
        Add-Member -InputObject $Output -MemberType NoteProperty -Name 'SHA256' -Value $SHA256
    }

    $Output
}


text:
function Get-Version {
    <#
    .SYNOPSIS
        Extracts a version number from either a string or the content of a web page using a chosen or pre-defined match pattern.

    .DESCRIPTION
        Extracts a version number from either a string or the content of a web page using a chosen or pre-defined match pattern.

    .NOTES
        Site: https://packageology.com
        Author: Dan Gough
        Twitter: @packageologist

    .LINK
        https://github.com/DanGough/Nevergreen

    .PARAMETER String
        The string to process.

    .PARAMETER Uri
        The Uri to load web content from to process.

    .PARAMETER UserAgent
        Optional parameter to provide a user agent for Invoke-WebRequest to use. Examples are:

        Googlebot: 'Googlebot/2.1 (+http://www.google.com/bot.html)'
        Microsoft Edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'

    .PARAMETER Pattern
        Optional RegEx pattern to use for version matching. Pattern to return must be included in parentheses.

    .PARAMETER ReplaceWithDot
        Switch to automatically replace characters - or _ with . in detected version.

    .EXAMPLE
        Get-Version -String 'http://somewhere.com/somefile_1.2.3.exe'

        Description:
        Returns '1.2.3'
    #>
    [CmdletBinding(SupportsShouldProcess = $False)]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ParameterSetName = 'String')]
        [ValidateNotNullOrEmpty()]
        [String[]] $String,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Uri')]
        [ValidatePattern('^(http|https)://')]
        [String] $Uri,
        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'Uri')]
        [String] $UserAgent,
        [Parameter(
            Mandatory = $false,
            Position = 1)]
        [ValidateNotNullOrEmpty()]
        [String] $Pattern = '((?:\d+\.)+\d+)',
        [Switch] $ReplaceWithDot
    )

    begin {

    }

    process {

        if ($PsCmdlet.ParameterSetName -eq 'Uri') {

            $ProgressPreference = 'SilentlyContinue'

            try {
                $ParamHash = @{
                    Uri              = $Uri
                    Method           = 'GET'
                    UseBasicParsing  = $True
                    DisableKeepAlive = $True
                    ErrorAction      = 'Stop'
                }

                if ($UserAgent) {
                    $ParamHash.UserAgent = $UserAgent
                }

                $String = (Invoke-WebRequest @ParamHash).Content
            }
            catch {
                Write-Error "Unable to query URL '$Uri': $($_.Exception.Message)"
            }

        }

        foreach ($CurrentString in $String) {

            if ($CurrentString -match $Pattern) {
                if ($ReplaceWithDot) {
                    $matches[1].Replace('-','.').Replace('_','.')
                }
                else {
                    $matches[1]
                }
            }
            else {
                Write-Warning "No version found within $CurrentString using pattern $Pattern"
            }

        }

    }

    end {
    }

}

repo_name: DanGough/Nevergreen

repo_language: PowerShell

mime_type: text/plain


text:
function Resolve-Uri {
    <#
    .SYNOPSIS
        Resolves a URI and also returns the filename and last modified date if found.

    .DESCRIPTION
        Resolves a URI and also returns the filename and last modified date if found.

    .NOTES
        Site: https://packageology.com
        Author: Dan Gough
        Twitter: @packageologist

    .LINK
        https://github.com/DanGough/Nevergreen

    .PARAMETER Uri
        The URI resolve. Accepts an array of strings or pipeline input.

    .PARAMETER UserAgent
        Optional parameter to provide a user agent for Invoke-WebRequest to use. Examples are:

        Googlebot: 'Googlebot/2.1 (+http://www.google.com/bot.html)'
        Microsoft Edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'

    .EXAMPLE
        Resolve-Uri -Uri 'http://somewhere.com/somefile.exe'

        Description:
        Returns the absolute redirected URI, filename and last modified date.
    #>
    [CmdletBinding(SupportsShouldProcess = $False)]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [ValidatePattern('^(http|https)://')]
        [Alias('Url')]
        [String[]] $Uri,
        [Parameter(
            Mandatory = $false,
            Position = 1)]
        [String] $UserAgent,
        [System.Collections.Hashtable] $Headers
    )

    begin {
        $ProgressPreference = 'SilentlyContinue'
    }

    process {

        foreach ($UriToResolve in $Uri) {

            try {

                $ParamHash = @{
                    Uri              = $UriToResolve
                    Method           = 'Head'
                    UseBasicParsing  = $True
                    DisableKeepAlive = $True
                    ErrorAction      = 'Stop'
                }

                if ($UserAgent) {
                    $ParamHash.UserAgent = $UserAgent
                }

                if ($Headers) {
                    $ParamHash.Headers = $Headers
                }

                $Response = Invoke-WebRequest @ParamHash

                if ($IsCoreCLR) {
                    $ResolvedUri = $Response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri
                }
                else {
                    $ResolvedUri = $Response.BaseResponse.ResponseUri.AbsoluteUri
                }

                Write-Verbose "$($MyInvocation.MyCommand): URI resolved to: $ResolvedUri"

                #PowerShell 7 returns each header value as single unit arrays instead of strings which messes with the -match operator coming up, so use Select-Object:
                $ContentDisposition = $Response.Headers.'Content-Disposition' | Select-Object -First 1

                if ($ContentDisposition -match 'filename="?([^\\/:\*\?"<>\|]+)') {
                    $FileName = $matches[1]
                    Write-Verbose "$($MyInvocation.MyCommand): Content-Disposition header found: $ContentDisposition"
                    Write-Verbose "$($MyInvocation.MyCommand): File name determined from Content-Disposition header: $FileName"
                }
                else {
                    $Slug = [uri]::UnescapeDataString($ResolvedUri.Split('?')[0].Split('/')[-1])
                    if ($Slug -match '^[^\\/:\*\?"<>\|]+\.[^\\/:\*\?"<>\|]+$') {
                        Write-Verbose "$($MyInvocation.MyCommand): URI slug is a valid file name: $FileName"
                        $FileName = $Slug
                    }
                    else {
                        $FileName = $null
                    }
                }

                try {
                    $LastModified = [DateTime]($Response.Headers.'Last-Modified' | Select-Object -First 1)
                    Write-Verbose "$($MyInvocation.MyCommand): Last modified date: $LastModified"
                }
                catch {
                    Write-Verbose "$($MyInvocation.MyCommand): Unable to parse date from last modified header: $($Response.Headers.'Last-Modified')"
                    $LastModified = $null
                }

            }
            catch {
                Throw "$($MyInvocation.MyCommand): Unable to resolve URI: $($_.Exception.Message)"
            }

            if ($ResolvedUri) {
                [PSCustomObject]@{
                    Uri          = $ResolvedUri
                    FileName     = $FileName
                    LastModified = $LastModified
                }
            }

        }
    }

    end {
    }

}


text:
function Set-UriPrefix {
    <#
    .SYNOPSIS
        Prefixes a URI with a specific domain if one is not already present.

    .DESCRIPTION
        Some web pages store URIs as relative paths, this function will prefix such URLs with a chosen domain.

    .NOTES
        Site: https://packageology.com
        Author: Dan Gough
        Twitter: @packageologist

    .LINK
        https://github.com/DanGough/Nevergreen

    .PARAMETER Uri
        The URI to process.

    .PARAMETER Prefix
        The prefix to add to the URI.

    .EXAMPLE
        Set-UriPrefix -Uri '/somefile.exe' -Prefix 'http://somewhere.com'

        Description:
        Returns 'http://somewhere.com/somefile.exe'
    #>
    [CmdletBinding(SupportsShouldProcess = $False)]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [Alias('Url')]
        [System.String] $Uri,
        [Parameter(
            Mandatory = $true,
            Position = 1)]
        [ValidatePattern('^(http|https)://')]
        [System.String] $Prefix
    )

    if ($Uri -like '^http') {
        # Nothing to do, return input
        $Uri
    }
    else {
        # Output joined strings
        $Prefix.TrimEnd('/') + "/" + $Uri.TrimStart('/')
    }
}


text:
function Get-Link {
    <#
    .SYNOPSIS
        Returns a specific link from a web page.

    .DESCRIPTION
        Returns a specific link from a web page.

    .NOTES
        Site: https://packageology.com
        Author: Dan Gough
        Twitter: @packageologist

    .LINK
        https://github.com/DanGough/Nevergreen

    .PARAMETER Uri
        The URI to query.

    .PARAMETER MatchProperty
        Whether the RegEx pattern should be applied to the href, outerHTML, class, title or data-filename of the link.

    .PARAMETER Pattern
        The RegEx pattern to apply to the selected property. Supply an array of patterns to receive multiple links.

    .PARAMETER ReturnProperty
        Optional. Specifies which property to return from the link. Defaults to href, but 'data-filename' can also be useful to retrieve.

    .PARAMETER UserAgent
        Optional parameter to provide a user agent for Invoke-WebRequest to use. Examples are:

        Googlebot: 'Googlebot/2.1 (+http://www.google.com/bot.html)'
        Microsoft Edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'

    .EXAMPLE
        Get-Link -Uri 'http://somewhere.com' -MatchProperty href -Pattern '\.exe$'

        Description:
        Returns first download link matching *.exe from http://somewhere.com.
    #>
    [CmdletBinding(SupportsShouldProcess = $False)]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline)]
        [ValidatePattern('^(http|https)://')]
        [Alias('Url')]
        [String] $Uri,
        [Parameter(
            Mandatory = $true,
            Position = 1)]
        [ValidateSet('href', 'outerHTML', 'innerHTML', 'outerText', 'innerText', 'class', 'title', 'tagName', 'data-filename')]
        [String] $MatchProperty,
        [Parameter(
            Mandatory = $true,
            Position = 2)]
        [ValidateNotNullOrEmpty()]
        [String[]] $Pattern,
        [Parameter(
            Mandatory = $false,
            Position = 3)]
        [ValidateNotNullOrEmpty()]
        [String] $ReturnProperty = 'href',
        [Parameter(
            Mandatory = $false)]
        [String] $UserAgent,
        [System.Collections.Hashtable] $Headers,
        [Switch] $PrefixDomain,
        [Switch] $PrefixParent
    )

    $ProgressPreference = 'SilentlyContinue'

    $ParamHash = @{
        Uri              = $Uri
        Method           = 'GET'
        UseBasicParsing  = $True
        DisableKeepAlive = $True
        ErrorAction      = 'Stop'
    }

    if ($UserAgent) {
        $ParamHash.UserAgent = $UserAgent
    }

    if ($Headers) {
        $ParamHash.Headers = $Headers
    }

    try {
        $Response = Invoke-WebRequest @ParamHash

        foreach ($CurrentPattern in $Pattern) {
            $Link = $Response.Links | Where-Object $MatchProperty -Match $CurrentPattern | Select-Object -First 1 -ExpandProperty $ReturnProperty

            if ($PrefixDomain) {
                $BaseURL = ($Uri -split '/' | Select-Object -First 3) -join '/'
                $Link = Set-UriPrefix -Uri $Link -Prefix $BaseURL
            }
            elseif ($PrefixParent) {
                $BaseURL = ($Uri -split '/' | Select-Object -SkipLast 1) -join '/'
                $Link = Set-UriPrefix -Uri $Link -Prefix $BaseURL
            }

            $Link

        }
    }
    catch {
        Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
    }

}


text:
function Find-NevergreenApp {
    <#
    .SYNOPSIS
        Returns a list of all apps currently supported by Nevergreen.

    .DESCRIPTION
        Retrieves a list of supported applications by querying Get-*.ps1 under the Apps folder.

    .NOTES
        Site: https://packageology.com
        Author: Dan Gough
        Twitter: @packageologist

    .LINK
        https://github.com/DanGough/Nevergreen

    .PARAMETER Name
        The application name to return details for. Uses a RegEx match so will return all apps where the name contains the supplied string. Accepts an array for multiple matches, or can also accept values from the pipeline.

    .EXAMPLE
        Find-NevergreenApp

        Description:
        Returns a list of all currently supported applications.

    .EXAMPLE
        Find-NevergreenApp -Name 'Microsoft'

        Description:
        Returns a list of all currently supported applications with 'Microsoft' in the name.

    .EXAMPLE
        Find-NevergreenApp -Name 'Adobe','Microsoft'

        Description:
        Returns a list of all currently supported applications matching 'Adobe' or 'Microsoft'.
    #>
    [CmdletBinding(SupportsShouldProcess = $False)]
    param (
        [Parameter(
            Mandatory = $false,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [System.String[]] $Name = ''
    )

    begin {
        $AppDir = [System.IO.Path]::Combine((Split-Path -Path $PSScriptRoot -Parent), 'Apps')
        $Apps = (Get-ChildItem -Path $AppDir).BaseName | ForEach-Object { $_.Split('-')[1] }
        $Results = @()
    }

    process {
        foreach ($AppName in $Name) {
            $Results += $Apps | Where-Object { $_ -match $AppName }
        }
    }

    end {
        $Results | Select-Object -Unique
    }
}


text:
function Get-NevergreenApp {
    <#
    .SYNOPSIS
        Attempts to retrieve the latest version and download link for the selected application.

    .DESCRIPTION
        The application name must be a valid supported name - use Find-Nevergreen app to list supported applications. The output of this command may retrieve multiple results, filter using Where-Object and Select-Object where appropriate.

    .NOTES
        Site: https://packageology.com
        Author: Dan Gough
        Twitter: @packageologist

    .LINK
        https://github.com/DanGough/Nevergreen

    .PARAMETER Name
        The application name to return details for. Must be an exact match for a supported application name.

    .EXAMPLE
        Get-NevergreenApp -Name MicrosoftPowerBIDesktop

        Description:
        Returns the latest version and download links for all available platforms.

    .EXAMPLE
        Get-NevergreenApp -Name MicrosoftPowerBIDesktop | Where-Object {$_.Architecture -eq 'x64'}

        Description:
        Returns the latest version and download links for the 64-bit version only.

    .EXAMPLE
        Find-NevergreenApp | Get-NevergreenApp

        Description:
        List results for all apps!
    #>
    [OutputType([System.Management.Automation.PSObject])]
    [CmdletBinding(SupportsShouldProcess = $False)]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            HelpMessage = "Specify an application name. Use Find-NevergreenApp to list supported applications.")]
        [ValidateNotNull()]
        [System.String[]] $Name
    )

    begin {
        $AppDir = [System.IO.Path]::Combine((Split-Path -Path $PSScriptRoot -Parent), 'Apps')
        $ProgressPreference = 'SilentlyContinue'
    }

    process {
        foreach ($AppName in $Name) {
            $Script = [System.IO.Path]::Combine($AppDir, "Get-$AppName.ps1")
            if (Test-Path -LiteralPath $Script -PathType Leaf) {
                try {
                    $Output = &$Script
                    if ($Output.Version -and $Output.Uri) {
                        $Output
                    }
                }
                catch {
                    Write-Error -Message "Error retriving results for '$AppName': $($_.Exception.Message)"
                }
            }
            else {
                Write-Error "Unknown application name: $AppName"
            }
        }
    }

    end {
    }
}


text:
$MajorVersion = 13

do {
    try {

        $Releases = Invoke-RestMethod -Uri "https://support.freedomscientific.com/Downloads/OfflineInstallers/GetInstallers?product=Fusion&version=$MajorVersion&language=mul&releaseType=Offline" -DisableKeepAlive

        foreach ($Release in $Releases) {

            $Version = $Release.FileName | Get-Version
            $Type = (Get-Culture).TextInfo.ToTitleCase([System.IO.Path]::GetExtension($Release.InstallerLocationHTTP).Replace('.',''))
            New-NevergreenApp -Name 'Fusion' -Version $Version -Uri $Release.InstallerLocationHTTP -Architecture 'Multi' -Type $Type

        }

    }
    catch {
        Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
    }

    $MajorVersion++

}
until ($MajorVersion -ge ([int](Get-Date -Format 'yy') - 6) -and $Releases.Count -eq 0)


text:
$Version = Get-Version -Uri 'https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html' -Pattern 'latest\srelease\s\(((?:\d+\.)+\d+)\)'

$URL64,$URLARM64,$URL32,$URLARM32 = Get-Link -Uri 'https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html' -MatchProperty href -Pattern 'putty-64bit.+\.msi','putty-arm64.+\.msi','putty-\d+\.\d+.+\.msi','putty-arm32.+\.msi'

New-NevergreenApp -Name 'Simon Tatham PuTTY' -Version $Version -Uri $URL64 -Architecture 'x64' -Type 'Msi'
New-NevergreenApp -Name 'Simon Tatham PuTTY' -Version $Version -Uri $URLARM64 -Architecture 'ARM64' -Type 'Msi'
New-NevergreenApp -Name 'Simon Tatham PuTTY' -Version $Version -Uri $URL32 -Architecture 'x86' -Type 'Msi'
New-NevergreenApp -Name 'Simon Tatham PuTTY' -Version $Version -Uri $URLARM32 -Architecture 'ARM32' -Type 'Msi'


text:
$MajorVersion = 12

do {
    try {

        $Releases = Invoke-RestMethod -Uri "https://support.freedomscientific.com/Downloads/OfflineInstallers/GetInstallers?product=ZoomText&version=$MajorVersion&language=enu&releaseType=Offline" -DisableKeepAlive

        foreach ($Release in $Releases) {

            $Version = $Release.FileName | Get-Version
            $Type = (Get-Culture).TextInfo.ToTitleCase([System.IO.Path]::GetExtension($Release.InstallerLocationHTTP).Replace('.',''))
            New-NevergreenApp -Name 'ZoomText' -Version $Version -Uri $Release.InstallerLocationHTTP -Architecture 'Multi' -Type $Type

        }

    }
    catch {
        Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
    }

    $MajorVersion++

}
until ($MajorVersion -ge ([int](Get-Date -Format 'yy') - 6) -and $Releases.Count -eq 0)


text:
$Releases = @(
    @{Architecture = 'x64'; Type = 'exe'; Pattern = 'Notepad3_((?:\d+\.)+\d+)_x64_Setup\.exe' }
    @{Architecture = 'x86'; Type = 'exe'; Pattern = 'Notepad3_((?:\d+\.)+\d+)_x86_Setup\.exe' }
)

foreach ($Release in $Releases) {
    try {
        $URL = Get-Link -Uri 'https://www.rizonesoft.com/downloads/notepad3/' -MatchProperty outerHTML -Pattern $Release.Pattern -ReturnProperty href -ErrorAction Stop
        $Version = Get-Version -Uri 'https://www.rizonesoft.com/downloads/notepad3/' -Pattern $Release.Pattern -ErrorAction Stop
        New-NevergreenApp -Name 'Notepad3' -Version $Version -Uri $URL -Architecture $Release.Architecture -Type $Release.Type
    }
    catch {
        Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
    }
}


text:
try {

    $URL = Get-Link -Uri 'https://www.rstudio.com/products/rstudio/download/' -MatchProperty href -Pattern '\.exe$'

    $Version = $URL | Get-Version

    New-NevergreenApp -Name 'RStudio' -Version $Version -Uri $URL -Architecture 'x64' -Type 'Exe'

}
catch {
    Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
}


text:
$DownloadURL = 'https://raw.githubusercontent.com/ItzLevvie/MicrosoftTeams-msinternal/master/defconfig'
$DownloadText = (Invoke-WebRequest -Uri $DownloadURL -DisableKeepAlive -UseBasicParsing).Content

$Rings = @('Daily', 'Experimental', 'Preview', 'Production')
$Architectures = @('x64', 'x86', 'ARM64')
$Types = @('Exe', 'MSI')

foreach ($Ring in $Rings) {
    foreach ($Architecture in $Architectures) {
        foreach ($Type in $Types) {

            $RegEx = "$Ring (?:.+\n)+((?:\d+\.)+\d+).+win-$Architecture.+(http.+\.$Type)"

            if ($DownloadText -match $RegEx) {
                New-NevergreenApp -Name 'Microsoft Teams' -Version $matches[1] -Uri $matches[2] -Architecture $Architecture -Type $Type -Ring $Ring
            }
            else {
                Write-Warning "No match found for Microsoft Teams $Channel $Architecture $Type"
            }

        }
    }
}


text:
$MajorVersions = @(8,11,16)
$JvmTypes = @('HotSpot', 'OpenJ9')
$HeapSizes = @('Normal', 'Large')
$ImageTypes = @('JDK', 'JRE')
$Architectures = @('x32','x64')

foreach ($MajorVersion in $MajorVersions) {
    foreach ($JvmType in $JvmTypes) {

        $ReleasesURL = "https://api.adoptopenjdk.net/v3/assets/latest/$MajorVersion/$($JvmType.ToLower())?release=latest&jvm_impl=$($JvmType.ToLower())&vendor=adoptopenjdk&"

        try {

            $Releases = Invoke-RestMethod -Uri $ReleasesURL -DisableKeepAlive -ErrorAction Stop

            foreach ($HeapSize in $HeapSizes) {
                foreach ($ImageType in $ImageTypes) {
                    foreach ($Architecture in $Architectures) {

                        $Release = $Releases | Where-Object { $_.binary.os -eq 'windows' -and $_.binary.heap_size -eq $HeapSize -and $_.binary.image_type -eq $ImageType -and $_.binary.architecture -eq $Architecture }

                        if ($Release) {
                            if ($Release.Count -gt 1) {
                                Write-Warning "Unexpected multiple releases returned for AdoptOpenJDK $MajorVersion $JvmType $HeapSize $ImageType $Architecture"
                            }

                            if ($Architecture -eq 'x32') {
                                $Architecture = 'x86'
                            }

                            $Release | ForEach-Object {
                                $Version = ($_.version.openjdk_version.Replace('_','.').Replace('+','.') -Replace '-[a-z]','.') -Replace '^1\.8\.','8.'
                                New-NevergreenApp -Name "AdoptOpenJDK$MajorVersion" -Version $Version -Uri $_.binary.installer.link -Platform "$JvmType $HeapSize $ImageType" -Architecture $Architecture -Type 'Msi'
                                New-NevergreenApp -Name "AdoptOpenJDK$MajorVersion" -Version $Version -Uri $_.binary.package.link -Platform "$JvmType $HeapSize $ImageType" -Architecture $Architecture -Type 'Zip'
                            }
                        }

                    }
                }
            }
        }
        catch {
            Write-Error "$($MyInvocation.MyCommand): Error querying REST API $($ReleasesURL): $($_.Exception.Message)"
        }
    }
}


text:
$URL64 = 'https://go.microsoft.com/fwlink/?linkid=2068602'
$URL32 = 'https://go.microsoft.com/fwlink/?linkid=2098960'
$URLARM64 = 'https://go.microsoft.com/fwlink/?linkid=2098961'

$Version = (Resolve-Uri -Uri $URL64).FileName | Get-Version

New-NevergreenApp -Name 'Microsoft Remote Desktop' -Version $Version -Uri $URL32 -Architecture 'x86'
New-NevergreenApp -Name 'Microsoft Remote Desktop' -Version $Version -Uri $URL64 -Architecture 'x64'
New-NevergreenApp -Name 'Microsoft Remote Desktop' -Version $Version -Uri $URLARM64 -Architecture 'ARM64'


text:
try {

    $URL = Get-Link -Uri 'https://www.audacityteam.org/download/windows/' -MatchProperty href -Pattern '\.exe$'

    $Version = $URL | Get-Version

    New-NevergreenApp -Name 'Audacity' -Version $Version -Uri $URL -Architecture 'x86' -Type 'Exe'

}
catch {
    Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
}


text:
$MajorVersion = 20

do {
    try {

        $Releases = Invoke-RestMethod -Uri "https://support.freedomscientific.com/Downloads/OfflineInstallers/GetInstallers?product=JAWS&version=$MajorVersion&language=mul&releaseType=Offline" -DisableKeepAlive

        foreach ($Release in $Releases) {

            if ($Release.InstallerPlatform -eq '32-bit') {
                $Architecture = 'x86'
            }
            elseif ($Release.InstallerPlatform -eq '64-bit') {
                $Architecture = 'x64'
            }
            else{
                Write-Warning "Unrecognised platform: $($Release.InstallerPlatform)"
                break
            }

            $Version = $Release.FileName | Get-Version
            $Type = (Get-Culture).TextInfo.ToTitleCase([System.IO.Path]::GetExtension($Release.InstallerLocationHTTP).Replace('.',''))
            New-NevergreenApp -Name 'JAWS' -Version $Version -Uri $Release.InstallerLocationHTTP -Architecture $Architecture -Type $Type
        }

    }
    catch {
        Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
    }

    $MajorVersion++

}
until ($MajorVersion -ge ([int](Get-Date -Format 'yy') + 1) -and $Releases.Count -eq 0)


text:
$URL64 = (Resolve-Uri -Uri 'https://www.tableau.com/downloads/desktop/pc64').Uri
$Version = $URL64 | Get-Version -Pattern '((?:\d+-)+\d+)\.exe$' -ReplaceWithDot

New-NevergreenApp -Name 'Tableau Desktop' -Version $Version -Uri $URL64 -Architecture 'x64' -Type 'Exe'


text:
try {

    $URL = Get-Link -Uri 'https://appventix.com/thank-you/' -MatchProperty outerHTML -Pattern 'Download AppVentix'

    $Version = (Resolve-Uri -Uri $URL).FileName | Get-Version

    New-NevergreenApp -Name 'AppVentix' -Version $Version -Uri $URL -Architecture 'x64' -Type 'Zip'

}
catch {
    Write-Error "$($MyInvocation.MyCommand): $($_.Exception.Message)"
}


text:
$URL32 = Get-Link -Uri 'https://www.masterpackager.com/pricing' -MatchProperty href -Pattern '\.msi$' -PrefixDomain

try {
    $Version = Get-Version -Uri 'https://www.masterpackager.com/uploads/file_archive/version.txt' -ErrorAction Stop
}
catch {
    $Version = $URL32 | Get-Version
}

New-NevergreenApp -Name 'Master Packager' -Version $Version -Uri $URL32 -Architecture 'x86' -Type 'Msi'


text:
$URL64 = (Resolve-Uri -Uri 'https://www.tableau.com/downloads/reader/pc64').Uri
$Version = $URL64 | Get-Version -Pattern '((?:\d+-)+\d+)\.exe$' -ReplaceWithDot

New-NevergreenApp -Name 'Tableau Reader' -Version $Version -Uri $URL64 -Architecture 'x64' -Type 'Exe'


text:
#URL not absolute. Exists on Github so submit to Evergreen instead.

$URL = Get-Link -Uri 'https://desowin.org/usbpcap/' -MatchProperty href -Pattern 'USBPcapSetup-(?:\d+\.)+\d+\.exe$' -PrefixParent

$Version = $URL | Get-Version

New-NevergreenApp -Name 'USBPcap' -Version $Version -Uri $URL -Architecture 'Multi' -Type 'Exe'


text:
org.gradle.jvmargs=-Xmx2G

# Mod Info
maven_group = com.example
archives_base_name = modid
mod_version = 0.0.1

minecraft_version = 1.18.2

# Dependencies
# https://fabricmc.net/develop
fabric_loader_version = 0.14.11
fabric_api_version = 0.67.0+1.18.2

# Mappings
# https://lambdaurora.dev/tools/import_quilt.html
qm_version = 26
# https://parchmentmc.org/docs/getting-started
parchment_version = 2022.11.06

# Create
# https://modrinth.com/mod/create-fabric/versions
create_version = 0.5.0.i-944+1.18.2

# Development QOL
# Create supports all 3 recipe viewers: JEI, REI, and EMI. This decides which is enabled at runtime.
# set to disabled to have none of them.
recipe_viewer = unspecified
# JEI - https://www.curseforge.com/minecraft/mc-mods/jei/files/all
jei_version = 10.2.1.283
# REI - https://modrinth.com/mod/roughly-enough-items/versions
rei_version = 8.3.583
# EMI - https://modrinth.com/mod/emi/versions
emi_version = 0.5.3+1.18.2

# Mod Menu - https://modrinth.com/mod/modmenu/versions
modmenu_version = 3.2.5
# LazyDFU - https://modrinth.com/mod/lazydfu/versions
lazydfu_version = 0.1.2

repo_language: Java


text:
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


text:
#!/bin/sh

#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

##############################################################################
#
#   Gradle start up script for POSIX generated by Gradle.
#
#   Important for running:
#
#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
#       noncompliant, but you have some other compliant shell such as ksh or
#       bash, then to run this script, type that shell name before the whole
#       command line, like:
#
#           ksh Gradle
#
#       Busybox and similar reduced shells will NOT work, because this script
#       requires all of these POSIX shell features:
#         * functions;
#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
#         * compound commands having a testable exit status, especially «case»;
#         * various built-in commands including «command», «set», and «ulimit».
#
#   Important for patching:
#
#   (2) This script targets any POSIX shell, so it avoids extensions provided
#       by Bash, Ksh, etc; in particular arrays are avoided.
#
#       The "traditional" practice of packing multiple parameters into a
#       space-separated string is a well documented source of bugs and security
#       problems, so this is (mostly) avoided, by progressively accumulating
#       options in "$@", and eventually passing that to Java.
#
#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
#       see the in-line comments for details.
#
#       There are tweaks for specific operating systems such as AIX, CygWin,
#       Darwin, MinGW, and NonStop.
#
#   (3) This script is generated from the Groovy template
#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
#       within the Gradle project.
#
#       You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: $0 may be a link
app_path=$0

# Need this for daisy-chained symlinks.
while
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
    [ -h "$app_path" ]
do
    ls=$( ls -ld "$app_path" )
    link=${ls#*' -> '}
    case $link in             #(
      /*)   app_path=$link ;; #(
      *)    app_path=$APP_HOME$link ;;
    esac
done

APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
    echo "$*"
} >&2

die () {
    echo
    echo "$*"
    echo
    exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in                #(
  CYGWIN* )         cygwin=true  ;; #(
  Darwin* )         darwin=true  ;; #(
  MSYS* | MINGW* )  msys=true    ;; #(
  NONSTOP* )        nonstop=true ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD=$JAVA_HOME/jre/sh/java
    else
        JAVACMD=$JAVA_HOME/bin/java
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD=java
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
    case $MAX_FD in #(
      max*)
        MAX_FD=$( ulimit -H -n ) ||
            warn "Could not query maximum file descriptor limit"
    esac
    case $MAX_FD in  #(
      '' | soft) :;; #(
      *)
        ulimit -n "$MAX_FD" ||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
    esac
fi

# Collect all arguments for the java command, stacking in reverse order:
#   * args from the command line
#   * the main class name
#   * -classpath
#   * -D...appname settings
#   * --module-path (only if needed)
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

    JAVACMD=$( cygpath --unix "$JAVACMD" )

    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    for arg do
        if
            case $arg in                                #(
              -*)   false ;;                            # don't mess with options #(
              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
                    [ -e "$t" ] ;;                      #(
              *)    false ;;
            esac
        then
            arg=$( cygpath --path --ignore --mixed "$arg" )
        fi
        # Roll the args list around exactly as many times as the number of
        # args, so each arg winds up back in the position where it started, but
        # possibly modified.
        #
        # NB: a `for` loop captures its iteration list before it begins, so
        # changing the positional parameters here affects neither the number of
        # iterations, nor the values presented in `arg`.
        shift                   # remove old arg
        set -- "$@" "$arg"      # push replacement arg
    done
fi

# Collect all arguments for the java command;
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
#     shell script including quotes and variable substitutions, so put them in
#     double quotes to make sure that they get re-expanded; and
#   * put everything else in single quotes, so that it's not re-expanded.

set -- \
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
        -classpath "$CLASSPATH" \
        org.gradle.wrapper.GradleWrapperMain \
        "$@"

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
#   set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- $(
        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
        xargs -n1 |
        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
        tr '\n' ' '
    )" '"$@"'

exec "$JAVACMD" "$@"


text:
# Create Fabric Addon Template

This template mod comes pre-configured for developing an addon mod for Create on Fabric.

## Setup

This template is an extension of the base [Fabric Example Mod](https://github.com/FabricMC/fabric-example-mod).
Setup is mostly the same.

Additionally, set `recipe_viewer` in [gradle.properties](gradle.properties). Remember to remove unused
example code. Make sure versions are up-to-date.

When you publish your mod, you should use jars provided by GitHub Actions. These jars will have
build number metadata and will be compressed by the Machete plugin.

## Features
- Access to Create and all of its dependencies
- Mojang Mappings base, with Quilt Mappings and Parchment providing Javadoc and parameters
- QuiltFlower decompiler for high quality Minecraft sources: `gradlew genSourcesWithQuiltflower`
- GitHub Actions automatic build workflow
- Machete Gradle plugin to shrink jar file sizes
- Developer QOL: Mod Menu, LazyDFU, recipe viewers

## Other Templates
- [Multi-loader template](https://github.com/Fabricators-of-Create/create-multiloader-addon-template)
- [Forge-only template](https://github.com/kotakotik22/CreateAddonTemplate)

## Help
Questions? Join us in the #devchat channel of the [Create Discord](https://discord.com/invite/hmaD7Se).

## License

This template is available under the CC0 license. Feel free to do as you wish with it.


text:
name: build
on: [ pull_request, push ]

jobs:
  build:
    strategy:
      matrix:
        java: [ 17 ]
    runs-on: ubuntu-latest
    steps:
      - name: checkout repository
        uses: actions/checkout@v2

      - name: setup jdk ${{ matrix.java }}
        uses: actions/setup-java@v1
        with:
          java-version: ${{ matrix.java }}

      - uses: actions/cache@v2
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/loom-cache
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle-wrapper.properties') }}
          restore-keys: ${{ runner.os }}-gradle

      - name: make gradle wrapper executable
        run: chmod +x ./gradlew

      - name: build
        run: ./gradlew build

      - name: capture build artifacts
        uses: actions/upload-artifact@v2
        with:
          name: Artifacts
          path: build/libs/


text:
package com.example.modid;

import com.simibubi.create.Create;

import io.github.fabricators_of_create.porting_lib.util.EnvExecutor;
import net.fabricmc.api.ModInitializer;

import net.minecraft.resources.ResourceLocation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExampleMod implements ModInitializer {
	public static final String ID = "modid";
	public static final String NAME = "Example Mod";
	public static final Logger LOGGER = LoggerFactory.getLogger(NAME);

	@Override
	public void onInitialize() {
		LOGGER.info("Create addon mod [{}] is loading alongside Create [{}]!", NAME, Create.VERSION);
		LOGGER.info(EnvExecutor.unsafeRunForDist(
				() -> () -> "{} is accessing Porting Lib from the client!",
				() -> () -> "{} is accessing Porting Lib from the server!"
		), NAME);
	}

	public static ResourceLocation id(String path) {
		return new ResourceLocation(ID, path);
	}
}


text:
package com.example.modid.mixin;

import com.example.modid.ExampleMod;

import net.minecraft.client.Minecraft;

import net.minecraft.client.main.GameConfig;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(Minecraft.class)
public class MinecraftMixin {
	@Inject(method = "<init>", at = @At("TAIL"))
	private void example$init(GameConfig gameConfig, CallbackInfo ci) {
		ExampleMod.LOGGER.info("Hello from {}", ExampleMod.NAME);
	}
}


text:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


text:
Michael Williams <mike@origamicomet.com>
Jason Kozak <jay@noctal.com>

repo_name: origamicomet/wtk

repo_language: C

mime_type: text/x-c


text:
/*===-- example.c -------------------------------------------*- mode: C -*-===*/
/*                                                                            */
/*                                     _   _                                  */
/*                               _ _ _| |_| |_                                */
/*                              | | | |  _| '_|                               */
/*                              |_____|_| |_,_|                               */
/*                                                                            */
/*       This file is distributed under the terms described in LICENSE.       */
/*                                                                            */
/*===----------------------------------------------------------------------===*/

#include <stdlib.h>

#include "wtk.h"

static wtk_uint32_t my_event_handler(wtk_handle_t window,
                                     const wtk_window_event_t *e,
                                     void *context) {

  switch (e->type) {
    case WTK_WINDOW_EVENT_DRAW: {
      wtk_canvas_rect(e->draw.canvas, {0,       0,  1280, 720}, wtk_rgb(127, 127, 127), 0);
      wtk_canvas_rect(e->draw.canvas, {0 * 128, 0,   128, 128}, wtk_rgb(255, 0, 0), 0);
      wtk_canvas_rect(e->draw.canvas, {1 * 128, 0,   128, 128}, wtk_rgb(0, 255, 0), 0);
      wtk_canvas_rect(e->draw.canvas, {2 * 128, 0,   128, 128}, wtk_rgb(0, 0, 255), 0);
      wtk_canvas_rect(e->draw.canvas, {3 * 128, 0,   128, 128}, wtk_rgb(127, 0, 0), 0);
      wtk_canvas_rect(e->draw.canvas, {4 * 128, 0,   128, 128}, wtk_rgb(0, 127, 0), 0);
      wtk_canvas_rect(e->draw.canvas, {5 * 128, 0,   128, 128}, wtk_rgb(0, 0, 127), 0);
      wtk_canvas_push(e->draw.canvas, {768 + 32, 32, 384 - 32 - 32, 64});
        wtk_canvas_rect(e->draw.canvas, {6 * 128, 0,   128, 128}, wtk_rgb(63, 0, 0), 0);
        wtk_canvas_rect(e->draw.canvas, {7 * 128, 0,   128, 128}, wtk_rgb(0, 63, 0), 0);
        wtk_canvas_push(e->draw.canvas, {1024 + 16, 16, 128 - 16 - 16, 128 - 16});
          wtk_canvas_rect(e->draw.canvas, {8 * 128, 0,   128, 128}, wtk_rgb(0, 0, 63), 0);
        wtk_canvas_pop(e->draw.canvas);
      wtk_canvas_pop(e->draw.canvas);
    } return 0;
  }

  return 0;
}

static wtk_handle_t open_a_window(void) {
  wtk_window_desc_t desc;

  desc.title = "Example";

  desc.x = 0;
  desc.y = 0;

  desc.width = 1280;
  desc.height = 720;

  desc.resizeable = 1;

  desc.closable = 0;

  desc.event_handler = &my_event_handler;
  desc.event_handler_context = NULL;

  return wtk_window_open(&desc);
}

int main(int argc, const char *argv[]) {
  (void)argc;
  (void)argv;

  wtk_app_init();

  wtk_handle_t window = open_a_window();

  while (1)
    wtk_app_pump();

  wtk_app_shutdown();

  return EXIT_SUCCESS;
}


text:
# ===-- Rybfile ------------------------------------------*- mode: Ruby -*-=== #
#
#
#                                      _   _
#                                _ _ _| |_| |_
#                               | | | |  _| '_|
#                               |_____|_| |_,_|
#
#        This file is distributed under the terms described in LICENSE.
#
# ===----------------------------------------------------------------------=== #

project :wtk, pretty: 'wtk' do |proj|
  # Dynamically linking makes some sense.
  proj.define 'WTK_LINKAGE' => 'WTK_LINKAGE_STATIC'

  # Supress all of Visual Studio's bullshit.
  # TODO(mtwilliams): Refactor into Ryb.
  proj.define '_HAS_EXCEPTIONS' => false,
              '_SCL_SECURE_NO_WARNINGS' => true,
              '_CRT_SECURE_NO_WARNINGS' => true,
              '_CRT_SECURE_NO_DEPRECATE' => true,
              '_SECURE_SCL_THROWS' => false,
              '_SILENCE_DEPRECATION_OF_SECURE_SCL_THROWS' => true,
              # See http://stackoverflow.com/questions/14363929
              '_USING_V110_SDK71_' => true

  # Suffix builds in the form: _{configuration}_{platform}_{32,64}.
   # e.g. libwtk_macosx_64.dylib or wtk_windows_32.dll
  proj.architecture :x86 do |arch| arch.suffix = '_32'; end
  proj.architecture :x86_64 do |arch| arch.suffix = '_64'; end
  proj.platform :windows do |platform| platform.suffix = '_windows'; end
  proj.platform :macosx do |platform| platform.suffix = '_macosx'; end
  proj.platform :linux do |platform| platform.suffix = '_linux'; end
  proj.configuration :debug do |config| config.suffix = '_debug'; end
  proj.configuration :release do |config| config.suffix = '_release'; end

  proj.configuration :debug, pretty: 'Debug' do |config|
    config.define 'WTK_CONFIGURATION' => 'WTK_CONFIGURATION_DEBUG'

    config.define '_DEBUG' => true,
                  '_HAS_ITERATOR_DEBUGGING' => true,
                  '_SECURE_SCL' => true

    config.generate_debug_symbols = true
    config.link_time_code_generation = false
    config.optimize = :nothing
  end

  proj.configuration :release, pretty: 'Release' do |config|
    config.define 'WTK_CONFIGURATION' => 'WTK_CONFIGURATION_RELEASE'

    config.define 'NDEBUG' => true,
                  '_HAS_ITERATOR_DEBUGGING' => false,
                  '_SECURE_SCL' => false

    config.generate_debug_symbols = true
    config.link_time_code_generation = true
    config.optimize = :speed
  end

  proj.library :wtk, pretty: 'Loom' do |lib|
    lib.linkage = :static

    lib.author = 'Origami Comet Games, Inc.'
    lib.description = 'Mixed mode user interface toolkit.'
    lib.license = 'CC0'

    lib.define '__WTK_IS_BEING_COMPILED__' => true
    lib.define '__WTK_VERSION__' => "\"\\\"#{`git rev-parse HEAD`.rstrip}\\\"\""
    lib.define '__WTK_REVISION__' => `git rev-list --count HEAD`.strip.to_i

    lib.add_include_paths 'include/'
    lib.add_library_paths '$build/lib/', '$build/bin/'
    lib.add_binary_paths '$build/bin/'

    lib.add_source_files 'include/**/*.h'

    # HACK(mtwilliams): Force compilation as C++ because Visual C doesn't do C99.
    lib.add_source_files 'src/**/*.c', language: :cpp

    lib.platform :windows do |platform|
      platform.add_external_dependencies %w(kernel32 user32 advapi32 gdi32)
    end
  end

  proj.application :example, pretty: 'Example' do |app|
    app.add_include_paths 'include/', 'test/'
    app.add_library_paths '$build/lib/', '$build/bin/'
    app.add_binary_paths '$build/bin/'

    # HACK(mtwilliams): Force compilation as C++ because Visual C doesn't do C99.
    app.add_source_files 'example.c', language: :cpp

    app.add_dependency :wtk

    app.platform :windows do |platform|
      platform.add_external_dependencies %w(kernel32 user32 advapi32 gdi32)
    end
  end
end


text:
  wtk

  :tv: A cross-platform immediate mode UI library. Public domain.

## Intro

`wtk` is an immediate mode library for building user interfaces.

## Questions

For questions, please [email me](mailto:m.t.williams@live.com). The issue tracker of this repository is exclusively for bugs, feature requests, and very occasional meta discussions.

## Contributing

Thoroughly read our [contributing guide](https://github.com/mtwilliams/wtk/blob/master/CONTRIBUTING) before opening a pull request.

## Changelog

A detailed changelog for every release will be maintained, but just gets in the way for now.

## License

Public domain with alternative stipulations made _just-in-case_ (Unlicense).


text:
/*===-- wtk/canvas.c ----------------------------------------*- mode: C -*-===*/
/*                                                                            */
/*                                     _   _                                  */
/*                               _ _ _| |_| |_                                */
/*                              | | | |  _| '_|                               */
/*                              |_____|_| |_,_|                               */
/*                                                                            */
/*       This file is distributed under the terms described in LICENSE.       */
/*                                                                            */
/*===----------------------------------------------------------------------===*/

#include "wtk/canvas.h"

/* For batches. */
#include "wtk/draw.h"

WTK_BEGIN_EXTERN_C

/* FIXME(mtwilliams): Batch ordering when parent is flushed. */
/* FIXME(mtwilliams): Batch ordering in general. */

/* PERF(mtwilliams): Tag batches with magic. Internally, magic is frame and
 * widget identifier. Hash batch after drawing. Check if hash matches previous
 * frame. If it does, discard.
 */

/* PERF(mtwilliams): Dirty area optimization. Instead of drawing everything
 * again, set the viewport to the dirty area and render anything that is
 * potentially visible, making sure to clip scissors against the viewport. We
 * can also discard whole batches, by calculating (or maintaining) a bounding
 * rectangle.
 */

/* MEM(mtwilliams): Batch overlapping. Allocate batches for best case, i.e. a
 * a lot of verticies. When flushed or finished, identify free space. If a
 * bunch is left, use free space as backing memory for a smaller batch. If a
 * smaller batch is exhausted, copy to full size block, and push previously
 * used free space into a free-list.
 */

/* TODO(mtwilliams): Debug.
 * TODO(mtwilliams): Wireframe.
 * TODO(mtwilliams): Hollow rectangles.
 * TODO(mtwilliams): Feathering. Extrude by one pixel and set alpha to zero.
 */

static wtk_batch_t *allocate_a_batch(wtk_block_t *block)
{
  /* Maximum number of verticies per batch. */
  static const wtk_uint32_t VERTICIES_PER_BATCH = 1024;

  static wtk_uint32_t id = 0;

  wtk_batch_t *batch =
    (wtk_batch_t *)wtk_block_allocate_s(block,
                                        sizeof(wtk_batch_t) + VERTICIES_PER_BATCH * sizeof(wtk_vertex_t));

  batch->next = NULL;

  /* Assign a globally unique identifier to aid debugging. */
  batch->id = ++id;

  batch->count = 0;
  batch->limit = VERTICIES_PER_BATCH;

  return batch;
}

wtk_canvas_t *wtk_canvas_create(wtk_uint32_t width,
                                wtk_uint32_t height,
                                wtk_uint32_t flags)
{
  wtk_canvas_t *canvas =
    (wtk_canvas_t *)wtk_allocate_s(sizeof(wtk_canvas_t), 16);

  canvas->width = width;
  canvas->height = height;

  canvas->batches = NULL;
  canvas->batch = NULL;
  canvas->batch_at_depth[0] = NULL;
  canvas->batch_at_depth[1] = NULL;
  canvas->batch_at_depth[2] = NULL;
  canvas->batch_at_depth[3] = NULL;
  canvas->batch_at_depth[4] = NULL;
  canvas->batch_at_depth[5] = NULL;
  canvas->batch_at_depth[6] = NULL;
  canvas->batch_at_depth[7] = NULL;

  canvas->permanent.base   = (wtk_uintptr_t)wtk_allocate_s(65535, 16);
  canvas->permanent.offset = 0;
  canvas->permanent.size   = 65535;

  /* TODO(mtwilliams): Dynamically grow and shrink transient arena. */
  canvas->transient.base   = (wtk_uintptr_t)wtk_allocate_s(1048575, 16);
  canvas->transient.offset = 0;
  canvas->transient.size   = 65535;

  canvas->drawing = 0;

  return canvas;
}

void wtk_canvas_destroy(wtk_canvas_t *canvas)
{
  wtk_free((void *)canvas->permanent.base);
  wtk_free((void *)canvas->transient.base);
  wtk_free((void *)canvas);
}

void wtk_canvas_begin(wtk_canvas_t *canvas)
{
  wtk_assert_debug(canvas->drawing == 0);

  /* Blow away allocations from previous frame. */
  wtk_block_reset(&canvas->transient);

  /*
   * Reset clipping stack.
   */

  canvas->clip[0].x = 0;
  canvas->clip[0].y = 0;
  canvas->clip[0].w = canvas->width;
  canvas->clip[0].h = canvas->height;

  canvas->depth = 0;

  /*
   * Restart batching.
   */

  wtk_batch_t *batch = allocate_a_batch(&canvas->transient);

  /* TODO(mtwilliams): Timestamp batches. */
  batch->started = batch->completed = 0;

  /* First batch spans entirety of canvas. */
  batch->clip = canvas->clip[0];

  canvas->batches           = batch;
  canvas->batch             = batch;
  canvas->batch_at_depth[0] = batch;
  canvas->batch_at_depth[1] = NULL;
  canvas->batch_at_depth[2] = NULL;
  canvas->batch_at_depth[3] = NULL;
  canvas->batch_at_depth[4] = NULL;
  canvas->batch_at_depth[5] = NULL;
  canvas->batch_at_depth[6] = NULL;
  canvas->batch_at_depth[7] = NULL;

  /* Mark start of drawing. */
  canvas->drawing = 1;
}

void wtk_canvas_end(wtk_canvas_t *canvas)
{
  /*
   * Prevent erroneous drawing outside of markers.
   */

  canvas->depth = 0;

  canvas->batch             = NULL;
  canvas->batch_at_depth[0] = NULL;
  canvas->batch_at_depth[1] = NULL;
  canvas->batch_at_depth[2] = NULL;
  canvas->batch_at_depth[3] = NULL;
  canvas->batch_at_depth[4] = NULL;
  canvas->batch_at_depth[5] = NULL;
  canvas->batch_at_depth[6] = NULL;
  canvas->batch_at_depth[7] = NULL;

  /* Mark end of drawing. */
  canvas->drawing = 0;
}

void wtk_canvas_push(wtk_canvas_t *canvas,
                     wtk_rectangle_t clip)
{
  wtk_assert_debug(canvas->depth <= 8);

  canvas->depth += 1;

  wtk_batch_t *batch = allocate_a_batch(&canvas->transient);

  /* TODO(mtwilliams): Timestamp batches. */
  batch->started = batch->completed = 0;

  /* Circumscribed by previous clipping rectangle. */
  clip.x = WTK_MAX(clip.x, canvas->clip[canvas->depth - 1].x);
  clip.y = WTK_MAX(clip.y, canvas->clip[canvas->depth - 1].y);
  clip.w = WTK_MIN(clip.w, canvas->clip[canvas->depth - 1].w);
  clip.h = WTK_MIN(clip.h, canvas->clip[canvas->depth - 1].h);

  /* New clipping rectangle. */
  canvas->clip[canvas->depth] = batch->clip = clip;

  /* Add to linked-list of batches. */
  batch->next = canvas->batch->next;
  canvas->batch->next = batch;

  /* Replace current batch. */
  canvas->batch = canvas->batch_at_depth[canvas->depth] = batch;
}

void wtk_canvas_pop(wtk_canvas_t *canvas)
{
  canvas->depth -= 1;

  /* Reinstate previous batch. */
  canvas->batch = canvas->batch_at_depth[canvas->depth];
}

static void wtk_canvas_flush(wtk_canvas_t *canvas)
{
  wtk_batch_t *batch = allocate_a_batch(&canvas->transient);

  /* TODO(mtwilliams): Timestamp batches. */
  batch->started = batch->completed = 0;

  /* Continuation of previous batch, so continue. */
  batch->clip = canvas->batch->clip;

  /* Add to linked-list of batches. Since current batch may not be at maximum
   * depth, we have to take care to link prior to children ensure correct
   * ordering.
   */
  batch->next = canvas->batch->next;
  canvas->batch->next = batch;

  /* Replace current batch. */
  canvas->batch = canvas->batch_at_depth[canvas->depth] = batch;
}

static void wtk_canvas_flush_if_nessecary(wtk_canvas_t *canvas,
                                          wtk_uint32_t verticies)
{
  if ((canvas->batch->limit - canvas->batch->count) >= verticies)
    /* Enough room. */
    return;

  wtk_canvas_flush(canvas);
}

static void wtk_canvas_vert(wtk_canvas_t *canvas,
                            wtk_uint16_t x,
                            wtk_uint16_t y,
                            wtk_color_t color)
{
  wtk_vertex_t *v = &canvas->batch->verticies[canvas->batch->count++];

  v->x = x;
  v->y = y;

  v->u = 0;
  v->v = 0;

  v->color = color.rgba;

  v->texture = 0;
}

void wtk_canvas_rect(wtk_canvas_t *canvas,
                     wtk_rectangle_t r,
                     wtk_color_t color,
                     wtk_uint32_t flags)
{
  wtk_canvas_flush_if_nessecary(canvas, 6);

  wtk_canvas_vert(canvas, r.x,       r.y,       color);
  wtk_canvas_vert(canvas, r.x + r.w, r.y,       color);
  wtk_canvas_vert(canvas, r.x,       r.y + r.h, color);
  wtk_canvas_vert(canvas, r.x,       r.y + r.h, color);
  wtk_canvas_vert(canvas, r.x + r.w, r.y,       color);
  wtk_canvas_vert(canvas, r.x + r.w, r.y + r.h, color);
}

WTK_END_EXTERN_C


text:
/*===-- wtk/window.c ----------------------------------------*- mode: C -*-===*/
/*                                                                            */
/*                                     _   _                                  */
/*                               _ _ _| |_| |_                                */
/*                              | | | |  _| '_|                               */
/*                              |_____|_| |_,_|                               */
/*                                                                            */
/*       This file is distributed under the terms described in LICENSE.       */
/*                                                                            */
/*===----------------------------------------------------------------------===*/

#include "wtk/window.h"

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  #include <windows.h>
  #include <stdio.h>
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
#endif

/* For creating associated renderable surfaces. */
#include "wtk/gl.h"

/* For rendering canvas to associated surface. */
#include "wtk/render.h"

WTK_BEGIN_EXTERN_C

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  static void wtk_register_window_class();
#endif

typedef struct wtk_window {
#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  HWND handle;
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
#endif

  wtk_uint32_t x;
  wtk_uint32_t y;
  wtk_uint32_t width;
  wtk_uint32_t height;

  unsigned minimized;
  unsigned maximized;

  unsigned resizeable;
  unsigned closable;

  /* Destroy window when closed. */
  unsigned destroy_on_close;

  /* User-specified handler for various events. */
  wtk_window_event_handler_t event_handler;
  void *event_handler_context;

  wtk_ogl_surface_t *surface;
  wtk_canvas_t *canvas;
} wtk_window_t;

wtk_window_t *handle_to_window(const wtk_handle_t handle)
{
  wtk_assert_paranoid(handle != WTK_INVALID_HANDLE);
  wtk_assert_debug((handle & WTK_HANDLE_TYPE_MASK) == WTK_WINDOW_HANDLE);
  return (wtk_window_t *)(handle & WTK_HANDLE_ID_MASK);
}

wtk_handle_t window_to_handle(wtk_window_t *window)
{
  if (window)
    return (wtk_handle_t)((wtk_handle_t)window | WTK_WINDOW_HANDLE);
  return WTK_INVALID_HANDLE;
}

wtk_ogl_surface_t *window_to_surface(wtk_handle_t window)
{
  return handle_to_window(window)->surface;
}

static wtk_uint32_t default_window_event_handler(wtk_handle_t handle,
                                                 const wtk_window_event_t *event,
                                                 void *context)
{
  (void)handle;
  (void)event;
  (void)context;

  return 0;
}

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  static void compensate_for_chrome(const DWORD styles,
                                    const DWORD ex_styles,
                                    wtk_uint32_t *width,
                                    wtk_uint32_t *height)
  {
    RECT area = { 0, 0, *width, *height };

    AdjustWindowRectEx(&area, styles, FALSE, ex_styles);

    *width  = area.right - area.left;
    *height = area.bottom - area.top;
  }
#endif

wtk_handle_t wtk_window_open(const wtk_window_desc_t *desc)
{
  wtk_window_t *window;

  wtk_assert_paranoid(desc != NULL);
  wtk_assert_paranoid(desc->title != NULL);
  wtk_assert_paranoid(desc->width > 0);
  wtk_assert_paranoid(desc->height > 0);

  window =
    (wtk_window_t *)wtk_allocate_s(sizeof(wtk_window_t), 16);

  window->x = desc->x;
  window->y = desc->y;
  window->width = desc->width;
  window->height = desc->height;

  window->resizeable = desc->resizeable;

  window->closable = desc->closable;

  /* Only destroy when explicitly closed. */
  window->destroy_on_close = 0;

  window->minimized = 0;
  window->maximized = 0;

  if (desc->event_handler) {
    window->event_handler = desc->event_handler;
    window->event_handler_context = desc->event_handler_context;
  } else {
    /* We specify a default handler rather than checking if one was specified
     * each invocation.
     */
    window->event_handler = &default_window_event_handler;
    window->event_handler_context = NULL;
  }

  /* Created asynchronously. */
  window->surface = NULL;
  window->canvas = NULL;

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  wtk_register_window_class();

  DWORD styles = WS_CAPTION
               | WS_BORDER
               | WS_CLIPCHILDREN
               | WS_CLIPSIBLINGS
               | WS_VISIBLE;

  if (window->resizeable)
    styles |= (WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX);
  else
    styles |= (WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);

  /* TODO(mtwilliams): Drag 'n' drop. */
  const DWORD ex_styles = WS_EX_APPWINDOW;

  WCHAR title[256];
  MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, &title[0], 256);

  wtk_uint32_t width = desc->width;
  wtk_uint32_t height = desc->height;

  compensate_for_chrome(styles, ex_styles, &width, &height);

  const HWND handle =
    CreateWindowExW(ex_styles,
                    L"6bdf53a8-2d8f-4372-894e-08e28a8f0ad7",
                    &title[0],
                    styles,
                    window->x, window->y, width, height,
                    NULL,
                    NULL,
                    GetModuleHandle(NULL),
                    (LPVOID)window);

  wtk_assert_release(handle != NULL);

  window->handle = handle;
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
#endif

  return window_to_handle(window);
}

void wtk_window_close(wtk_handle_t handle)
{
  wtk_window_t *window = handle_to_window(handle);

  /* Implied. */
  window->closable = 1;
  window->destroy_on_close = 1;

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  /* We destroy the window in WM_CLOSE (and always free resources associated
   * with a window in WM_NCDESTROY) so that we don't have to handle
   * programmatic window closes differently from user requests. This trades
   * some complexity on our end to make library users' lives easier.
   */
  CloseWindow(window->handle);
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
#endif
}

unsigned wtk_window_is_resizeable(wtk_handle_t handle)
{
  const wtk_window_t *window = handle_to_window(handle);
  return window->resizeable;
}

unsigned wtk_window_is_closable(wtk_handle_t handle)
{
  const wtk_window_t *window = handle_to_window(handle);
  return window->closable;
}

wtk_uintptr_t wtk_window_to_native(wtk_handle_t handle)
{
  const wtk_window_t *window = handle_to_window(handle);
#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  return (wtk_uintptr_t)window->handle;
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
#endif
}

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  static LRESULT WINAPI wtk_window_callback(HWND hWnd,
                                            UINT uMsg,
                                            WPARAM wParam,
                                            LPARAM lParam)
  {
    wtk_window_t *window = (wtk_window_t *)GetPropA(hWnd, "wtk");
    wtk_handle_t handle = window_to_handle(window);

    switch (uMsg) {
      case WM_CREATE: {
        const CREATESTRUCT *cs = (CREATESTRUCT *)lParam;

        window = (wtk_window_t *)cs->lpCreateParams;

        /* Store property so we can recover `wtk_window_t *` from handle. */
        SetPropA(hWnd, "wtk", (HANDLE)window);

        if (!window->closable) {
          EnableMenuItem(GetSystemMenu(hWnd, FALSE),
                         SC_CLOSE,
                         MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        }

        window->surface =
          wtk_ogl_create_surface((wtk_uintptr_t)hWnd);

        window->canvas =
          wtk_canvas_create(window->width, window->height, 0x00000000);

        wtk_window_event_t event;
        event.type = WTK_WINDOW_EVENT_OPENED;

        window->event_handler(handle, &event, window->event_handler_context);
      } return 0;

      case WM_CLOSE: {
        if (!window->closable) {
          return 0;
        }

        /* TODO(mtwilliams): Defer delivery? What guarantee do we make about
         * lifetime then? */
        wtk_window_event_t event;
        event.type = WTK_WINDOW_EVENT_CLOSED;
        event.closed.destroying = window->destroy_on_close;

        window->event_handler(handle, &event, window->event_handler_context);

        if (window->destroy_on_close) {
          /* See `wtk_window_close` for reasoning. */
          DestroyWindow(hWnd);
        }
      } return 0;

      case WM_NCDESTROY: {
        wtk_ogl_destroy_surface(window->surface);

        wtk_canvas_destroy(window->canvas);

        /* According to MSDN, all entires in the property list of a window must
         * be removed (via RemoveProp) before it is destroyed. In practice,
         * this doesn't make any material difference. Still, we try to be good
         * citizens.
         */
        RemovePropA(hWnd, "wtk");

        /* Finally, we clean up after ourselves. */
        wtk_free((void *)window);
      } return 0;

      case WM_MOVE: {
        window->x = LOWORD(lParam);
        window->y = HIWORD(lParam);

        wtk_window_event_t event;
        event.type = WTK_WINDOW_EVENT_MOVED;

        window->event_handler(handle, &event, window->event_handler_context);
      } return 0;

      case WM_SIZE: {
        switch (wParam) {
          case SIZE_MAXIMIZED:
            /* Maximized. */
            break;
          case SIZE_MINIMIZED:
            /* Minimized. */
            break;
        }

        window->width = LOWORD(lParam);
        window->height = HIWORD(lParam);

        wtk_ogl_resize_surface(window->surface, window->width, window->height);

        window->canvas->width = window->width;
        window->canvas->height = window->height;

        wtk_window_event_t event;
        event.type = WTK_WINDOW_EVENT_RESIZED;

        window->event_handler(handle, &event, window->event_handler_context);
      } return 0;

      case WM_ENTERSIZEMOVE: {
      } return 0;

      /* Only resize our surface once the user is done resizing. */
      case WM_EXITSIZEMOVE: {
      } return 0;

      case WM_ERASEBKGND: {
        /* Prevent flickering. */
      } return TRUE;

      case WM_PAINT: {
        PAINTSTRUCT ps;

        /* TODO(mtwilliams): Respect `ps.fErase`? */
        if (BeginPaint(hWnd, &ps)) {
          wtk_canvas_begin(window->canvas);

          wtk_window_event_t event;
          event.type = WTK_WINDOW_EVENT_DRAW;
          event.draw.canvas = window->canvas;
          event.draw.dirty.x = ps.rcPaint.left;
          event.draw.dirty.y = ps.rcPaint.top;
          event.draw.dirty.w = ps.rcPaint.right - ps.rcPaint.left;
          event.draw.dirty.h = ps.rcPaint.bottom - ps.rcPaint.top;

          window->event_handler(handle, &event, window->event_handler_context);

          wtk_canvas_end(window->canvas);

          wtk_ogl_bind(window->surface);
          wtk_renderer_render(window->canvas);
          wtk_ogl_present(window->surface, WTK_OGL_PRESENT_SYNCHRONIZE);

          EndPaint(hWnd, &ps);
        }
      } return 0;
    }

    return DefWindowProcW(hWnd, uMsg, wParam, lParam);
  }

  /* PERF(mtwilliams): Skip entire function, not just registration. */
  static void wtk_register_window_class()
  {
    WNDCLASSEXW common_window_class;
    ZeroMemory((void *)&common_window_class, sizeof(WNDCLASSEXW));

    common_window_class.cbSize        = sizeof(WNDCLASSEXW);
    common_window_class.style         = CS_VREDRAW | CS_HREDRAW | CS_OWNDC;
    common_window_class.lpfnWndProc   = &wtk_window_callback;
    common_window_class.hInstance     = GetModuleHandle(NULL);
    common_window_class.hCursor       = LoadCursor(NULL, IDC_ARROW);
    common_window_class.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    common_window_class.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
    common_window_class.lpszClassName = L"6bdf53a8-2d8f-4372-894e-08e28a8f0ad7";

    static const BOOL registered_class_succesfully =
      RegisterClassExW(&common_window_class);

    /* Should rarely, if ever fail. */
    wtk_assert_release(registered_class_succesfully);
  }
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
#endif

WTK_END_EXTERN_C


text:
/*===-- wtk/gl.c --------------------------------------------*- mode: C -*-===*/
/*                                                                            */
/*                                     _   _                                  */
/*                               _ _ _| |_| |_                                */
/*                              | | | |  _| '_|                               */
/*                              |_____|_| |_,_|                               */
/*                                                                            */
/*       This file is distributed under the terms described in LICENSE.       */
/*                                                                            */
/*===----------------------------------------------------------------------===*/

#include "wtk/gl.h"

WTK_BEGIN_EXTERN_C

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  #include "wtk/wgl.h"

  typedef wtk_wgl_context_t wtk_ogl_context_t;

  #define wtk_ogl_get_proc_address wtk_wgl_get_proc_address
#endif

GLPUSHDEBUGGROUPPROC glPushDebugGroup;
GLPOPDEBUGGROUPPROC glPopDebugGroup;

GLOBJECTLABELPROC glObjectLabel;

GLFRAMETERMINATORGREMEDYPROC glFrameTerminatorGREMEDY;

GLGETBOOLEANVPROC glGetBooleanv;
GLGETINTEGERVPROC glGetIntegerv;
GLGETFLOATVPROC glGetFloatv;
GLGETDOUBLEVPROC glGetDoublev;
GLGETSTRINGPROC glGetString;

GLENABLEPROC  glEnable;
GLDISABLEPROC glDisable;

GLPOLYGONMODEPROC glPolygonMode;

GLFRONTFACEPROC glFrontFace;
GLCULLFACEPROC glCullFace;

GLBLENDFUNCSEPARATEPROC glBlendFuncSeparate;
GLBLENDEQUATIONSEPARATEPROC glBlendEquationSeparate;
GLBLENDCOLORPROC glBlendColor;

GLCOLORMASKPROC glColorMask;

GLDEPTHFUNCPROC glDepthFunc;
GLDEPTHMASKPROC glDepthMask;

GLSTENCILOPSEPARATEPROC glStencilOpSeparate;
GLSTENCILFUNCSEPARATEPROC glStencilFuncSeparate;
GLSTENCILMASKSEPARATEPROC glStencilMaskSeparate;

GLGENBUFFERSPROC glGenBuffers;
GLGENTEXTURESPROC glGenTextures;
GLGENFRAMEBUFFERSPROC glGenFramebuffers;
GLCREATESHADERPROC glCreateShader;
GLCREATEPROGRAMPROC glCreateProgram;

GLDELETEBUFFERSPROC glDeleteBuffers;
GLDELETETEXTURESPROC glDeleteTextures;
GLDELETESHADERPROC glDeleteShader;
GLDELETEPROGRAMPROC glDeleteProgram;
GLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;

GLVIEWPORTPROC glViewport;
GLSCISSORPROC  glScissor;

GLBINDBUFFERPROC glBindBuffer;

GLBUFFERDATAPROC glBufferData;
GLBUFFERSUBDATAPROC glBufferSubData;

GLMAPBUFFERPROC glMapBuffer;

GLUNMAPBUFFERPROC glUnmapBuffer;

GLACTIVETEXTUREPROC glActiveTexture;

GLBINDTEXTUREPROC glBindTexture;

GLTEXIMAGE1DPROC glTexImage1D;
GLTEXIMAGE2DPROC glTexImage2D;
GLTEXIMAGE3DPROC glTexImage3D;

GLTEXPARAMETERFPROC glTexParameterf;
GLTEXPARAMETERIPROC glTexParameteri;

GLGENERATEMIPMAPPROC glGenerateMipmap;

GLSHADERSOURCEPROC glShaderSource;
GLCOMPILESHADERPROC glCompileShader;

GLGETSHADERIVPROC glGetShaderiv;
GLGETSHADERINFOLOGPROC glGetShaderInfoLog;

GLATTACHSHADERPROC glAttachShader;
GLDETACHSHADERPROC glDetachShader;
GLLINKPROGRAMPROC glLinkProgram;

GLGETPROGRAMIVPROC glGetProgramiv;
GLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;

GLGETUNIFORMLOCATIONPROC glGetUniformLocation;

GLUNIFORM1FPROC glUniform1f;
GLUNIFORM2FPROC glUniform2f;
GLUNIFORM3FPROC glUniform3f;
GLUNIFORM4FPROC glUniform4f;

GLUNIFORM1IPROC glUniform1i;
GLUNIFORM2IPROC glUniform2i;
GLUNIFORM3IPROC glUniform3i;
GLUNIFORM4IPROC glUniform4i;

GLUNIFORM1UIPROC glUniform1ui;
GLUNIFORM2UIPROC glUniform2ui;
GLUNIFORM3UIPROC glUniform3ui;
GLUNIFORM4UIPROC glUniform4ui;

GLUSEPROGRAMPROC glUseProgram;

GLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
GLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;

GLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
GLVERTEXATTRIBIPOINTERPROC glVertexAttribIPointer;

GLBINDFRAMEBUFFERPROC glBindFramebuffer;

GLFRAMEBUFFERTEXTURE1DPROC glFramebufferTexture1D;
GLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
GLFRAMEBUFFERTEXTURE3DPROC glFramebufferTexture3D;

GLDRAWBUFFERPROC glDrawBuffer;
GLDRAWBUFFERSPROC glDrawBuffers;

GLCLEARCOLORPROC glClearColor;
GLCLEARDEPTHPROC glClearDepth;
GLCLEARSTENCILPROC glClearStencil;

GLCLEARPROC glClear;

typedef void (GL_ENTRY_POINT *GLGENVERTEXARRAYSPROC)(wtk_uint32_t n, wtk_uint32_t *arrays);
typedef void (GL_ENTRY_POINT *GLDELETEVERTEXARRAYSPROC)(wtk_uint32_t n, const wtk_uint32_t *arrays);
typedef void (GL_ENTRY_POINT *GLBINDVERTEXARRAYPROC)(wtk_uint32_t array);

GLGENVERTEXARRAYSPROC glGenVertexArrays;
GLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
GLBINDVERTEXARRAYPROC glBindVertexArray;

GLDRAWARRAYSPROC glDrawArrays;
GLDRAWELEMENTSPROC glDrawElements;

GLBLITFRAMEBUFFERPROC glBlitFramebuffer;

GLFLUSHPROC  glFlush;
GLFINISHPROC glFinish;

wtk_uint32_t GL_KHR_debug = 0;
wtk_uint32_t GL_GREMEDY_frame_terminator = 0;

static void wtk_ogl_wrangle(void)
{
  static wtk_uint32_t wrangled = 0;

  if (wrangled)
    return;

  wrangled = 1;

  glGetBooleanv = (GLGETBOOLEANVPROC)wtk_ogl_get_proc_address("glGetBooleanv");
  glGetIntegerv = (GLGETINTEGERVPROC)wtk_ogl_get_proc_address("glGetIntegerv");
  glGetFloatv = (GLGETFLOATVPROC)wtk_ogl_get_proc_address("glGetFloatv");
  glGetDoublev = (GLGETDOUBLEVPROC)wtk_ogl_get_proc_address("glGetDoublev");
  glGetString = (GLGETSTRINGPROC)wtk_ogl_get_proc_address("glGetString");

  glEnable = (GLENABLEPROC)wtk_ogl_get_proc_address("glEnable");
  glDisable = (GLDISABLEPROC)wtk_ogl_get_proc_address("glDisable");

  glPolygonMode = (GLPOLYGONMODEPROC)wtk_ogl_get_proc_address("glPolygonMode");

  glFrontFace = (GLFRONTFACEPROC)wtk_ogl_get_proc_address("glFrontFace");
  glCullFace = (GLCULLFACEPROC)wtk_ogl_get_proc_address("glCullFace");

  glBlendFuncSeparate = (GLBLENDFUNCSEPARATEPROC)wtk_ogl_get_proc_address("glBlendFuncSeparate");
  glBlendEquationSeparate = (GLBLENDEQUATIONSEPARATEPROC)wtk_ogl_get_proc_address("glBlendEquationSeparate");
  glBlendColor = (GLBLENDCOLORPROC)wtk_ogl_get_proc_address("glBlendColor");

  glColorMask = (GLCOLORMASKPROC)wtk_ogl_get_proc_address("glColorMask");

  glDepthFunc = (GLDEPTHFUNCPROC)wtk_ogl_get_proc_address("glDepthFunc");
  glDepthMask = (GLDEPTHMASKPROC)wtk_ogl_get_proc_address("glDepthMask");

  glStencilOpSeparate = (GLSTENCILOPSEPARATEPROC)wtk_ogl_get_proc_address("glStencilOpSeparate");
  glStencilFuncSeparate = (GLSTENCILFUNCSEPARATEPROC)wtk_ogl_get_proc_address("glStencilFuncSeparate");
  glStencilMaskSeparate = (GLSTENCILMASKSEPARATEPROC)wtk_ogl_get_proc_address("glStencilMaskSeparate");

  glGenBuffers = (GLGENBUFFERSPROC)wtk_ogl_get_proc_address("glGenBuffers");
  glGenTextures = (GLGENTEXTURESPROC)wtk_ogl_get_proc_address("glGenTextures");
  glCreateShader = (GLCREATESHADERPROC)wtk_ogl_get_proc_address("glCreateShader");
  glCreateProgram = (GLCREATEPROGRAMPROC)wtk_ogl_get_proc_address("glCreateProgram");
  glGenFramebuffers = (GLGENFRAMEBUFFERSPROC)wtk_ogl_get_proc_address("glGenFramebuffers");

  glDeleteBuffers = (GLDELETEBUFFERSPROC)wtk_ogl_get_proc_address("glDeleteBuffers");
  glDeleteTextures = (GLDELETETEXTURESPROC)wtk_ogl_get_proc_address("glDeleteTextures");
  glDeleteShader = (GLDELETESHADERPROC)wtk_ogl_get_proc_address("glDeleteShader");
  glDeleteProgram = (GLDELETEPROGRAMPROC)wtk_ogl_get_proc_address("glDeleteProgram");
  glDeleteFramebuffers = (GLDELETEFRAMEBUFFERSPROC)wtk_ogl_get_proc_address("glDeleteFramebuffers");

  glViewport = (GLVIEWPORTPROC)wtk_ogl_get_proc_address("glViewport");
  glScissor = (GLSCISSORPROC)wtk_ogl_get_proc_address("glScissor");

  glBindBuffer = (GLBINDBUFFERPROC)wtk_ogl_get_proc_address("glBindBuffer");

  glBufferData = (GLBUFFERDATAPROC)wtk_ogl_get_proc_address("glBufferData");
  glBufferSubData = (GLBUFFERSUBDATAPROC)wtk_ogl_get_proc_address("glBufferSubData");

  glMapBuffer = (GLMAPBUFFERPROC)wtk_ogl_get_proc_address("glMapBuffer");
  glUnmapBuffer = (GLUNMAPBUFFERPROC)wtk_ogl_get_proc_address("glUnmapBuffer");

  glActiveTexture = (GLACTIVETEXTUREPROC)wtk_ogl_get_proc_address("glActiveTexture");

  glBindTexture = (GLBINDTEXTUREPROC)wtk_ogl_get_proc_address("glBindTexture");

  glTexImage1D = (GLTEXIMAGE1DPROC)wtk_ogl_get_proc_address("glTexImage1D");
  glTexImage2D = (GLTEXIMAGE2DPROC)wtk_ogl_get_proc_address("glTexImage2D");
  glTexImage3D = (GLTEXIMAGE3DPROC)wtk_ogl_get_proc_address("glTexImage3D");

  glTexParameterf = (GLTEXPARAMETERFPROC)wtk_ogl_get_proc_address("glTexParameterf");
  glTexParameteri = (GLTEXPARAMETERIPROC)wtk_ogl_get_proc_address("glTexParameteri");

  glGenerateMipmap = (GLGENERATEMIPMAPPROC)wtk_ogl_get_proc_address("glGenerateMipmap");

  glShaderSource = (GLSHADERSOURCEPROC)wtk_ogl_get_proc_address("glShaderSource");
  glCompileShader = (GLCOMPILESHADERPROC)wtk_ogl_get_proc_address("glCompileShader");

  glGetShaderiv = (GLGETSHADERIVPROC)wtk_ogl_get_proc_address("glGetShaderiv");
  glGetShaderInfoLog = (GLGETSHADERINFOLOGPROC)wtk_ogl_get_proc_address("glGetShaderInfoLog");

  glAttachShader = (GLATTACHSHADERPROC)wtk_ogl_get_proc_address("glAttachShader");
  glDetachShader = (GLDETACHSHADERPROC)wtk_ogl_get_proc_address("glDetachShader");
  glLinkProgram = (GLLINKPROGRAMPROC)wtk_ogl_get_proc_address("glLinkProgram");

  glGetProgramiv = (GLGETPROGRAMIVPROC)wtk_ogl_get_proc_address("glGetProgramiv");
  glGetProgramInfoLog = (GLGETPROGRAMINFOLOGPROC)wtk_ogl_get_proc_address("glGetProgramInfoLog");

  glGetUniformLocation = (GLGETUNIFORMLOCATIONPROC)wtk_ogl_get_proc_address("glGetUniformLocation");

  glUniform1f = (GLUNIFORM1FPROC)wtk_ogl_get_proc_address("glUniform1f");
  glUniform2f = (GLUNIFORM2FPROC)wtk_ogl_get_proc_address("glUniform2f");
  glUniform3f = (GLUNIFORM3FPROC)wtk_ogl_get_proc_address("glUniform3f");
  glUniform4f = (GLUNIFORM4FPROC)wtk_ogl_get_proc_address("glUniform4f");

  glUniform1i = (GLUNIFORM1IPROC)wtk_ogl_get_proc_address("glUniform1i");
  glUniform2i = (GLUNIFORM2IPROC)wtk_ogl_get_proc_address("glUniform2i");
  glUniform3i = (GLUNIFORM3IPROC)wtk_ogl_get_proc_address("glUniform3i");
  glUniform4i = (GLUNIFORM4IPROC)wtk_ogl_get_proc_address("glUniform4i");

  glUniform1ui = (GLUNIFORM1UIPROC)wtk_ogl_get_proc_address("glUniform1ui");
  glUniform2ui = (GLUNIFORM2UIPROC)wtk_ogl_get_proc_address("glUniform2ui");
  glUniform3ui = (GLUNIFORM3UIPROC)wtk_ogl_get_proc_address("glUniform3ui");
  glUniform4ui = (GLUNIFORM4UIPROC)wtk_ogl_get_proc_address("glUniform4ui");

  glUseProgram = (GLUSEPROGRAMPROC)wtk_ogl_get_proc_address("glUseProgram");

  glEnableVertexAttribArray = (GLENABLEVERTEXATTRIBARRAYPROC)wtk_ogl_get_proc_address("glEnableVertexAttribArray");
  glDisableVertexAttribArray = (GLDISABLEVERTEXATTRIBARRAYPROC)wtk_ogl_get_proc_address("glDisableVertexAttribArray");

  glVertexAttribPointer = (GLVERTEXATTRIBPOINTERPROC)wtk_ogl_get_proc_address("glVertexAttribPointer");
  glVertexAttribIPointer = (GLVERTEXATTRIBIPOINTERPROC)wtk_ogl_get_proc_address("glVertexAttribIPointer");

  glBindFramebuffer = (GLBINDFRAMEBUFFERPROC)wtk_ogl_get_proc_address("glBindFramebuffer");

  glFramebufferTexture1D = (GLFRAMEBUFFERTEXTURE1DPROC)wtk_ogl_get_proc_address("glFramebufferTexture1D");
  glFramebufferTexture2D = (GLFRAMEBUFFERTEXTURE2DPROC)wtk_ogl_get_proc_address("glFramebufferTexture2D");
  glFramebufferTexture3D = (GLFRAMEBUFFERTEXTURE3DPROC)wtk_ogl_get_proc_address("glFramebufferTexture3D");

  glDrawBuffer = (GLDRAWBUFFERPROC)wtk_ogl_get_proc_address("glDrawBuffer");
  glDrawBuffers = (GLDRAWBUFFERSPROC)wtk_ogl_get_proc_address("glDrawBuffers");

  glClearColor = (GLCLEARCOLORPROC)wtk_ogl_get_proc_address("glClearColor");
  glClearDepth = (GLCLEARDEPTHPROC)wtk_ogl_get_proc_address("glClearDepth");
  glClearStencil = (GLCLEARSTENCILPROC)wtk_ogl_get_proc_address("glClearStencil");

  glClear = (GLCLEARPROC)wtk_ogl_get_proc_address("glClear");

  glGenVertexArrays = (GLGENVERTEXARRAYSPROC)wtk_ogl_get_proc_address("glGenVertexArrays");
  glDeleteVertexArrays = (GLDELETEVERTEXARRAYSPROC)wtk_ogl_get_proc_address("glDeleteVertexArrays");
  glBindVertexArray = (GLBINDVERTEXARRAYPROC)wtk_ogl_get_proc_address("glBindVertexArray");

  glDrawArrays = (GLDRAWARRAYSPROC)wtk_ogl_get_proc_address("glDrawArrays");
  glDrawElements = (GLDRAWELEMENTSPROC)wtk_ogl_get_proc_address("glDrawElements");

  glBlitFramebuffer = (GLBLITFRAMEBUFFERPROC)wtk_ogl_get_proc_address("glBlitFramebuffer");

  glFlush = (GLFLUSHPROC)wtk_ogl_get_proc_address("glFlush");
  glFinish = (GLFINISHPROC)wtk_ogl_get_proc_address("glFinish");

  /* TODO(mtwilliams): Check for extensions. */
#if 0
  wtk_ogl_check_for_extensions();
#endif

  if (GL_KHR_debug) {
    glPushDebugGroup = (GLPUSHDEBUGGROUPPROC)wtk_ogl_get_proc_address("glPushDebugGroup");
    glPopDebugGroup  = (GLPOPDEBUGGROUPPROC)wtk_ogl_get_proc_address("glPopDebugGroup");
    glObjectLabel    = (GLOBJECTLABELPROC)wtk_ogl_get_proc_address("glObjectLabel");
  } else {
    glPushDebugGroup = NULL;
    glPopDebugGroup  = NULL;
    glObjectLabel    = NULL;
  }

  if (GL_GREMEDY_frame_terminator) {
    glFrameTerminatorGREMEDY = (GLFRAMETERMINATORGREMEDYPROC)wtk_ogl_get_proc_address("glFrameTerminatorGREMEDY");
  } else {
    glFrameTerminatorGREMEDY = NULL;
  }
}

/* We share a context rather than creating one per surface. */
static wtk_ogl_context_t *context = NULL;

void wtk_ogl_init(void)
{
#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  wtk_wgl_init();

  wtk_wgl_context_creation_params_t context_creation_params; {
    context_creation_params.version.major = 3;

  #if WTK_CONFIGURATION == WTK_CONFIGURATION_DEBUG
    /* Tools, like RenderDoc, require 3.2+. */
    context_creation_params.version.minor = 2;
  #else
    context_creation_params.version.minor = 1;
  #endif

    context_creation_params.flags = 0x00000000;

  #if WTK_CONFIGURATION == WTK_CONFIGURATION_DEBUG
    context_creation_params.flags |= WTK_WGL_CONTEXT_DEBUG;
  #endif

    context_creation_params.share = NULL;
  }

  context = wtk_wgl_create_context(&context_creation_params);

  wtk_wgl_bind(context, NULL);
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
  wtk_agl_init();
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
  wtk_glx_init();
#endif

  wtk_ogl_wrangle();

  /* We draw to the backbuffer for the currently bound surface unless otherwise
   * specified. Don't change this without understanding how presentation works.
   * See `wtk_ogl_present` for details.
   */
  glDrawBuffer(GL_BACK);

  /* HACK(mtwilliams): OpenGL 3.2 onwards no longer provides an implicit vertex
   * array object, but requires a vertex array object for vertex specification,
   * so we mimic that behavior by generating our own implict vertex array
   * object.
   */
  wtk_uint32_t vao;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  /* Insert a frame terminator to prevent "junk" from showing up in the first frame. */
  if (GL_GREMEDY_frame_terminator)
    glFrameTerminatorGREMEDY();
}

void wtk_ogl_shutdown(void)
{
  /* NOTE(mtwilliams): Unbind context to prevent crashes on some drivers by
   * making sure no context is current prior to its deletion.
   */
#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  wtk_wgl_bind(NULL, NULL);
  wtk_wgl_destroy_context(context);
  wtk_wgl_shutdown();
#elif WTK_PLATFORM == WTK_PLATFORM_MAC
  wtk_agl_bind(NULL, NULL);
  wtk_agl_shutdown();
#elif WTK_PLATFORM == WTK_PLATFORM_LINUX
  wtk_glx_bind(NULL, NULL);
  wtk_glx_shutdown();
#endif
}

struct wtk_ogl_surface {
  wtk_uint32_t width;
  wtk_uint32_t height;

  /* We actually render to an texture and copy its contents to the surface
   * prior to presentation.
   */
  wtk_uint32_t fbo;
  wtk_uint32_t offscreen;

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  wtk_wgl_surface_t *underlying;
#endif
};

wtk_ogl_surface_t *wtk_ogl_create_surface(wtk_uintptr_t window)
{
  wtk_ogl_surface_t *surface =
    (wtk_ogl_surface_t *)wtk_allocate_s(sizeof(wtk_ogl_surface_t), 16);

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  surface->underlying = wtk_wgl_create_surface(context, (HWND)window);

  wtk_wgl_dimensions_of_surface(surface->underlying,
                                &surface->width,
                                &surface->height);
#endif

  glGenTextures(1, &surface->offscreen);
  glBindTexture(GL_TEXTURE_2D, surface->offscreen);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, surface->width, surface->height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
  glBindTexture(GL_TEXTURE_2D, 0);

  glGenFramebuffers(1, &surface->fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, surface->fbo);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT + 0, GL_TEXTURE_2D, surface->offscreen, 0);
  glBindFramebuffer(GL_FRAMEBUFFER, 0);

  return surface;
}

void wtk_ogl_destroy_surface(wtk_ogl_surface_t *surface) {
  glDeleteFramebuffers(1, &surface->fbo);
  glDeleteTextures(1, &surface->offscreen);

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  wtk_wgl_destroy_surface(surface->underlying);
#endif

  wtk_free((void *)surface);
}

void wtk_ogl_resize_surface(wtk_ogl_surface_t *surface,
                            wtk_uint32_t width,
                            wtk_uint32_t height)
{
  surface->width = width;
  surface->height = height;

  /* PERF(mtwilliams): Allegedly slow on some drivers. Should recreate the
   * framebuffer object?
   */
  glBindTexture(GL_TEXTURE_2D, surface->offscreen);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
}

void wtk_ogl_dimensions_of_surface(const wtk_ogl_surface_t *surface,
                                   wtk_uint32_t *width,
                                   wtk_uint32_t *height)
{
  *width = surface->width;
  *height = surface->height;
}

void wtk_ogl_bind(wtk_ogl_surface_t *surface)
{
  glBindFramebuffer(GL_FRAMEBUFFER, surface ? surface->fbo : 0);
}

void wtk_ogl_present(wtk_ogl_surface_t *surface,
                     wtk_uint32_t flags)
{
  /* TODO(mtwilliams): Resolve swap chain if multisampling. */

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  wtk_uint32_t width, height;
  wtk_wgl_dimensions_of_surface(surface->underlying, &width, &height);

  /* Bind to appropriate surface. */
  wtk_wgl_bind(context, surface->underlying);
#endif

  glViewport(0, 0, width, height);
  glScissor(0, 0, width, height);

  /* Resolve and scale into backbuffer. */
  glBindFramebuffer(GL_READ_FRAMEBUFFER, surface->fbo);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
  glBlitFramebuffer(0, 0, surface->width, surface->height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);

#if WTK_PLATFORM == WTK_PLATFORM_WINDOWS
  /* Swap. */
  wtk_wgl_present(surface->underlying, (flags & WTK_OGL_PRESENT_SYNCHRONIZE) ? WTK_WGL_PRESENT_SYNCHRONIZE : 0);

  /* Unbind. */
  wtk_wgl_bind(context, NULL);
#endif
}

WTK_END_EXTERN_C