param(
  [ValidateSet('state','playpause','previous','next','volumeup','volumedown','mute')]
  [string]$Command = 'state'
)

$ErrorActionPreference = 'Stop'

function ConvertTo-JsonLine($Value) {
  $Value | ConvertTo-Json -Compress -Depth 8
}

function Enable-WinRt {
  Add-Type -AssemblyName System.Runtime.WindowsRuntime -ErrorAction SilentlyContinue
  [Windows.Foundation.IAsyncOperation`1, Windows.Foundation, ContentType = WindowsRuntime] | Out-Null
  [Windows.Foundation.IAsyncAction, Windows.Foundation, ContentType = WindowsRuntime] | Out-Null
  [Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager, Windows.Media.Control, ContentType = WindowsRuntime] | Out-Null
  [Windows.Media.Control.GlobalSystemMediaTransportControlsSessionMediaProperties, Windows.Media.Control, ContentType = WindowsRuntime] | Out-Null
  [Windows.Storage.Streams.IRandomAccessStreamWithContentType, Windows.Storage.Streams, ContentType = WindowsRuntime] | Out-Null
  [Windows.Storage.Streams.DataReader, Windows.Storage.Streams, ContentType = WindowsRuntime] | Out-Null
}

function Await-Operation($Operation, [Type]$ResultType) {
  $method = [System.WindowsRuntimeSystemExtensions].GetMethods() |
    Where-Object {
      $_.Name -eq 'AsTask' -and
      $_.IsGenericMethodDefinition -and
      $_.GetParameters().Count -eq 1 -and
      $_.GetGenericArguments().Count -eq 1
    } |
    Select-Object -First 1
  $task = $method.MakeGenericMethod($ResultType).Invoke($null, @($Operation))
  return $task.Result
}

function Await-Action($Operation) {
  $method = [System.WindowsRuntimeSystemExtensions].GetMethods() |
    Where-Object {
      $_.Name -eq 'AsTask' -and
      -not $_.IsGenericMethodDefinition -and
      $_.GetParameters().Count -eq 1
    } |
    Select-Object -First 1
  $task = $method.Invoke($null, @($Operation))
  $task.Wait()
}

function Get-Session {
  Enable-WinRt
  $manager = Await-Operation ([Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager]::RequestAsync()) ([Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager])
  if ($null -eq $manager) { return $null }
  return $manager.GetCurrentSession()
}

function Get-ThumbnailDataUrl($Thumbnail) {
  if ($null -eq $Thumbnail) { return '' }
  try {
    $stream = Await-Operation ($Thumbnail.OpenReadAsync()) ([Windows.Storage.Streams.IRandomAccessStreamWithContentType])
    if ($null -eq $stream) { return '' }
    $size = [int]$stream.Size
    if ($size -le 0 -or $size -gt 2000000) { return '' }
    $reader = [Windows.Storage.Streams.DataReader]::new($stream.GetInputStreamAt(0))
    Await-Operation ($reader.LoadAsync([uint32]$size)) ([UInt32]) | Out-Null
    $bytes = New-Object byte[] $size
    $reader.ReadBytes($bytes)
    $reader.Dispose()
    $mime = $stream.ContentType
    if ([string]::IsNullOrWhiteSpace($mime)) { $mime = 'image/png' }
    return ('data:{0};base64,{1}' -f $mime, [Convert]::ToBase64String($bytes))
  } catch {
    return ''
  }
}

function Send-MediaKey($Name) {
  Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class BraventMusicGMediaKey {
  [DllImport("user32.dll", SetLastError = true)]
  public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
}
"@
  $map = @{
    playpause = 0xB3
    next = 0xB0
    previous = 0xB1
    volumeup = 0xAF
    volumedown = 0xAE
    mute = 0xAD
  }
  $keyUp = 0x0002
  $vk = [byte]$map[$Name]
  [BraventMusicGMediaKey]::keybd_event($vk, 0, 0, [UIntPtr]::Zero)
  Start-Sleep -Milliseconds 50
  [BraventMusicGMediaKey]::keybd_event($vk, 0, $keyUp, [UIntPtr]::Zero)
}

function Invoke-Transport($Session, $Name) {
  if ($null -eq $Session) {
    Send-MediaKey $Name
    return $false
  }
  try {
    if ($Name -eq 'playpause') {
      $status = $Session.GetPlaybackInfo().PlaybackStatus.ToString()
      if ($status -eq 'Playing') {
        Await-Operation ($Session.TryPauseAsync()) ([Boolean]) | Out-Null
      } else {
        Await-Operation ($Session.TryPlayAsync()) ([Boolean]) | Out-Null
      }
      return $true
    }
    if ($Name -eq 'previous') {
      Await-Operation ($Session.TrySkipPreviousAsync()) ([Boolean]) | Out-Null
      return $true
    }
    if ($Name -eq 'next') {
      Await-Operation ($Session.TrySkipNextAsync()) ([Boolean]) | Out-Null
      return $true
    }
  } catch {
    Send-MediaKey $Name
    return $false
  }
  Send-MediaKey $Name
  return $false
}

try {
  if ($Command -eq 'state') {
    $session = Get-Session
    if ($null -eq $session) {
      ConvertTo-JsonLine ([pscustomobject]@{
        ok = $true
        session = $false
        title = 'Bravent Music - G'
        artist = 'No active media session'
        album = ''
        playbackStatus = 'NoSession'
        positionMs = 0
        endMs = 180000
        thumbnailDataUrl = ''
      })
      exit 0
    }
    $props = Await-Operation ($session.TryGetMediaPropertiesAsync()) ([Windows.Media.Control.GlobalSystemMediaTransportControlsSessionMediaProperties])
    $playback = $session.GetPlaybackInfo()
    $timeline = $session.GetTimelineProperties()
    ConvertTo-JsonLine ([pscustomobject]@{
      ok = $true
      session = $true
      title = [string]$props.Title
      artist = [string]$props.Artist
      album = [string]$props.AlbumTitle
      playbackStatus = [string]$playback.PlaybackStatus
      positionMs = [int][Math]::Max(0, [Math]::Round($timeline.Position.TotalMilliseconds))
      endMs = [int][Math]::Max(1, [Math]::Round($timeline.EndTime.TotalMilliseconds))
      thumbnailDataUrl = Get-ThumbnailDataUrl $props.Thumbnail
    })
    exit 0
  }

  $session = $null
  try { $session = Get-Session } catch { $session = $null }
  $usedSession = $false
  if ($Command -in @('playpause','previous','next')) {
    $usedSession = Invoke-Transport $session $Command
  } else {
    Send-MediaKey $Command
  }
  ConvertTo-JsonLine ([pscustomobject]@{
    ok = $true
    command = $Command
    usedSession = $usedSession
  })
} catch {
  ConvertTo-JsonLine ([pscustomobject]@{
    ok = $false
    command = $Command
    error = $_.Exception.Message
  })
  exit 1
}
