# Regenerates dodossh.ico from the same geometry the Android launcher icon draws.
#
# The phone's mark is a vector — Resources/drawable/ic_launcher_foreground.xml — and the whole
# reason it is a vector is that there is then one geometry to change and no set of PNG densities
# to forget one of. Windows will not take a vector: <ApplicationIcon> wants an .ico and nothing
# else, and Window.Icon wants a bitmap. So the raster exists, and this script is how it stays
# honest: the numbers below are the ones in that XML, and regenerating is the whole edit.
#
#     pwsh -File src/DodoSSH.Client.App/Assets/dodossh-icon.ps1
#
# Coordinates are the launcher's 108-unit viewport, mapped so the middle 72 fills the canvas.
# That 72 is not an arbitrary crop: it is the part of an adaptive icon a launcher actually shows,
# the outer 18 on each edge being what it eats for masking and parallax. Rendering the whole 108
# here would draw the glyph at half the size it is meant to be — correct arithmetic, and a stamp
# lost in a field of accent. Matching what the phone displays means matching the 72.
#
# The one thing this draws that the phone's vector does not is the tile. On Android the tile is
# the background layer and the launcher's mask rounds it; Windows has no mask, so the rounding
# happens here.

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing

$accent = [System.Drawing.ColorTranslator]::FromHtml('#5B8CFF')  # dodo_accent / AccentColor
$ink = [System.Drawing.ColorTranslator]::FromHtml('#0E1220')     # AccentInk

# Every size Windows asks for: 16 in a titlebar and a tree, 32 on the desktop, 48 in a large-icon
# view, 256 for the preview pane. Shipping fewer means Windows downsamples one of these to fill
# the gap, and its downsampler is not kind to a hairline.
$sizes = @(16, 20, 24, 32, 40, 48, 64, 128, 256)

function New-MarkPng([int]$size)
{
    $bitmap = New-Object System.Drawing.Bitmap($size, $size, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
    $g = [System.Drawing.Graphics]::FromImage($bitmap)
    $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
    $g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality

    # The accent tile, rounded as a launcher mask rounds it. A square-cornered tile would be the
    # one icon on the taskbar with corners, which reads as unfinished rather than as deliberate.
    $radius = [double]$size * 0.22
    $d = $radius * 2.0
    $path = New-Object System.Drawing.Drawing2D.GraphicsPath
    $path.AddArc(0.0, 0.0, $d, $d, 180, 90)
    $path.AddArc($size - $d, 0.0, $d, $d, 270, 90)
    $path.AddArc($size - $d, $size - $d, $d, $d, 0, 90)
    $path.AddArc(0.0, $size - $d, $d, $d, 90, 90)
    $path.CloseFigure()
    $brush = New-Object System.Drawing.SolidBrush($accent)
    $g.FillPath($brush, $path)

    # 108-viewport units to pixels, with the outer 18 dropped on each edge.
    $scale = [double]$size / 72.0
    function P([double]$x, [double]$y) { New-Object System.Drawing.PointF((($x - 18.0) * $scale), (($y - 18.0) * $scale)) }

    # A stroke thinner than a pixel renders as a grey suggestion of itself, which at 16px is the
    # difference between a mark and a smudge. The phone's file already bumps this width for the
    # same reason at 48dp; the floor is that argument carried down to the sizes Windows asks for.
    $pen = New-Object System.Drawing.Pen($ink, [float][Math]::Max(5.4 * $scale, 1.0))
    $pen.StartCap = [System.Drawing.Drawing2D.LineCap]::Round
    $pen.EndCap = [System.Drawing.Drawing2D.LineCap]::Round
    $pen.LineJoin = [System.Drawing.Drawing2D.LineJoin]::Round

    # The chevron of >_ and the underscore on the baseline it bottoms out at.
    $g.DrawLines($pen, @((P 36.06 43.33), (P 49.91 53.57), (P 36.06 63.8)))
    $g.DrawLine($pen, (P 54 64.68), (P 71.94 64.68))

    $stream = New-Object System.IO.MemoryStream
    $bitmap.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)

    $pen.Dispose(); $brush.Dispose(); $path.Dispose(); $g.Dispose(); $bitmap.Dispose()
    return $stream.ToArray()
}

# ICO is a six-byte header, a sixteen-byte directory entry per image, then the images. The entries
# carry PNG payloads rather than the older BMP-with-AND-mask form, which every Windows since Vista
# reads and which is what keeps a 256px entry from costing 256KB.
#
# The cast on each frame is load-bearing. A byte[] returned through PowerShell's output collector
# comes back as Object[] of boxed bytes, which BinaryWriter has no overload for — it binds to one
# of the scalar Write()s instead and puts a single byte on the stream. The first run of this
# script produced a 159-byte .ico that way, header and directory intact and nine one-byte images.
$frames = New-Object 'System.Collections.Generic.List[byte[]]'
foreach ($size in $sizes)
{
    [byte[]]$png = New-MarkPng $size
    $frames.Add($png)
}

$out = New-Object System.IO.MemoryStream
$w = New-Object System.IO.BinaryWriter($out)
$w.Write([uint16]0)             # reserved
$w.Write([uint16]1)             # type: icon
$w.Write([uint16]$sizes.Count)

$offset = 6 + (16 * $sizes.Count)
for ($i = 0; $i -lt $sizes.Count; $i++)
{
    $size = $sizes[$i]
    $w.Write([byte]($(if ($size -ge 256) { 0 } else { $size })))  # 0 means 256
    $w.Write([byte]($(if ($size -ge 256) { 0 } else { $size })))
    $w.Write([byte]0)           # palette entries: none, this is truecolour
    $w.Write([byte]0)           # reserved
    $w.Write([uint16]1)         # colour planes
    $w.Write([uint16]32)        # bits per pixel
    $w.Write([uint32]$frames[$i].Length)
    $w.Write([uint32]$offset)
    $offset += $frames[$i].Length
}

foreach ($frame in $frames) { $w.Write($frame) }
$w.Flush()

$target = Join-Path $PSScriptRoot 'dodossh.ico'
[System.IO.File]::WriteAllBytes($target, $out.ToArray())
$w.Dispose(); $out.Dispose()

Write-Output "Wrote $target ($($sizes.Count) sizes, $((Get-Item $target).Length) bytes)"
