@echo off
setlocal EnableExtensions
chcp 65001 >nul
set "APPDIR=%LOCALAPPDATA%\FitnessPhotoConverter"
set "APP=%APPDIR%\FitnessPhotoConverter.cmd"
set "PSSCRIPT=%APPDIR%\FitnessPhotoConverter.ps1"
set "LOG=%APPDIR%\launcher.log"

if not exist "%APPDIR%" mkdir "%APPDIR%" >nul 2>&1
> "%LOG%" echo [%date% %time%] Fitness Photo Converter launcher started
>>"%LOG%" echo Source=%~f0

rem Keep a persistent copy so later web launches do not depend on Downloads.
if /I not "%~f0"=="%APP%" (
  copy /y "%~f0" "%APP%" >>"%LOG%" 2>&1
  if errorlevel 1 goto COPY_FAIL
)

rem IMPORTANT: build the marker in PowerShell from two pieces so the full marker
rem does not occur earlier in this CMD file. This fixes the V7.5 extraction bug.
set "SELF=%~f0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$raw=Get-Content -LiteralPath $env:SELF -Raw -Encoding UTF8; $mark=('#<FITNESS_'+'PS_BEGIN>'); $i=$raw.LastIndexOf($mark); if($i -lt 0){exit 2}; $body=$raw.Substring($i+$mark.Length); [IO.File]::WriteAllText($env:PSSCRIPT,$body,(New-Object Text.UTF8Encoding($true)))" >>"%LOG%" 2>&1
if errorlevel 1 goto EXTRACT_FAIL

if not exist "%PSSCRIPT%" goto EXTRACT_FAIL
for %%A in ("%PSSCRIPT%") do if %%~zA LSS 1000 goto EXTRACT_FAIL

rem Register per-user URL protocol. Administrator rights are not required.
reg add "HKCU\Software\Classes\fitnessphoto" /ve /d "URL:Fitness Photo Converter" /f >>"%LOG%" 2>&1
reg add "HKCU\Software\Classes\fitnessphoto" /v "URL Protocol" /d "" /f >>"%LOG%" 2>&1
reg add "HKCU\Software\Classes\fitnessphoto\DefaultIcon" /ve /d "%SystemRoot%\System32\shell32.dll,3" /f >>"%LOG%" 2>&1
reg add "HKCU\Software\Classes\fitnessphoto\shell\open\command" /ve /d "\"%APP%\" \"%%1\"" /f >>"%LOG%" 2>&1

>>"%LOG%" echo Launching PowerShell UI...
powershell.exe -NoProfile -ExecutionPolicy Bypass -STA -File "%PSSCRIPT%" "%~1" >>"%LOG%" 2>&1
set "RC=%ERRORLEVEL%"
>>"%LOG%" echo PowerShell exit code=%RC%
if not "%RC%"=="0" goto PS_FAIL
exit /b 0

:COPY_FAIL
echo.
echo Fitness Photo Converter 파일 준비에 실패했습니다.
echo 로그: %LOG%
echo.
pause
exit /b 11

:EXTRACT_FAIL
echo.
echo Fitness Photo Converter 실행 파일을 준비하지 못했습니다.
echo 다운로드 파일을 다시 실행해 주세요.
echo 로그: %LOG%
echo.
pause
exit /b 12

:PS_FAIL
echo.
echo Fitness Photo Converter를 실행하지 못했습니다.
echo 오류 확인용 로그가 생성되었습니다:
echo %LOG%
echo.
pause
exit /b %RC%

#<FITNESS_PS_BEGIN>

# Fitness Photo Converter
# Local-only photo -> low-volume PDF converter for Windows.
# No network upload is performed by this program.

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Web
Add-Type -AssemblyName System.Net.Http


$LaunchUri = if ($args.Count -gt 0) { [string]$args[0] } else { '' }
$UploadToken = ''
$ServerBase = ''
if ($LaunchUri -match '^fitnessphoto://') {
    try {
        $u = [Uri]$LaunchUri
        $q = [System.Web.HttpUtility]::ParseQueryString($u.Query)
        $UploadToken = $q['token']
        $ServerBase = $q['base']
    } catch {}
}

