#!/usr/bin/env python3
"""Прошивка RT-4D из WSL через powershell.exe (WSL2 не отдаёт COM напрямую).

    python3 tools/flash_ps.py stock-fw/rt4d_ru_batt.bin [--port COM4]

Рация должна быть в режиме прошивки: выключить -> включить, удерживая тангенту PTT.

Протокол бутлоадера (тот же, что в flash_rt4d.py):
    рукопожатие : слать 0xFF, пока не придёт 0xFF
    стирание    : [39 33 05 10]+CK, затем [39 33 05 55]+CK  -> ACK 0x06
    запись      : [57 hi lo] + 1024 Б + CK                  -> ACK 0x06
    CK = (0x48 + сумма) & 0xFF
"""
import argparse, os, shutil, subprocess, sys

WIN_TMP_WSL = "/mnt/c/Users/vikto/AppData/Local/Temp"
WIN_TMP_WIN = r"C:\Users\vikto\AppData\Local\Temp"

PS = r'''
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$fw = [System.IO.File]::ReadAllBytes("{fwpath}")
$p = New-Object System.IO.Ports.SerialPort "{port}",115200,"None",8,"one"
$p.ReadTimeout = 250
$p.WriteTimeout = 5000
try {{ $p.Open() }} catch {{ Write-Output "ERR: порт занят или недоступен: $($_.Exception.Message)"; exit 1 }}

Write-Output "INFO: прошивка $($fw.Length) байт, порт {port}"

# --- рукопожатие ---
$p.DiscardInBuffer()
$ready = $false
$sw = [Diagnostics.Stopwatch]::StartNew()
while ($sw.ElapsedMilliseconds -lt 8000) {{
  try {{ if ($p.ReadByte() -eq 0xFF) {{ $ready = $true; break }} }}
  catch {{ $p.Write([byte[]]@(0xFF), 0, 1) }}
}}
if (-not $ready) {{
  Write-Output "ERR: бутлоадер не отвечает. Выключите рацию и включите с зажатой тангентой PTT."
  $p.Close(); exit 1
}}
Write-Output "INFO: бутлоадер найден"

# --- стирание ---
foreach ($trig in 0x10, 0x55) {{
  $pl = [byte[]]@(0x39, 0x33, 0x05, $trig, 0)
  $s = 0x48; for ($i = 0; $i -lt 4; $i++) {{ $s = ($s + $pl[$i]) -band 0xFF }}
  $pl[4] = [byte]$s
  $p.DiscardInBuffer(); $p.Write($pl, 0, 5)
  $ok = $false; $sw2 = [Diagnostics.Stopwatch]::StartNew()
  while ($sw2.ElapsedMilliseconds -lt 6000) {{ try {{ if ($p.ReadByte() -eq 6) {{ $ok = $true; break }} }} catch {{}} }}
  if (-not $ok) {{ Write-Output "ERR: ошибка стирания"; $p.Close(); exit 1 }}
}}
Write-Output "INFO: область приложения стёрта"

# --- дополнение до кратности 1024 ---
$pad = (1024 - ($fw.Length % 1024)) % 1024
if ($pad -gt 0) {{
  $t = New-Object byte[] ($fw.Length + $pad)
  [Array]::Copy($fw, $t, $fw.Length)
  for ($i = $fw.Length; $i -lt $t.Length; $i++) {{ $t[$i] = 0xFF }}
  $fw = $t
}}
$total = $fw.Length
$blocks = $total / 1024

# --- запись ---
$pl = New-Object byte[] 1028
for ($off = 0; $off -lt $total; $off += 1024) {{
  $pl[0] = 0x57
  $pl[1] = [byte](($off -shr 8) -band 0xFF)
  $pl[2] = [byte]($off -band 0xFF)
  [Array]::Copy($fw, $off, $pl, 3, 1024)
  $s = 0x48
  for ($i = 0; $i -lt 1027; $i++) {{ $s = ($s + $pl[$i]) -band 0xFF }}
  $pl[1027] = [byte]$s
  $p.DiscardInBuffer(); $p.Write($pl, 0, 1028)
  $ok = $false; $sw3 = [Diagnostics.Stopwatch]::StartNew()
  while ($sw3.ElapsedMilliseconds -lt 4000) {{ try {{ if ($p.ReadByte() -eq 6) {{ $ok = $true; break }} }} catch {{}} }}
  if (-not $ok) {{ Write-Output ("ERR: нет ACK на блоке 0x{{0:X5}}" -f $off); $p.Close(); exit 1 }}
  $n = ($off / 1024) + 1
  if (($n % 16) -eq 0 -or $n -eq $blocks) {{ Write-Output ("PROG: {{0}}/{{1}} блоков" -f $n, $blocks) }}
}}
$p.Close()
Write-Output "OK: записано. Выключите и включите рацию обычным образом."
'''


def find_port() -> str:
    out = subprocess.run(["powershell.exe", "-NoProfile", "-Command",
                          "[System.IO.Ports.SerialPort]::GetPortNames() -join ','"],
                         capture_output=True, text=True).stdout
    ports = [p for p in out.strip().replace("\r", "").split(",") if p]
    if not ports:
        sys.exit("COM-порт не найден — подключите рацию.")
    return ports[0]


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("firmware")
    ap.add_argument("--port")
    a = ap.parse_args()

    fw = os.path.abspath(a.firmware)
    if not os.path.exists(fw):
        sys.exit(f"нет файла: {fw}")
    name = "rt4d_flash_tmp.bin"
    shutil.copy(fw, os.path.join(WIN_TMP_WSL, name))
    win_fw = os.path.join(WIN_TMP_WIN, name)

    port = a.port or find_port()
    script = PS.format(fwpath=win_fw.replace("\\", "\\\\"), port=port)

    proc = subprocess.Popen(["powershell.exe", "-NoProfile", "-Command", script],
                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                            text=True, encoding='utf-8', errors='replace')
    rc = 0
    for line in proc.stdout:
        line = line.rstrip("\r\n")
        if line:
            print(line, flush=True)
            if line.startswith("ERR:"):
                rc = 1
    proc.wait()
    sys.exit(rc or proc.returncode)


if __name__ == "__main__":
    main()
