$fallback7z = Join-Path (Get-Location) "\7z\7zr.exe";
$useragent = "mpv-win-updater"

function Get-7z {
    $7z_command = Get-Command -CommandType Application -ErrorAction Ignore 7z.exe | Select-Object -Last 1
    if ($7z_command) {
        return $7z_command.Source
    }
    $7zdir = Get-ItemPropertyValue -ErrorAction Ignore "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\7-Zip" "InstallLocation"
    if ($7zdir -and (Test-Path (Join-Path $7zdir "7z.exe"))) {
        return Join-Path $7zdir "7z.exe"
    }
    if (Test-Path $fallback7z) {
        return $fallback7z
    }
    return $null
}

function Check-7z {
    if (-not (Get-7z))
    {
        $null = New-Item -ItemType Directory -Force (Split-Path $fallback7z)
        $download_file = $fallback7z
        Write-Host "Downloading 7zr.exe" -ForegroundColor Green
        Invoke-WebRequest -Uri "https://www.7-zip.org/a/7zr.exe" -UserAgent $useragent -OutFile $download_file
    }
    else
    {
        Write-Host "7z already exist. Skipped download" -ForegroundColor Green
    }
}

function Check-PowershellVersion {
    $version = $PSVersionTable.PSVersion.Major
    Write-Host "Checking Windows PowerShell version -- $version" -ForegroundColor Green
    if ($version -le 2)
    {
        Write-Host "Using Windows PowerShell $version is unsupported. Upgrade your Windows PowerShell." -ForegroundColor Red
        throw
    }
}

function Check-Ytplugin {
    $ytdlp = Get-ChildItem "yt-dlp*.exe" -ErrorAction Ignore
    $youtubedl = Get-ChildItem "youtube-dl.exe" -ErrorAction Ignore
    if ($ytdlp) {
        return $ytdlp.ToString()
    }
    elseif ($youtubedl) {
        return $youtubedl.ToString()
    }
    else {
        return $null
    }
}

function Check-Ytplugin-In-System {
    $ytp = Get-Command -CommandType Application -ErrorAction Ignore yt-dlp.exe | Select-Object -Last 1
    if (-not $ytp) {
        $ytp = Get-Command -CommandType Application -ErrorAction Ignore youtube-dl.exe | Select-Object -Last 1
    }
    return [bool]($ytp -and ((Split-Path $ytp.Source) -ne (Get-Location)))
}

function Check-Mpv {
    $mpv = (Get-Location).Path + "\mpv.exe"
    $is_exist = Test-Path $mpv
    return $is_exist
}

function Download-Archive ($filename, $link) {
    Write-Host "Downloading" $filename -ForegroundColor Green
    Invoke-WebRequest -Uri $link -UserAgent $useragent -OutFile $filename
}

function Download-Ytplugin ($plugin, $version) {
    $link = ""
    $plugin_exe = ""
    switch -wildcard ($plugin) {
        "yt-dlp*" {
            Write-Host "Downloading $plugin ($version)" -ForegroundColor Green
            $32bit = ""
            if (-Not (Test-Path (Join-Path $env:windir "SysWow64"))) {
                $32bit = "_x86"
            }
            $link = -join("https://github.com/yt-dlp/yt-dlp/releases/download/", $version, "/", $plugin, $32bit, ".exe")
            $plugin_exe = -join($plugin, $32bit, ".exe")
        }
        "youtube-dl" {
            Write-Host "Downloading $plugin ($version)" -ForegroundColor Green
            $link = -join("https://yt-dl.org/downloads/", $version, "/youtube-dl.exe")
            $plugin_exe = "youtube-dl.exe"
        }
    }
    Invoke-WebRequest -Uri $link -UserAgent $useragent -OutFile $plugin_exe
}

function Extract-Archive ($file) {
    $7z = Get-7z
    Write-Host "Extracting" $file -ForegroundColor Green
    & $7z x -y $file
}