[System.Windows.Forms.Application]::EnableVisualStyles()

$script:Files = New-Object System.Collections.Generic.List[string]

function Format-Bytes([long]$n) {
    if ($n -lt 1KB) { return "$n B" }
    if ($n -lt 1MB) { return ('{0:N0} KB' -f ($n / 1KB)) }
    return ('{0:N1} MB' -f ($n / 1MB))
}

function Add-PhotoPaths([string[]]$paths) {
    foreach ($p in $paths) {
        if (-not (Test-Path -LiteralPath $p -PathType Leaf)) { continue }
        $ext = [IO.Path]::GetExtension($p).ToLowerInvariant()
        if ($ext -in @('.jpg','.jpeg','.png','.bmp','.gif','.webp')) {
            if (-not $script:Files.Contains($p)) { [void]$script:Files.Add($p) }
        }
    }
    Refresh-List
}

function Refresh-List {
    $list.Items.Clear()
    [long]$total = 0
    foreach ($p in $script:Files) {
        try { $fi = Get-Item -LiteralPath $p; $total += $fi.Length; [void]$list.Items.Add(("{0}   ({1})" -f $fi.Name,(Format-Bytes $fi.Length))) } catch {}
    }
    $summary.Text = ("선택 {0}장 · 원본 {1}" -f $script:Files.Count,(Format-Bytes $total))
    $convert.Enabled = ($script:Files.Count -gt 0)
}

function Get-JpegBytes([string]$path) {
    $source = $null
    $bmp = $null
    $ms = $null
    try {
        # Image.FromFile(string) is Unicode-path aware on Windows/.NET.
        $source = [System.Drawing.Image]::FromFile($path)
        $maxDim = 1200
        $scale = [Math]::Min(1.0, $maxDim / [double][Math]::Max($source.Width,$source.Height))
        $w = [Math]::Max(1,[int][Math]::Round($source.Width*$scale))
        $h = [Math]::Max(1,[int][Math]::Round($source.Height*$scale))

        $qualities = @(62L,54L,46L,38L,32L)
        $target = 360KB
        $maxOut = 520KB
        $resultBytes = $null

        foreach ($q in $qualities) {
            if ($bmp) { $bmp.Dispose(); $bmp=$null }
            $bmp = New-Object System.Drawing.Bitmap $w,$h
            $g = [System.Drawing.Graphics]::FromImage($bmp)
            try {
                $g.Clear([System.Drawing.Color]::White)
                $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
                $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
                $g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
                $g.DrawImage($source,0,0,$w,$h)
            } finally { $g.Dispose() }

            $codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object MimeType -eq 'image/jpeg' | Select-Object -First 1
            $ep = New-Object System.Drawing.Imaging.EncoderParameters 1
            $ep.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter ([System.Drawing.Imaging.Encoder]::Quality),$q
            $ms = New-Object IO.MemoryStream
            $bmp.Save($ms,$codec,$ep)
            $resultBytes = $ms.ToArray()
            $ms.Dispose();$ms=$null
            if ($resultBytes.Length -le $target) { break }
        }

        while ($resultBytes.Length -gt $maxOut -and [Math]::Max($w,$h) -gt 640) {
            $w = [Math]::Max(1,[int][Math]::Round($w*0.84))
            $h = [Math]::Max(1,[int][Math]::Round($h*0.84))
            if ($bmp) { $bmp.Dispose(); $bmp=$null }
            $bmp = New-Object System.Drawing.Bitmap $w,$h
            $g = [System.Drawing.Graphics]::FromImage($bmp)
            try {
                $g.Clear([System.Drawing.Color]::White)
                $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
                $g.DrawImage($source,0,0,$w,$h)
            } finally { $g.Dispose() }
            $codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object MimeType -eq 'image/jpeg' | Select-Object -First 1
            $ep = New-Object System.Drawing.Imaging.EncoderParameters 1
            $ep.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter ([System.Drawing.Imaging.Encoder]::Quality),38L
            $ms = New-Object IO.MemoryStream
            $bmp.Save($ms,$codec,$ep)
            $resultBytes = $ms.ToArray()
            $ms.Dispose();$ms=$null
        }

        return [PSCustomObject]@{ Bytes=$resultBytes; Width=$w; Height=$h }
    } finally {
        if ($ms) { $ms.Dispose() }
        if ($bmp) { $bmp.Dispose() }
        if ($source) { $source.Dispose() }
    }
}

function Write-Ascii([IO.Stream]$stream,[string]$s) {
    $b=[Text.Encoding]::ASCII.GetBytes($s);$stream.Write($b,0,$b.Length)
}

function Write-PhotoPdf([string]$outputPath,$images) {
    $ms = New-Object IO.MemoryStream
    try {
        Write-Ascii $ms "%PDF-1.4`n%1234`n"
        $count = $images.Count
        $maxObj = 2 + 3*$count
        $offsets = New-Object long[] ($maxObj+1)

        function Start-Obj([int]$n) {
            $offsets[$n]=$ms.Position
            Write-Ascii $ms ("{0} 0 obj`n" -f $n)
        }
        function End-Obj { Write-Ascii $ms "`nendobj`n" }

        Start-Obj 1; Write-Ascii $ms "<< /Type /Catalog /Pages 2 0 R >>"; End-Obj

        $kids=@()
        for($i=0;$i -lt $count;$i++){ $kids += ("{0} 0 R" -f (5+3*$i)) }
        Start-Obj 2; Write-Ascii $ms ("<< /Type /Pages /Kids [{0}] /Count {1} >>" -f ($kids -join ' '),$count); End-Obj

        for($i=0;$i -lt $count;$i++){
            $im=$images[$i]
            $imageObj=3+3*$i;$contentObj=4+3*$i;$pageObj=5+3*$i
            $land=($im.Width -gt $im.Height)
            if($land){$pw=841.89;$ph=595.28}else{$pw=595.28;$ph=841.89}
            $margin=24.0
            $fit=[Math]::Min(($pw-2*$margin)/$im.Width,($ph-2*$margin)/$im.Height)
            $dw=$im.Width*$fit;$dh=$im.Height*$fit;$x=($pw-$dw)/2;$y=($ph-$dh)/2
            $content=("q`n{0:F2} 0 0 {1:F2} {2:F2} {3:F2} cm`n/Im1 Do`nQ`n" -f $dw,$dh,$x,$y)
            $contentBytes=[Text.Encoding]::ASCII.GetBytes($content)

            Start-Obj $imageObj
            Write-Ascii $ms ("<< /Type /XObject /Subtype /Image /Width {0} /Height {1} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length {2} >>`nstream`n" -f $im.Width,$im.Height,$im.Bytes.Length)
            $ms.Write($im.Bytes,0,$im.Bytes.Length);Write-Ascii $ms "`nendstream";End-Obj

            Start-Obj $contentObj
            Write-Ascii $ms ("<< /Length {0} >>`nstream`n" -f $contentBytes.Length)
            $ms.Write($contentBytes,0,$contentBytes.Length);Write-Ascii $ms "endstream";End-Obj

            Start-Obj $pageObj
            Write-Ascii $ms ("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {0:F2} {1:F2}] /Resources << /XObject << /Im1 {2} 0 R >> >> /Contents {3} 0 R >>" -f $pw,$ph,$imageObj,$contentObj)
            End-Obj
        }

        $xrefPos=$ms.Position
        Write-Ascii $ms ("xref`n0 {0}`n0000000000 65535 f `n" -f ($maxObj+1))
        for($i=1;$i -le $maxObj;$i++){ Write-Ascii $ms (("{0:D10} 00000 n `n" -f $offsets[$i])) }
        Write-Ascii $ms ("trailer`n<< /Size {0} /Root 1 0 R >>`nstartxref`n{1}`n%%EOF" -f ($maxObj+1),$xrefPos)

        [IO.File]::WriteAllBytes($outputPath,$ms.ToArray())
    } finally { $ms.Dispose() }
}