function Get-Latest-Mpv($Arch, $channel) {
    $filename = ""
    $download_link = ""
    switch -wildcard ($channel) {
        "daily" {
            $api_gh = "https://api.github.com/repos/shinchiro/mpv-winbuild-cmake/releases/latest"
            $json = Invoke-WebRequest $api_gh -MaximumRedirection 0 -ErrorAction Ignore -UseBasicParsing | ConvertFrom-Json
            $filename = $json.assets | where { $_.name -Match "mpv-$Arch" } | Select-Object -ExpandProperty name
            $download_link = $json.assets | where { $_.name -Match "mpv-$Arch" } | Select-Object -ExpandProperty browser_download_url
        }
        "weekly" {
            $i686_link = "https://sourceforge.net/projects/mpv-player-windows/rss?path=/32bit"
            $x86_64_link = "https://sourceforge.net/projects/mpv-player-windows/rss?path=/64bit"
            $x86_64v3_link = "https://sourceforge.net/projects/mpv-player-windows/rss?path=/64bit-v3"
            $rss_link = ''
            switch ($Arch)
            {
                i686 { $rss_link = $i686_link}
                x86_64 { $rss_link = $x86_64_link }
                x86_64-v3 { $rss_link = $x86_64v3_link }
            }
            Write-Host "Fetching RSS feed for mpv" -ForegroundColor Green
            $result = [xml](New-Object System.Net.WebClient).DownloadString($rss_link)
            $latest = $result.rss.channel.item.link[0]
            $tempname = $latest.split("/")[-2]
            $filename = [System.Uri]::UnescapeDataString($tempname)
            $download_link = "https://download.sourceforge.net/mpv-player-windows/" + $filename
        }
    }
    if ($filename -is [array]) {
        return $filename[0], $download_link[0]
    }
    else {
        return $filename, $download_link
    }
}

function Get-Latest-Ytplugin ($plugin) {
    switch -wildcard ($plugin) {
        "yt-dlp*" {
            $link = "https://github.com/yt-dlp/yt-dlp/releases.atom"
            Write-Host "Fetching RSS feed for ytp-dlp" -ForegroundColor Green
            $resp = [xml](Invoke-WebRequest $link -MaximumRedirection 0 -ErrorAction Ignore -UseBasicParsing).Content
            $link = $resp.feed.entry[0].link.href
            $version = $link.split("/")[-1]
            return $version
        }
        "youtube-dl" {
            $link = "https://yt-dl.org/downloads/latest/youtube-dl.exe"
            Write-Host "Fetching RSS feed for youtube-dl" -ForegroundColor Green
            $resp = Invoke-WebRequest $link -MaximumRedirection 0 -ErrorAction Ignore -UseBasicParsing
            $redirect_link = $resp.Headers.Location
            $version = $redirect_link.split("/")[4]
            return $version
        }
    }
}

function Get-Latest-FFmpeg ($Arch) {
    $api_gh = "https://api.github.com/repos/shinchiro/mpv-winbuild-cmake/releases/latest"
    $json = Invoke-WebRequest $api_gh -MaximumRedirection 0 -ErrorAction Ignore -UseBasicParsing | ConvertFrom-Json
    $filename = $json.assets | where { $_.name -Match "ffmpeg-$Arch" } | Select-Object -ExpandProperty name
    $download_link = $json.assets | where { $_.name -Match "ffmpeg-$Arch" } | Select-Object -ExpandProperty browser_download_url
    if ($filename -is [array]) {
        return $filename[0], $download_link[0]
    }
    else {
        return $filename, $download_link
    }
}

function Get-Arch {
    # Reference: http://superuser.com/a/891443
    $FilePath = [System.IO.Path]::Combine((Get-Location).Path, 'mpv.exe')
    [int32]$MACHINE_OFFSET = 4
    [int32]$PE_POINTER_OFFSET = 60

    [byte[]]$data = New-Object -TypeName System.Byte[] -ArgumentList 4096
    $stream = New-Object -TypeName System.IO.FileStream -ArgumentList ($FilePath, 'Open', 'Read')
    $stream.Read($data, 0, 4096) | Out-Null

    # DOS header is 64 bytes, last element, long (4 bytes) is the address of the PE header
    [int32]$PE_HEADER_ADDR = [System.BitConverter]::ToInt32($data, $PE_POINTER_OFFSET)
    [int32]$machineUint = [System.BitConverter]::ToUInt16($data, $PE_HEADER_ADDR + $MACHINE_OFFSET)

    $result = "" | select FilePath, FileType
    $result.FilePath = $FilePath

    switch ($machineUint)
    {
        0      { $result.FileType = 'Native' }
        0x014c { $result.FileType = 'i686' } # 32bit
        0x0200 { $result.FileType = 'Itanium' }
        0x8664 { $result.FileType = 'x86_64' } # 64bit
    }

    $stream.Close()
    $result
}