function Upload-Pdf([string]$path,[string]$base,[string]$token) {
    if ([string]::IsNullOrWhiteSpace($base) -or [string]::IsNullOrWhiteSpace($token)) {
        throw "웹 첨부 세션 정보가 없습니다. Fitness Daily Task의 [프로그램 실행] 버튼으로 실행해 주세요."
    }
    $client = New-Object System.Net.Http.HttpClient
    $content = New-Object System.Net.Http.MultipartFormDataContent
    $fs = $null
    try {
        $fs = [IO.File]::OpenRead($path)
        $streamContent = New-Object System.Net.Http.StreamContent($fs)
        $streamContent.Headers.ContentType = New-Object System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf")
        $content.Add((New-Object System.Net.Http.StringContent($token)),"token")
        $content.Add($streamContent,"file",[IO.Path]::GetFileName($path))
        $response = $client.PostAsync(($base.TrimEnd('/') + '/api/pc-upload/file'),$content).GetAwaiter().GetResult()
        $text = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
        if (-not $response.IsSuccessStatusCode) { throw ("업로드 실패 ({0}): {1}" -f [int]$response.StatusCode,$text) }
        return $text
    } finally {
        if ($content) { $content.Dispose() }
        if ($client) { $client.Dispose() }
        if ($fs) { $fs.Dispose() }
    }
}

$form = New-Object System.Windows.Forms.Form
$form.Text = "Fitness Photo Converter"
$form.Size = New-Object System.Drawing.Size(620,520)
$form.StartPosition = "CenterScreen"
$form.Font = New-Object System.Drawing.Font("Malgun Gothic",9)
$form.AllowDrop = $true

$title = New-Object System.Windows.Forms.Label
$title.Text = "사진을 저용량 PDF로 변환"
$title.Font = New-Object System.Drawing.Font("Malgun Gothic",16,[System.Drawing.FontStyle]::Bold)
$title.AutoSize = $true
$title.Location = New-Object System.Drawing.Point(22,20)
$form.Controls.Add($title)

$desc = New-Object System.Windows.Forms.Label
$desc.Text = "사진 원본은 외부로 전송하지 않습니다. PDF 생성 후 저장 폴더가 자동으로 열립니다."
$desc.AutoSize = $true
$desc.Location = New-Object System.Drawing.Point(24,58)
$form.Controls.Add($desc)

$drop = New-Object System.Windows.Forms.Panel
$drop.Location = New-Object System.Drawing.Point(24,92)
$drop.Size = New-Object System.Drawing.Size(552,90)
$drop.BorderStyle = "FixedSingle"
$drop.AllowDrop = $true
$form.Controls.Add($drop)

$dropLabel = New-Object System.Windows.Forms.Label
$dropLabel.Text = "사진을 이곳에 끌어 놓거나 [사진 선택]을 눌러주세요."
$dropLabel.AutoSize = $true
$dropLabel.Location = New-Object System.Drawing.Point(92,33)
$drop.Controls.Add($dropLabel)

$select = New-Object System.Windows.Forms.Button
$select.Text = "사진 선택"
$select.Location = New-Object System.Drawing.Point(24,197)
$select.Size = New-Object System.Drawing.Size(100,34)
$form.Controls.Add($select)

$clear = New-Object System.Windows.Forms.Button
$clear.Text = "목록 비우기"
$clear.Location = New-Object System.Drawing.Point(132,197)
$clear.Size = New-Object System.Drawing.Size(100,34)
$form.Controls.Add($clear)

$summary = New-Object System.Windows.Forms.Label
$summary.Text = "선택 0장"
$summary.AutoSize = $true
$summary.Location = New-Object System.Drawing.Point(250,207)
$form.Controls.Add($summary)

$list = New-Object System.Windows.Forms.ListBox
$list.Location = New-Object System.Drawing.Point(24,244)
$list.Size = New-Object System.Drawing.Size(552,150)
$form.Controls.Add($list)

$status = New-Object System.Windows.Forms.Label
$status.Text = "대기 중"
$status.AutoSize = $true
$status.Location = New-Object System.Drawing.Point(24,408)
$form.Controls.Add($status)

$convert = New-Object System.Windows.Forms.Button
$convert.Text = "PDF 만들기"
$convert.Enabled = $false
$convert.Location = New-Object System.Drawing.Point(438,402)
$convert.Size = New-Object System.Drawing.Size(138,42)
$form.Controls.Add($convert)