function ExtractGitFromFile {
    $stripped = .\mpv --no-config | select-string "mpv" | select-object -First 1
    $pattern = "-g([a-z0-9-]{7})"
    $bool = $stripped -match $pattern
    return $matches[1]
}

function ExtractGitFromURL($filename) {
    $pattern = "-git-([a-z0-9-]{7})"
    $bool = $filename -match $pattern
    return $matches[1]
}

function ExtractDateFromFile {
    $date = (Get-Item ./mpv.exe).LastWriteTimeUtc
    $day = $date.Day.ToString("00")
    $month = $date.Month.ToString("00")
    $year = $date.Year.ToString("0000")
    return "$year$month$day"
}

function ExtractDateFromURL($filename) {
    $pattern = "mpv-[xi864_].*-([0-9]{8})-git-([a-z0-9-]{7})"
    $bool = $filename -match $pattern
    return $matches[1]
}

function Test-Admin
{
    $user = [Security.Principal.WindowsIdentity]::GetCurrent();
    (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

function Create-XML {
@"
<settings>
  <channel>unset</channel>
  <arch>unset</arch>
  <autodelete>unset</autodelete>
  <getffmpeg>unset</getffmpeg>
</settings>
"@ | Set-Content "settings.xml" -Encoding UTF8
}

function Check-ChannelRelease {
    $channel = ""
    $file = "settings.xml"

    if (-not (Test-Path $file)) {
        $result = Read-KeyOrTimeout "Choose mpv updates frequency, weekly or daily? [1=weekly/2=daily] (default=1)" "D1"
        Write-Host ""
        if ($result -eq 'D1') {
            $channel = "weekly"
        }
        elseif ($result -eq 'D2') {
            $channel = "daily"
        }
        else {
            throw "Please enter valid input key."
        }
        Create-XML
        [xml]$doc = Get-Content $file
        $doc.settings.channel = $channel
        $doc.Save($file)
    }
    else {
        [xml]$doc = Get-Content $file
        $channel = $doc.settings.channel
    }
    return $channel
}

function Check-Arch($arch) {
    $get_arch = ""
    $file = "settings.xml"

    if (-not (Test-Path $file)) { exit }
    [xml]$doc = Get-Content $file
    if ($doc.settings.arch -eq "unset") {
        if ($arch -eq "i686") {
            $get_arch = "i686"
        }
        else {
            $result = Read-KeyOrTimeout "Choose variant for 64bit builds: x86_64 or x86_64-v3 (for cpu with AVX2 support) [1=x86_64 / 2=x86_64-v3 (default=1)" "D1"
            Write-Host ""
            if ($result -eq 'D1') {
                $get_arch = "x86_64"
            }
            elseif ($result -eq 'D2') {
                $get_arch = "x86_64-v3"
            }
            else {
                throw "Please enter valid input key."
            }
        }
        $doc.settings.arch = $get_arch
        $doc.Save($file)
    }
    else {
        $get_arch = $doc.settings.arch
    }
    return $get_arch
}

function Check-Autodelete($archive) {
    $autodelete = ""
    $file = "settings.xml"

    if (-not (Test-Path $file)) { exit }
    [xml]$doc = Get-Content $file
    if ($doc.settings.autodelete -eq "unset") {
        $result = Read-KeyOrTimeout "Delete archives after extract? [Y/n] (default=Y)" "Y"
        Write-Host ""
        if ($result -eq 'Y') {
            $autodelete = "true"
        }
        elseif ($result -eq 'N') {
            $autodelete = "false"
        }
        else {
            throw "Please enter valid input key."
        }
        $doc.settings.autodelete = $autodelete
        $doc.Save($file)
    }
    if ($doc.settings.autodelete -eq "true") {
        if (Test-Path $archive)
        {
            Remove-Item -Force $archive
        }
    }
}

function Check-GetFFmpeg() {
    $get_ffmpeg = ""
    $file = "settings.xml"

    if (-not (Test-Path $file)) { exit }
    [xml]$doc = Get-Content $file
    if ($doc.settings.getffmpeg -eq "unset") {
        Write-Host "FFmpeg doesn't exist. " -ForegroundColor Green -NoNewline
        $result = Read-KeyOrTimeout "Proceed with downloading? [Y/n] (default=n)" "N"
        Write-Host ""
        if ($result -eq 'Y') {
            $get_ffmpeg = "true"
        }
        elseif ($result -eq 'N') {
            $get_ffmpeg = "false"
        }
        else {
            throw "Please enter valid input key."
        }
        $doc.settings.getffmpeg = $get_ffmpeg
        $doc.Save($file)
    }
    else {
        $get_ffmpeg = $doc.settings.getffmpeg
    }
    return $get_ffmpeg
}

function Upgrade-Mpv {
    $need_download = $false
    $remoteName = ""
    $download_link = ""
    $arch = ""
    $channel = ""

    if (Check-Mpv) {
        $channel = Check-ChannelRelease
        $file_arch = (Get-Arch).FileType
        $arch = Check-Arch $file_arch
        $remoteName, $download_link = Get-Latest-Mpv $arch $channel
        $localgit = ExtractGitFromFile
        $localdate = ExtractDateFromFile
        $remotegit = ExtractGitFromURL $remoteName
        $remotedate = ExtractDateFromURL $remoteName
        if ($localgit -match $remotegit)
        {
            if ($localdate -match $remotedate)
            {
                Write-Host "You are already using latest mpv build -- $remoteName" -ForegroundColor Green
                $need_download = $false
            }
            else {
                Write-Host "Newer mpv build available" -ForegroundColor Green
                $need_download = $true
            }
        }
        else {
            Write-Host "Newer mpv build available" -ForegroundColor Green
            $need_download = $true
        }
    }
    else {
        Write-Host "mpv doesn't exist. " -ForegroundColor Green -NoNewline
        $result = Read-KeyOrTimeout "Proceed with downloading? [Y/n] (default=y)" "Y"
        Write-Host ""

        if ($result -eq 'Y') {
            $need_download = $true
            if (Test-Path (Join-Path $env:windir "SysWow64")) {
                Write-Host "Detecting System Type is 64-bit" -ForegroundColor Green
                $original_arch = "x86_64"
            }
            else {
                Write-Host "Detecting System Type is 32-bit" -ForegroundColor Green
                $original_arch = "i686"
            }
            $channel = Check-ChannelRelease
            $arch = Check-Arch $original_arch
            $remoteName, $download_link = Get-Latest-Mpv $arch $channel
        }
        elseif ($result -eq 'N') {
            $need_download = $false
        }
        else {
            throw "Please enter valid input key."
        }
    }

    if ($need_download) {
        Download-Archive $remoteName $download_link
        Check-7z
        Extract-Archive $remoteName
    }
    Check-Autodelete $remoteName
}

function Upgrade-Ytplugin {
    if (Check-Ytplugin-In-System) {
        Write-Host "yt-dlp.exe or youtube-dl.exe already exists in your system, skip the update check." -ForegroundColor Green
        return
    }
    $yt = Check-Ytplugin
    if ($yt) {
        $latest_release = Get-Latest-Ytplugin((Get-Item $yt).BaseName)
        if ((& $yt --version) -match ($latest_release)) {
            Write-Host "You are already using latest" (Get-Item $yt).BaseName "-- $latest_release" -ForegroundColor Green
        }
        else {
            Write-Host "Newer" (Get-Item $yt).BaseName "build available" -ForegroundColor Green
            & $yt --update
        }
    }
    else {
        Write-Host "ytdlp or youtube-dl doesn't exist. " -ForegroundColor Green -NoNewline
        $result = Read-KeyOrTimeout "Proceed with downloading? [Y/n] (default=n)" "N"
        Write-Host ""

        if ($result -eq 'Y') {
            $result_exe = Read-KeyOrTimeout "Download ytdlp or youtubedl? [1=ytdlp/2=youtubedl] (default=1)" "D1"
            Write-Host ""
            if ($result_exe -eq 'D1') {
                $latest_release = Get-Latest-Ytplugin "yt-dlp"
                Download-Ytplugin "yt-dlp" $latest_release
            }
            elseif ($result_exe -eq 'D2') {
                $latest_release = Get-Latest-Ytplugin "youtube-dl"
                Download-Ytplugin "youtube-dl" $latest_release
            }
            else {
                throw "Please enter valid input key."
            }
        }
    }
}

function Upgrade-FFmpeg {
    $get_ffmpeg = Check-GetFFmpeg
    if ($get_ffmpeg -eq "false") {
        return
    }

    if (Test-Path (Join-Path $env:windir "SysWow64")) {
        $original_arch = "x86_64"
        $arch = Check-Arch $original_arch
    }
    else {
        $arch = "i686"
    }

    $need_download = $false
    $remote_name, $download_link = Get-Latest-FFmpeg $arch
    $ffmpeg = (Get-Location).Path + "\ffmpeg.exe"
    $ffmpeg_exist = Test-Path $ffmpeg

    if ($ffmpeg_exist) {
        $ffmpeg_file = .\ffmpeg -version | select-string "ffmpeg" | select-object -First 1
        $file_pattern_1 = "git-[0-9]{4}-[0-9]{2}-[0-9]{2}-(?<commit>[a-z0-9]+)" # git-2023-01-02-cc2b1a325
        $file_pattern_2 = "N-\d+-g(?<commit>[a-z0-9]+)"                         # N-109751-g9a820ec8b
        $file_pattern = $file_pattern_1, $file_pattern_2 -join '|'
        $url_pattern = "git-([a-z0-9]+)"
        $file_match= [Regex]::Matches($ffmpeg_file, $file_pattern)
        $remote_match = [Regex]::Matches($remote_name, $url_pattern)
        $local_git = $file_match[0].groups['commit'].value
        $remote_git = $remote_match[0].groups[1].value

        if ($local_git -match $remote_git) {
            Write-Host "You are already using latest ffmpeg build -- $remote_name" -ForegroundColor Green
            $need_download = $false
        }
        else {
            Write-Host "Newer ffmpeg build available" -ForegroundColor Green
            $need_download = $true
        }
    }
    else {
        $need_download = $true
    }

    if ($need_download) {
        Download-Archive $remote_name $download_link
        Check-7z
        Extract-Archive $remote_name
    }
    Check-Autodelete $remote_name
}

function Read-KeyOrTimeout ($prompt, $key){
    $seconds = 9
    $startTime = Get-Date
    $timeOut = New-TimeSpan -Seconds $seconds

    Write-Host "$prompt " -ForegroundColor Green

    # Basic progress bar
    [Console]::CursorLeft = 0
    [Console]::Write("[")
    [Console]::CursorLeft = $seconds + 2
    [Console]::Write("]")
    [Console]::CursorLeft = 1

    while (-not [System.Console]::KeyAvailable) {
        $currentTime = Get-Date
        Start-Sleep -s 1
        Write-Host "#" -ForegroundColor Green -NoNewline
        if ($currentTime -gt $startTime + $timeOut) {
            Break
        }
    }
    if ([System.Console]::KeyAvailable) {
        $response = [System.Console]::ReadKey($true).Key
    }
    else {
        $response = $key
    }
    return $response.ToString()
}

#
# Main script entry point
#
if (Test-Admin) {
    Write-Host "Running script with administrator privileges" -ForegroundColor Yellow
}
else {
    Write-Host "Running script without administrator privileges" -ForegroundColor Red
}

try {
    Check-PowershellVersion
    # Sourceforge only support TLS 1.2
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    $global:progressPreference = 'silentlyContinue'
    Upgrade-Mpv
    Upgrade-Ytplugin
    Upgrade-FFmpeg
    Write-Host "Operation completed" -ForegroundColor Magenta
}
catch [System.Exception] {
    Write-Host $_.Exception.Message -ForegroundColor Red
    exit 1
}