$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Multiselect = $true
$ofd.Filter = "사진 파일|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.webp|모든 파일|*.*"

$select.Add_Click({
    if($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ Add-PhotoPaths $ofd.FileNames }
})
$clear.Add_Click({ $script:Files.Clear(); Refresh-List })

$drop.Add_DragEnter({ param($s,$e) if($e.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)){$e.Effect=[Windows.Forms.DragDropEffects]::Copy} })
$drop.Add_DragDrop({ param($s,$e) Add-PhotoPaths ([string[]]$e.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) })
$form.Add_DragEnter({ param($s,$e) if($e.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)){$e.Effect=[Windows.Forms.DragDropEffects]::Copy} })
$form.Add_DragDrop({ param($s,$e) Add-PhotoPaths ([string[]]$e.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) })

$convert.Add_Click({
    if($script:Files.Count -eq 0){ return }
    $convert.Enabled=$false;$select.Enabled=$false;$clear.Enabled=$false
    $tempPdf=$null
    try{
        $images=New-Object System.Collections.ArrayList
        for($i=0;$i -lt $script:Files.Count;$i++){
            $name=[IO.Path]::GetFileName($script:Files[$i])
            $status.Text=("사진 처리 중 {0}/{1} : {2}" -f ($i+1),$script:Files.Count,$name)
            $form.Refresh()
            try{ [void]$images.Add((Get-JpegBytes $script:Files[$i])) }
            catch{ throw ("사진 처리 실패: {0}`r`n{1}" -f $name,$_.Exception.Message) }
        }
        $status.Text="PDF 변환 중..."
        $form.Refresh()
        $tempDir=Join-Path $env:LOCALAPPDATA "FitnessPhotoConverter\Temp"
        if(-not (Test-Path -LiteralPath $tempDir)){New-Item -ItemType Directory -Path $tempDir -Force | Out-Null}
        $stamp=Get-Date -Format "yyyyMMdd_HHmmss"
        $tempPdf=Join-Path $tempDir "Fitness_Photo_$stamp.pdf"
        Write-PhotoPdf $tempPdf $images
        $fi=Get-Item -LiteralPath $tempPdf
        if($fi.Length -gt 10MB){throw "변환된 PDF가 10MB를 초과했습니다. 사진 수를 나누어 다시 시도해 주세요."}
        $status.Text=("PDF 변환 완료 ({0}) · 자동 업로드 중..." -f (Format-Bytes $fi.Length))
        $form.Refresh()
        Upload-Pdf $tempPdf $ServerBase $UploadToken | Out-Null
        $status.Text=("업로드 완료 · {0}" -f (Format-Bytes $fi.Length))
        [System.Windows.Forms.MessageBox]::Show(
            "사진의 PDF 변환 및 업로드가 완료되었습니다.`r`n웹 화면으로 돌아가 기록을 저장해 주세요.",
            "Fitness Photo Converter",
            [System.Windows.Forms.MessageBoxButtons]::OK,
            [System.Windows.Forms.MessageBoxIcon]::Information
        ) | Out-Null
        $form.Close()
    }catch{
        $status.Text="처리 실패"
        [System.Windows.Forms.MessageBox]::Show(
            $_.Exception.Message,
            "Fitness Photo Converter",
            [System.Windows.Forms.MessageBoxButtons]::OK,
            [System.Windows.Forms.MessageBoxIcon]::Error
        ) | Out-Null
    }finally{
        if($tempPdf -and (Test-Path -LiteralPath $tempPdf)){Remove-Item -LiteralPath $tempPdf -Force -ErrorAction SilentlyContinue}
        $convert.Enabled=($script:Files.Count -gt 0);$select.Enabled=$true;$clear.Enabled=$true
    }
})


Refresh-List
if(-not [string]::IsNullOrWhiteSpace($UploadToken) -and -not [string]::IsNullOrWhiteSpace($ServerBase)){
    $form.Add_Shown({
        $form.BeginInvoke([Action]{
            if($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){
                Add-PhotoPaths $ofd.FileNames
                if($script:Files.Count -gt 0){ $convert.PerformClick() }
            }
        }) | Out-Null
    })
}
[void]$form.ShowDialog()
