Option Explicit

If WScript.Arguments.Count > 0 Then
    If LCase(Trim(CStr(WScript.Arguments(0)))) = "--validate" Then
        WScript.Echo "NuCasa Boot Production 2nd Run syntax OK"
        WScript.Quit 0
    End If
End If

' ============================================================
' Self boost launcher
' Purpose: run this VBS as High priority and ALL CPU affinity.
' Windows 7 x4 CPU mask: 15 = CPU0 + CPU1 + CPU2 + CPU3
' ============================================================
Const BOOST_ENABLE       = True
Const BOOST_PRIORITY     = "High"
Const BOOST_AFFINITY_ALL = 15

If BOOST_ENABLE Then
    Call RelaunchWithBoostIfNeeded()
End If

Sub RelaunchWithBoostIfNeeded()
    On Error Resume Next

    Dim boosted
    Dim i
    Dim sh
    Dim scriptPath
    Dim psCmd
    Dim runCmd

    boosted = False

    For i = 0 To WScript.Arguments.Count - 1
        If LCase(Trim(CStr(WScript.Arguments(i)))) = "--boosted" Then
            boosted = True
            Exit For
        End If
    Next

    If boosted = True Then
        Exit Sub
    End If

    Set sh = CreateObject("WScript.Shell")
    scriptPath = WScript.ScriptFullName

    psCmd = "$p = Start-Process -FilePath 'wscript.exe' " & _
            "-ArgumentList '""" & Replace(scriptPath, "'", "''") & """ --boosted' " & _
            "-PassThru -WindowStyle Hidden; " & _
            "Start-Sleep -Milliseconds 300; " & _
            "$p.PriorityClass = '" & BOOST_PRIORITY & "'; " & _
            "$p.ProcessorAffinity = " & CStr(BOOST_AFFINITY_ALL)

    runCmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command " & Chr(34) & psCmd & Chr(34)

    sh.Run runCmd, 0, False
    WScript.Quit
End Sub


' ============================================================
' NuCasa Boot Production 2nd Run
' COM4 Pre-Handshake + real API readiness + all-zone playback handoff
' ============================================================

Const COM_PORT = "COM4"
Const BAUDRATE = "115200"

Const CasaTunes_Svc  = "CasaTunesSvc"
Const ZONES_URL       = "http://127.0.0.1:8735/api/v1/zones"
Const ZONE_STATUS_URL = "http://127.0.0.1:8735/api/v1/zones/0/status"
Const SOURCES_URL     = "http://127.0.0.1:8735/api/v1/sources/nowplaying"

' ------------------------------------------------------------
' COM4 cold boot pre-handshake
' ------------------------------------------------------------
Const COM_PREWAKE_ENABLE          = True
Const COM_READY_MAX_WAIT_SEC      = 60
Const COM_PREWAKE_RETRY_MS        = 1000
Const COM_PREWAKE_SUCCESS_PULSES  = 4
Const COM_PREWAKE_PULSE_MS        = 120

' ------------------------------------------------------------
' Source 5/6 readiness and serial pacing. Source 5 is required for the present
' system. Source 6 is optional: it receives BootPhase only when the Grand
' Concerto reports that source enabled and ready.
' ------------------------------------------------------------
Const CRITICAL_SOURCE                  = 5
Const SOURCE_READY_GATE_TIMEOUT_SEC    = 5
Const SOURCE6_READY_GATE_TIMEOUT_SEC   = 3
Const SOURCE_WRITE_GAP_MS            = 120
Const SOURCE_VERIFY_RETRY_COUNT      = 3
Const SOURCE_VERIFY_RETRY_MS         = 300

' Release BootPhase COM writes before the external Translator starts.
' The script may keep polling metadata after release, but every serial write
' helper becomes a no-op once this bounded handoff has occurred.
Const WRITER_EXCLUSIVE_MAX_SEC       = 160

' ------------------------------------------------------------
' Final COM safety trigger
' ------------------------------------------------------------
Const FINAL_COM_SAFETY_ENABLE     = True
Const FINAL_COM_SAFETY_PULSE_MS   = 80

' ------------------------------------------------------------
' Timing
' ------------------------------------------------------------
Const SHOW_IP_FROM_SEC         = 40
Const IP_SHOW_END_SEC         = 60

Const READY_COUNTDOWN_SEC      = 0
Const PREPARING_DISPLAY_HOLD_SEC = 5
Const WAITING_DISPLAY_HOLD_SEC = 5
Const WAITING_EXTRA_SEC        = 0
Const WELCOME_SHOW_SEC         = 6
Const WELCOME_NAME_WAIT_SEC    = 10
Const RUNTIME_STATUS_URL       = "http://127.0.0.1:8787/status"
Const MAX_RUNTIME_SEC          = 105
Const METADATA_EXIT_DELAY_SEC  = 8
Const STATUS_READY_HOLD_SEC    = 7
Const COMPONENT_POLL_SEC       = 1

' Temporary display-card switches. These skip only the visible BootPhase card;
' the underlying network and CasaTunes readiness checks continue to run.
Const SHOW_STARTUP_CARD_5      = True
Const SHOW_STARTUP_CARD_6      = False
Const SHOW_STARTUP_CARD_7      = False

Const MASK_START_SEC           = 70
Const MASK_END_SEC             = 85

Const NORMAL_SLEEP_MS          = 200
Const MASK_SLEEP_MS            = 150

' ------------------------------------------------------------
' Line5 / MSG
' ------------------------------------------------------------
Const MAC_SHOW_START_SEC       = 40
Const MAC_SHOW_END_SEC         = 70
Const MAC_PREFIX_TEXT          = "This Server MAC : "
Const MAC_MSG_RESEND_SEC       = 4

Const LICENSE_MSG_PREFIX = "Licensed by CasaTunes - Powered by Rose Music Taiwan "

Const METADATA_POLL_INTERVAL_SEC = 3

' ------------------------------------------------------------
' Globals
' ------------------------------------------------------------
Dim shell
Dim gWmiService
Dim gWmiProcess
Dim gWmiStartupClass
Dim gWmiStartup
Dim SOURCE_LIST
Dim FINAL_COM_LIST
Dim gStartTick
Dim welcomeStartTick
Dim welcomeResolvedName
Dim welcomeNameLastPollTick
Dim preparingDisplayStartTick

Dim curLine1
Dim curLine2
Dim curLine3
Dim curLine4

Dim comReady
Dim svcReadyTriggered
Dim apiReadyTriggered
Dim metadataDetected
Dim metadataTick
Dim gLastMetadataCheckTick
Dim metadataApiReadable

Dim translatorReady
Dim imageProxyReady
Dim runtimeReady
Dim networkReady
Dim gLastComponentCheckTick
Dim startupStatusStage
Dim startupStatusReported
Dim startupStatusTick
Dim startupStageWaiting
Dim startupStageWaitTick
Dim ipPriorityActive

Dim msgStopped
Dim lastMacMsgTick
Dim macWindowActive

Dim countdownActive
Dim countdownRemainSec
Dim countdownNextTick
Dim countdownCompleted

Dim finalComSafetyTriggered

Dim gCachedIP
Dim gIPInitialized

Dim gCachedMAC
Dim gMacInitialized

Dim gInstanceFso
Dim gInstanceLockPath
Dim gInstanceLockOwned
Dim gBootPhaseLogPath
Dim gWriterHandoffPath
Dim gSourceHelperPath
Dim gS5RecoveryFlagPath
Dim gWriterReleased
Dim sourceGateRC
Dim source5Ready
Dim source6GateRC
Dim source6Ready

Set shell = CreateObject("WScript.Shell")
Set gInstanceFso = CreateObject("Scripting.FileSystemObject")
gInstanceLockPath = gInstanceFso.BuildPath(shell.ExpandEnvironmentStrings("%TEMP%"), _
    "NuCasa_Boot_Production_2nd_Run.lock")
gBootPhaseLogPath = "C:\NuvoCasaTools\Logs\Startup\NuCasa_Boot_Production_2nd_Run.log"
gWriterHandoffPath = "C:\NuvoCasaTools\Logs\Startup\NuCasa_BootPhase_Writer_Released.flag"
gS5RecoveryFlagPath = "C:\NuvoCasaTools\Logs\Startup\NuCasa_S5_GrandConcerto_Restart_Required.flag"
gSourceHelperPath = gInstanceFso.BuildPath( _
    gInstanceFso.GetParentFolderName(WScript.ScriptFullName), _
    "NuCasa_Source_Readiness.ps1")
gInstanceLockOwned = False
gWriterReleased = False
Call ClearStaleWriterHandoff()
If AcquireInstanceLock() = False Then
    WScript.Quit 10
End If
On Error Resume Next
Set gWmiService = GetObject("winmgmts:\\.\root\cimv2")
Set gWmiProcess = gWmiService.Get("Win32_Process")
Set gWmiStartupClass = gWmiService.Get("Win32_ProcessStartup")
Set gWmiStartup = gWmiStartupClass.SpawnInstance_
gWmiStartup.ShowWindow = 0
On Error GoTo 0
' BootPhase is the exclusive COM writer until handoff. S1-S4 are always sent;
' S5/S6 are filtered by their live readiness result inside the write loop.
SOURCE_LIST = Array(1, 2, 3, 4, 5, 6)
FINAL_COM_LIST = Array("COM4")

gStartTick = Timer

svcReadyTriggered = False
apiReadyTriggered = False
metadataDetected = False
metadataTick = 0
gLastMetadataCheckTick = -9999
metadataApiReadable = False

translatorReady = False
imageProxyReady = False
runtimeReady = False
networkReady = False
gLastComponentCheckTick = -9999
startupStatusStage = 1
startupStatusReported = False
startupStatusTick = 0
startupStageWaiting = True
startupStageWaitTick = Timer
ipPriorityActive = False

msgStopped = False
lastMacMsgTick = -1
macWindowActive = False

countdownActive = False
countdownRemainSec = 0
countdownNextTick = 0
countdownCompleted = False

finalComSafetyTriggered = False

gCachedIP = ""
gIPInitialized = False

gCachedMAC = ""
gMacInitialized = False

curLine1 = ""
curLine2 = ""
curLine3 = ""
curLine4 = ""

' COM4 is the mandatory gate for every keypad screen. Do not continue on a
' fixed Task Scheduler delay: wait dynamically until Windows can configure,
' wake and re-open COM4. A failed gate exits without running later displays.
comReady = WaitForComReady()
If comReady = False Then
    Call QuitWithCleanup(2)
End If

sourceGateRC = RunSourceReadinessGate(CRITICAL_SOURCE, SOURCE_READY_GATE_TIMEOUT_SEC)
If sourceGateRC = 0 Then
    source5Ready = True
    Call ClearS5RecoveryFlag()
    Call WriteBootLog("PASS Source 5 readiness gate; BootPhase display enabled")
Else
    source5Ready = False
    Call SetS5RecoveryFlag("Source 5 remained inactive after CasaTunes startup; Grand Concerto re-enumeration required")
    ' Repeating DISPLINES did not change ACTIVE0 in the V1 target-host test.
    ' Preserve S1-S4 BootPhase and do not overwrite the inactive S5 display.
    Call WriteBootLog("WARN Source 5 readiness gate rc=" & CStr(sourceGateRC) & _
        "; skipping Source 5 BootPhase writes")
End If

' Source 6 is deliberately optional because it is disabled in the present
' CasaTunes configuration. Detect it briefly and never let it fail the boot.
source6GateRC = RunSourceReadinessGate(6, SOURCE6_READY_GATE_TIMEOUT_SEC)
If source6GateRC = 0 Then
    source6Ready = True
    Call WriteBootLog("PASS Source 6 readiness gate; BootPhase display enabled")
Else
    source6Ready = False
    Call WriteBootLog("INFO Source 6 unavailable/disabled rc=" & CStr(source6GateRC) & _
        "; BootPhase display skipped without failing startup")
End If

Call InitDisplay()

' Confirm that COM4 remained available after the first display write.
' If the port changed during cold boot, run the same bounded gate once more.
If IsComPortReady() = False Then
    comReady = WaitForComReady()
    If comReady = False Then
        Call QuitWithCleanup(3)
    End If
    Call InitDisplay()
End If

' Start the welcome timer only after COM4 is ready and the first display
' initialization has completed. This keeps the full duration visible.
welcomeStartTick = Timer
welcomeResolvedName = ""
welcomeNameLastPollTick = -1
preparingDisplayStartTick = -1

Do
    Dim t
    t = ElapsedSec()

    If gWriterReleased = False And t >= WRITER_EXCLUSIVE_MAX_SEC Then
        Call ReleaseComWriter("absolute 160-second safety handoff")
    End If

    If t >= MAX_RUNTIME_SEC Then
        Call QuitWithCleanup(0)
    End If

    If t < MAC_SHOW_START_SEC Then

        Call SendLicenseMsg()
        macWindowActive = False
        lastMacMsgTick = -1

    ElseIf t >= MAC_SHOW_START_SEC And t < MAC_SHOW_END_SEC Then

        If macWindowActive = False Then
            Call GetLocalMAC()
            Call SendCustomMsg(MAC_PREFIX_TEXT & gCachedMAC)
            lastMacMsgTick = t
            macWindowActive = True

        ElseIf (t - lastMacMsgTick) >= MAC_MSG_RESEND_SEC Then
            Call SendCustomMsg(MAC_PREFIX_TEXT & gCachedMAC)
            lastMacMsgTick = t
        End If

    Else

        If msgStopped = False Then
            Call ClearMsgArea()
            msgStopped = True
        End If

    End If

    If svcReadyTriggered = False Then
        If IsCasaTunesServiceRunning() Then
            svcReadyTriggered = True
        End If
    End If

    ' Synchronize the keypad with real component readiness. Poll only once per
    ' second, and stop checking each component after it becomes ready.
    If (t - gLastComponentCheckTick) >= COMPONENT_POLL_SEC Then
        gLastComponentCheckTick = t
        If translatorReady = False Then
            translatorReady = ProcessRunning("NuCasa Translator.exe")
        End If
        If networkReady = False Then
            networkReady = IsNetworkConnected()
        End If
        If imageProxyReady = False Then
            imageProxyReady = PortListening(8765)
        End If
        If runtimeReady = False Then
            runtimeReady = PortListening(8787)
        End If
    End If

    If svcReadyTriggered = True And apiReadyTriggered = False Then
        If IsCasaTunesApiReady() Then
            apiReadyTriggered = True
            countdownActive = True
            countdownRemainSec = READY_COUNTDOWN_SEC
            countdownNextTick = Timer
        End If
    End If

    ' Metadata ownership must not depend on the strict API-ready flag.
    ' Once CasaTunesSvc is running, poll the real zone/source metadata so the
    ' first song can always stop BootPhase display writes.
    ' The dedicated preparing screen is intentionally placed before metadata
    ' ownership begins. Once that bounded stage completes, metadata resumes
    ' its original highest priority.
    If svcReadyTriggered = True And startupStatusStage > 9 Then
        If metadataDetected = False Then
            If (t - gLastMetadataCheckTick) >= METADATA_POLL_INTERVAL_SEC Then
                gLastMetadataCheckTick = t
                If IsMetadataActive() Then
                    metadataDetected = True
                    metadataTick = Timer
                    Call ReleaseComWriter("metadata ownership detected")
                End If
            End If
        End If
    End If

    If metadataDetected = True Then

        If TimerDiff(metadataTick, Timer) >= METADATA_EXIT_DELAY_SEC Then
            Call QuitWithCleanup(0)
        Else
            WScript.Sleep 100
        End If

    Else

        Call RenderMain(t)

        If t >= MASK_START_SEC And t < MASK_END_SEC Then
            WScript.Sleep MASK_SLEEP_MS
        Else
            WScript.Sleep NORMAL_SLEEP_MS
        End If

    End If

Loop

' ============================================================
' Main render
' ============================================================

Sub RenderMain(t)

    ' Show the startup welcome before the normal component/IP sequence.
    ' The name is read from Runtime status so HTML, PWA and Translator stay
    ' synchronized. If Runtime is not ready yet, use the safe Guest fallback.
    ' Keep the normal six-second welcome duration. If Runtime status is still
    ' unavailable, extend only the name-resolution window to twelve seconds so
    ' a valid HTML/PWA rename can replace Guest without waiting indefinitely.
    If TimerDiff(welcomeStartTick, Timer) < WELCOME_SHOW_SEC Or _
       (welcomeResolvedName = "" And TimerDiff(welcomeStartTick, Timer) < WELCOME_NAME_WAIT_SEC) Then
        If welcomeResolvedName = "" Then
            If welcomeNameLastPollTick < 0 Or TimerDiff(welcomeNameLastPollTick, Timer) >= 1 Then
                welcomeNameLastPollTick = Timer
                Dim resolvedName
                resolvedName = GetEndUserName()
                If LCase(Trim(resolvedName)) <> "guest" And Len(Trim(resolvedName)) > 0 Then
                    welcomeResolvedName = Trim(resolvedName)
                End If
            End If
        End If
        curLine1 ="      Welcome ! ... "
        If welcomeResolvedName = "" Then
            curLine2 = ""
        Else
            curLine2 = "Hello. " & welcomeResolvedName
        End If
        curLine3 ="      NuCasa "
        curLine4 = ""
        Call SendAllSourcesAbortable()
        Exit Sub
    End If

    ' The device IP owns the keypad from 60 through 80 seconds. This display
    ' temporarily pauses the current component status (normally CasaTunesSvc).
    If t >= SHOW_IP_FROM_SEC And t < IP_SHOW_END_SEC Then
        ipPriorityActive = True
        curLine1 = " Server IP address "
        curLine2 =GetLocalIP()
        curLine3 ="      NuCasa "
        curLine4 =""
        Call SendAllSourcesAbortable()
        Exit Sub
    End If

    ' Resume the interrupted component after the IP window. Restart its Ready
    ' hold so CasaTunesSvc remains visible for the complete three seconds.
    If ipPriorityActive = True Then
        ipPriorityActive = False
        If startupStatusReported = True Then
            startupStatusTick = Timer
        End If
    End If

    ' Report each real startup result in order. Every completed component stays
    ' on the keypad for three seconds before the next check is shown.
    If startupStatusStage <= 8 Then
        Call RenderStartupStatus()
        Exit Sub
    End If

    If countdownCompleted = False Then
        Call ShowCountdown(t)
        Exit Sub
    End If

    If FINAL_COM_SAFETY_ENABLE Then
        If finalComSafetyTriggered = False Then
            Call TriggerAllComPortsOnce()
            finalComSafetyTriggered = True
        End If
    End If

    ' Reserve the second bounded startup screen before metadata ownership is
    ' released. Preparing and waiting therefore receive a full five seconds
    ' each, without changing or skipping any earlier startup stage.
    Call ShowWaitingWindow(WAITING_DISPLAY_HOLD_SEC + WAITING_EXTRA_SEC)

    ' The keypad retains the last screen. Exit after the bounded window so the
    ' Translator/CasaTunes metadata owner can immediately take over; when no
    ' metadata exists, the waiting screen remains on the keypad.
    Call QuitWithCleanup(0)

End Sub

Sub ShowWaitingWindow(holdSeconds)
    Dim startedAt

    startedAt = Timer

    Do
        ' This is the second half of the explicit BootPhase display window.
        ' Metadata ownership is intentionally released only after this bounded
        ' waiting screen completes.
        curLine1 = "Ready"
        curLine2 = "Please wait a moment for automatic playback or start playback manually if needed"
        curLine3 = "      NuCasa "
        curLine4 = ""
        Call SendAllSourcesAbortable()

        If TimerDiff(startedAt, Timer) >= holdSeconds Then Exit Do
        WScript.Sleep 1000
    Loop

    startupStatusStage = 10
End Sub

' ============================================================
' Real synchronized startup status
' ============================================================

Sub RenderStartupStatus()

    Dim componentName
    Dim componentReady

    componentName = ""
    componentReady = False

    ' Insert the preparation message only after every synchronized startup
    ' result, including the CasaTunes license stage, has completed. Metadata
    ' ownership begins after this bounded screen so License cannot remain as
    ' the final stale keypad display.
    If startupStatusStage = 8 Then
        If preparingDisplayStartTick < 0 Then preparingDisplayStartTick = Timer

        If TimerDiff(preparingDisplayStartTick, Timer) < PREPARING_DISPLAY_HOLD_SEC Then
            Call ShowScreen("Ready", "NuCasa is preparing to resume playback..")
            Exit Sub
        End If

        startupStatusStage = 9
        startupStatusReported = False
        startupStageWaiting = True
        startupStageWaitTick = Timer
        Exit Sub
    End If

    ' Skip disabled cards cleanly without rendering an empty frame.
    If startupStatusStage = 6 And SHOW_STARTUP_CARD_6 = False Then
        startupStatusStage = 7
        startupStatusReported = False
        startupStageWaiting = True
        startupStageWaitTick = Timer
        Exit Sub
    End If

    If startupStatusStage = 7 And SHOW_STARTUP_CARD_7 = False Then
        startupStatusStage = 8
        startupStatusReported = False
        startupStageWaiting = True
        startupStageWaitTick = Timer
        Exit Sub
    End If

    Select Case startupStatusStage
        Case 1
            componentName = "Initialization Grand.Concerto COM Port"
            componentReady = comReady
        Case 2
            componentName = "Initialization for NuCasa Runtime Server UI"
            componentReady = runtimeReady
        Case 3
            componentName = "Launching Image Album Art Proxy"
            componentReady = imageProxyReady
        Case 4
            componentName = "Translator queued after BootPhase handoff"
            componentReady = True
        Case 5
            componentName = "NuVo MPS4v2 and CasaTunesSvc Integration.."
            componentReady = svcReadyTriggered
        Case 6
            componentName = "Launching NuVo Attero 4 Source Initialization ..."
            componentReady = svcReadyTriggered
        Case 7
            componentName = "CasaTunesX License Activated "
            componentReady = svcReadyTriggered
    End Select

    ' Every stage must enter a visible waiting state first, even when the
    ' component was already ready before this stage became visible.
    If startupStageWaiting = True Then
        If TimerDiff(startupStageWaitTick, Timer) < COMPONENT_POLL_SEC Then
            Call ShowScreen("Please Waiting" & BuildDots(), componentName)
            Exit Sub
        End If
        startupStageWaiting = False
    End If

    If componentReady = False Then
        Call ShowScreen("Please Waiting" & BuildDots(), componentName)
        Exit Sub
    End If

    If startupStatusReported = False Then
        startupStatusReported = True
        startupStatusTick = Timer
    End If

    If TimerDiff(startupStatusTick, Timer) < STATUS_READY_HOLD_SEC Then
        Call ShowScreen("Ready", componentName)
        Exit Sub
    End If

    startupStatusStage = startupStatusStage + 1
    startupStatusReported = False
    startupStageWaiting = True
    startupStageWaitTick = Timer

End Sub

' ============================================================
' Countdown
' ============================================================

Sub ShowCountdown(t)

    If countdownActive = False Then
        countdownActive = True
        countdownRemainSec = READY_COUNTDOWN_SEC
        countdownNextTick = Timer
    End If

    If TimerDiff(countdownNextTick, Timer) >= 1 Then
        countdownRemainSec = countdownRemainSec - 1
        countdownNextTick = Timer
    End If

    If countdownRemainSec <= 0 Then
        countdownActive = False
        countdownCompleted = True
        Exit Sub
    End If

    curLine1 = "System Ready " & CStr(countdownRemainSec)
    curLine2 = ""
    curLine3 ="      NuCasa "
    curLine4 = ""

    Call SendAllSourcesAbortable()

End Sub

' ============================================================
' Display layout
' ============================================================

Sub ShowScreen(line1Text, line2Text)
    curLine1 = line1Text
    curLine2 = line2Text
    curLine3 ="      NuCasa "
    curLine4 = ""
    Call SendAllSourcesAbortable()
End Sub

' ============================================================
' Metadata detection
' ============================================================

Function IsMetadataActive()
    On Error Resume Next

    Dim zonesText
    Dim sourcesText
    Dim zoneRegex
    Dim zoneMatches
    Dim zoneMatch
    Dim sourceID

    metadataApiReadable = False
    zonesText = HttpGetText(ZONES_URL)
    sourcesText = HttpGetText(SOURCES_URL)

    If Len(zonesText) = 0 Or Len(sourcesText) = 0 Then
        IsMetadataActive = False
        Exit Function
    End If

    metadataApiReadable = True

    ' CasaTunes builds do not always serialize Power and SourceID in the same
    ' order. Check both valid field orders for a powered zone.
    Set zoneRegex = New RegExp
    zoneRegex.Global = True
    zoneRegex.IgnoreCase = True
    zoneRegex.Pattern = """Power""\s*:\s*true[\s\S]{0,500}?""SourceID""\s*:\s*(\d+)"
    Set zoneMatches = zoneRegex.Execute(zonesText)

    For Each zoneMatch In zoneMatches
        sourceID = CStr(zoneMatch.SubMatches(0))
        If SourceMetadataActive(sourcesText, sourceID) Then
            IsMetadataActive = True
            Exit Function
        End If
    Next

    Set zoneRegex = New RegExp
    zoneRegex.Global = True
    zoneRegex.IgnoreCase = True
    zoneRegex.Pattern = """SourceID""\s*:\s*(\d+)[\s\S]{0,500}?""Power""\s*:\s*true"
    Set zoneMatches = zoneRegex.Execute(zonesText)

    For Each zoneMatch In zoneMatches
        sourceID = CStr(zoneMatch.SubMatches(0))
        If SourceMetadataActive(sourcesText, sourceID) Then
            IsMetadataActive = True
            Exit Function
        End If
    Next

    IsMetadataActive = False
End Function

Function SourceMetadataActive(sourcesText, sourceID)
    Dim sourceRegex

    SourceMetadataActive = False

    ' Common order: SourceID, Status, CurrSong, Title.
    Set sourceRegex = New RegExp
    sourceRegex.Global = False
    sourceRegex.IgnoreCase = True
    sourceRegex.Pattern = """SourceID""\s*:\s*" & sourceID & _
        "[\s\S]{0,1200}?""Status""\s*:\s*2" & _
        "[\s\S]{0,2600}?""CurrSong""\s*:\s*\{" & _
        "[\s\S]{0,1800}?""Title""\s*:\s*""[^""]+"""
    If sourceRegex.Test(sourcesText) Then
        SourceMetadataActive = True
        Exit Function
    End If

    ' Alternate order used by some CasaTunes responses: song before Status.
    Set sourceRegex = New RegExp
    sourceRegex.Global = False
    sourceRegex.IgnoreCase = True
    sourceRegex.Pattern = """SourceID""\s*:\s*" & sourceID & _
        "[\s\S]{0,2600}?""CurrSong""\s*:\s*\{" & _
        "[\s\S]{0,1800}?""Title""\s*:\s*""[^""]+""" & _
        "[\s\S]{0,1800}?""Status""\s*:\s*2"
    If sourceRegex.Test(sourcesText) Then
        SourceMetadataActive = True
    End If
End Function

Function IsCasaTunesApiReady()
    On Error Resume Next

    Dim statusText
    statusText = HttpGetText(ZONE_STATUS_URL)

    If Len(statusText) = 0 Then
        IsCasaTunesApiReady = False
        Exit Function
    End If

    If InStr(1, statusText, """Initialized"":true", vbTextCompare) > 0 And _
       InStr(1, statusText, """TaskCompleted"":true", vbTextCompare) > 0 And _
       InStr(1, statusText, """LicenseValid"":true", vbTextCompare) > 0 Then
        IsCasaTunesApiReady = True
    Else
        IsCasaTunesApiReady = False
    End If
End Function

Function HttpGetText(url)
    On Error Resume Next

    Dim http
    Set http = CreateObject("MSXML2.XMLHTTP")
    http.Open "GET", url, False
    http.setRequestHeader "Cache-Control", "no-cache"
    http.Send

    If Err.Number <> 0 Then
        Err.Clear
        HttpGetText = ""
        Exit Function
    End If

    If http.Status = 200 Then
        HttpGetText = CStr(http.responseText)
    Else
        HttpGetText = ""
    End If
End Function

Function GetEndUserName()

    Dim statusText
    Dim re
    Dim matches

    GetEndUserName = "Guest"
    statusText = HttpGetText(RUNTIME_STATUS_URL)

    If Len(statusText) = 0 Then Exit Function

    Set re = New RegExp
    re.Global = False
    re.IgnoreCase = True
    re.Pattern = """end_user_name""\s*:\s*""([^""]*)"""

    Set matches = re.Execute(statusText)

    If matches.Count > 0 Then
        If Len(Trim(matches(0).SubMatches(0))) > 0 Then
            GetEndUserName = Trim(matches(0).SubMatches(0))
        End If
    End If

End Function

' ============================================================
' MSG / Line5
' ============================================================

Sub SendLicenseMsg()
    ' Use the Win7 system calendar year so the license line remains current
    ' after New Year without another BootPhase code change.
    Call SendSerial(BuildMsgCommand(LICENSE_MSG_PREFIX & CStr(Year(Date))))
End Sub

Sub SendCustomMsg(msgText)
    Call SendSerial(BuildMsgCommand(msgText))
End Sub

Sub ClearMsgArea()
    Call SendSerial(BuildMsgCommand(""))
End Sub

Function BuildMsgCommand(msgText)
    BuildMsgCommand = "*MSG" & Q(SafeText(msgText))
End Function

' ============================================================
' Helpers
' ============================================================

Function ElapsedSec()
    ElapsedSec = TimerDiff(gStartTick, Timer)
End Function

Function TimerDiff(startTick, endTick)
    Dim d
    d = endTick - startTick
    If d < 0 Then
        d = d + 86400
    End If
    TimerDiff = d
End Function

Sub WriteBootLog(text)
    On Error Resume Next

    Dim stream
    Set stream = gInstanceFso.OpenTextFile(gBootPhaseLogPath, 8, True)
    If Err.Number = 0 Then
        stream.WriteLine Now & "  " & text
        stream.Close
    End If
    Err.Clear
    On Error GoTo 0
End Sub

Sub ClearStaleWriterHandoff()
    On Error Resume Next
    If gInstanceFso.FileExists(gWriterHandoffPath) Then
        gInstanceFso.DeleteFile gWriterHandoffPath, True
    End If
    Err.Clear
    On Error GoTo 0
End Sub

Sub ReleaseComWriter(reason)
    On Error Resume Next

    Dim stream
    If gWriterReleased = True Then Exit Sub

    gWriterReleased = True
    Set stream = gInstanceFso.OpenTextFile(gWriterHandoffPath, 2, True)
    If Err.Number = 0 Then
        stream.WriteLine Now & "  " & CStr(reason)
        stream.Close
    End If
    Err.Clear
    On Error GoTo 0

    Call WriteBootLog("COM writer released: " & CStr(reason))
End Sub

Function RunSourceReadinessGate(sourceNumber, timeoutSeconds)
    Dim command
    Dim rc

    If gInstanceFso.FileExists(gSourceHelperPath) = False Then
        Call WriteBootLog("ERROR source readiness helper missing: " & gSourceHelperPath)
        RunSourceReadinessGate = 40
        Exit Function
    End If

    command = "powershell.exe -NoProfile -ExecutionPolicy Bypass " & _
        "-WindowStyle Hidden -File " & Chr(34) & gSourceHelperPath & Chr(34) & _
        " -Mode Gate -PortName " & COM_PORT & _
        " -BaudRate " & BAUDRATE & _
        " -TimeoutSec " & CStr(timeoutSeconds) & _
        " -Source " & CStr(sourceNumber)

    rc = shell.Run(command, 0, True)
    RunSourceReadinessGate = rc
End Function

Sub SetS5RecoveryFlag(reason)
    On Error Resume Next
    Dim stream
    Set stream = gInstanceFso.OpenTextFile(gS5RecoveryFlagPath, 2, True)
    If Err.Number = 0 Then
        stream.WriteLine Now & "  " & CStr(reason)
        stream.Close
    End If
    Err.Clear
    On Error GoTo 0
End Sub

Sub ClearS5RecoveryFlag()
    On Error Resume Next
    If gInstanceFso.FileExists(gS5RecoveryFlagPath) Then
        gInstanceFso.DeleteFile gS5RecoveryFlagPath, True
    End If
    Err.Clear
    On Error GoTo 0
End Sub

Function VerifyCriticalSource(src)
    Dim command
    Dim rc

    VerifyCriticalSource = False
    If gInstanceFso.FileExists(gSourceHelperPath) = False Then Exit Function

    command = "powershell.exe -NoProfile -ExecutionPolicy Bypass " & _
        "-WindowStyle Hidden -File " & Chr(34) & gSourceHelperPath & Chr(34) & _
        " -Mode Verify -PortName " & COM_PORT & _
        " -BaudRate " & BAUDRATE & _
        " -Source " & CStr(src) & _
        " -ExpectedText NuCasa"

    rc = shell.Run(command, 0, True)
    VerifyCriticalSource = (rc = 0)
End Function

Sub VerifyAndRetryCriticalSource(src)
    Dim attempt

    For attempt = 1 To SOURCE_VERIFY_RETRY_COUNT
        If VerifyCriticalSource(src) Then
            Call WriteBootLog("PASS Source " & CStr(src) & _
                " display read-back attempt=" & CStr(attempt))
            Exit Sub
        End If

        Call WriteBootLog("RETRY Source " & CStr(src) & _
            " display read-back attempt=" & CStr(attempt))
        Call SendDisplay(src)
        WScript.Sleep SOURCE_VERIFY_RETRY_MS
    Next

    Call WriteBootLog("FAILED Source " & CStr(src) & _
        " display read-back after bounded retries")
End Sub

' ============================================================
' Single-instance ownership and clean exit
' ============================================================

Function AcquireInstanceLock()
    Dim matchingCount
    Dim lockFolder

    AcquireInstanceLock = False

    If gInstanceFso.FolderExists(gInstanceLockPath) Then
        matchingCount = CountBoostedScriptInstances()

        ' More than one matching process means another boosted BootPhase owns
        ' COM4. A negative result means WMI could not safely verify ownership.
        If matchingCount > 1 Or matchingCount < 0 Then
            Exit Function
        End If

        ' Only this process is present, so the directory is stale from an
        ' earlier crash or reboot and can be recovered safely.
        On Error Resume Next
        gInstanceFso.DeleteFolder gInstanceLockPath, True
        If Err.Number <> 0 Then
            Err.Clear
            On Error GoTo 0
            Exit Function
        End If
        On Error GoTo 0
    End If

    On Error Resume Next
    Set lockFolder = gInstanceFso.CreateFolder(gInstanceLockPath)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo 0
        Exit Function
    End If
    On Error GoTo 0

    gInstanceLockOwned = True
    AcquireInstanceLock = True
End Function

Function CountBoostedScriptInstances()
    Dim svc
    Dim processes
    Dim process
    Dim commandLine
    Dim scriptPath
    Dim count

    CountBoostedScriptInstances = -1
    count = 0
    scriptPath = LCase(CStr(WScript.ScriptFullName))

    On Error Resume Next
    Set svc = GetObject("winmgmts:\\.\root\cimv2")
    Set processes = svc.ExecQuery( _
        "SELECT CommandLine FROM Win32_Process " & _
        "WHERE Name='wscript.exe' OR Name='cscript.exe'")
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo 0
        Exit Function
    End If

    For Each process In processes
        If Not IsNull(process.CommandLine) Then
            commandLine = LCase(CStr(process.CommandLine))
            If InStr(1, commandLine, scriptPath, vbTextCompare) > 0 And _
               InStr(1, commandLine, "--boosted", vbTextCompare) > 0 Then
                count = count + 1
            End If
        End If
    Next
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo 0
        Exit Function
    End If
    On Error GoTo 0

    CountBoostedScriptInstances = count
End Function

Sub QuitWithCleanup(exitCode)
    On Error Resume Next
    Call ReleaseComWriter("BootPhase exit code " & CStr(exitCode))
    If gInstanceLockOwned = True Then
        If gInstanceFso.FolderExists(gInstanceLockPath) Then
            gInstanceFso.DeleteFolder gInstanceLockPath, True
        End If
        gInstanceLockOwned = False
    End If
    On Error GoTo 0
    WScript.Quit exitCode
End Sub

Function BuildDots()
    Dim n
    n = (Int(ElapsedSec()) Mod 4) + 1

    If n = 1 Then
        BuildDots = "."
    ElseIf n = 2 Then
        BuildDots = ".."
    ElseIf n = 3 Then
        BuildDots = "..."
    ElseIf n = 4 Then
        BuildDots = "...."
    End If
End Function

Function IsCasaTunesServiceRunning()
    IsCasaTunesServiceRunning = IsServiceRunning(CasaTunes_Svc)
End Function

Function IsServiceRunning(serviceName)
    On Error Resume Next

    Dim serviceWMI
    Dim services
    Dim svc

    IsServiceRunning = False
    Set serviceWMI = GetObject("winmgmts:\\.\root\cimv2")
    Set services = serviceWMI.ExecQuery("SELECT State FROM Win32_Service WHERE Name='" & Replace(serviceName, "'", "''") & "'")

    For Each svc In services
        IsServiceRunning = (UCase(Trim(CStr(svc.State))) = "RUNNING")
        Exit Function
    Next
End Function

Function ProcessRunning(imageName)
    On Error Resume Next

    Dim processes

    ProcessRunning = False
    Set processes = gWmiService.ExecQuery( _
        "SELECT ProcessId FROM Win32_Process WHERE Name='" & _
        Replace(CStr(imageName), "'", "''") & "'")

    If Err.Number <> 0 Then
        Err.Clear
        Exit Function
    End If

    ProcessRunning = (processes.Count > 0)
End Function

Function PortListening(portNumber)
    Dim command
    Dim rc

    command = "%ComSpec% /c netstat -ano | findstr /R /C:"":" & _
              CStr(portNumber) & " .*LISTENING"" >nul"
    rc = shell.Run(command, 0, True)
    PortListening = (rc = 0)
End Function

Function IsNetworkConnected()
    On Error Resume Next

    Dim adapters
    Dim adapter
    Dim address

    IsNetworkConnected = False
    Set adapters = gWmiService.ExecQuery( _
        "SELECT IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=True")

    If Err.Number <> 0 Then
        Err.Clear
        Exit Function
    End If

    For Each adapter In adapters
        If Not IsNull(adapter.IPAddress) Then
            For Each address In adapter.IPAddress
                address = Trim(CStr(address))
                If InStr(address, ".") > 0 And _
                   Left(address, 4) <> "127." And _
                   Left(address, 8) <> "169.254." And _
                   address <> "0.0.0.0" Then
                    IsNetworkConnected = True
                    Exit Function
                End If
            Next
        End If
    Next
End Function

' ============================================================
' IP / MAC
' ============================================================

Function GetLocalIP()

    If gIPInitialized = True Then
        GetLocalIP = gCachedIP
        Exit Function
    End If

    Dim ip

    ip = GetLocalIPv4WMI()

    If Len(ip) = 0 Then
        ip = "No IP"
    End If

    gCachedIP = ip
    gIPInitialized = True
    GetLocalIP = gCachedIP

End Function

Function GetLocalIPv4WMI()
    On Error Resume Next

    Dim objWMI
    Dim colItems
    Dim objItem
    Dim ip

    Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
    Set colItems = objWMI.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")

    For Each objItem In colItems
        If Not IsNull(objItem.IPAddress) Then
            For Each ip In objItem.IPAddress
                If InStr(ip, ".") > 0 Then
                    GetLocalIPv4WMI = ip
                    Exit Function
                End If
            Next
        End If
    Next

    GetLocalIPv4WMI = ""
End Function

Function GetLocalIPv4FromIpconfig()
    On Error Resume Next

    Dim execObj
    Dim output
    Dim lines
    Dim i
    Dim line
    Dim re4
    Dim matches

    Set execObj = shell.Exec("cmd /c ipconfig")
    output = execObj.StdOut.ReadAll
    lines = Split(output, vbCrLf)

    Set re4 = CreateObject("VBScript.RegExp")
    re4.Pattern = "(\d{1,3}\.){3}\d{1,3}"
    re4.Global = False
    re4.IgnoreCase = True

    For i = 0 To UBound(lines)
        line = Trim(lines(i))
        If InStr(1, line, "IPv4", vbTextCompare) > 0 Or InStr(1, line, "IP Address", vbTextCompare) > 0 Then
            Set matches = re4.Execute(line)
            If matches.Count > 0 Then
                GetLocalIPv4FromIpconfig = matches(0).Value
                Exit Function
            End If
        End If
    Next

    GetLocalIPv4FromIpconfig = ""
End Function

Function GetLocalMAC()

    If gMacInitialized = True Then
        GetLocalMAC = gCachedMAC
        Exit Function
    End If

    On Error Resume Next

    Dim objWMI
    Dim colItems
    Dim objItem
    Dim macAddr

    Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
    Set colItems = objWMI.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")

    For Each objItem In colItems
        If Not IsNull(objItem.MACAddress) Then
            macAddr = Trim(CStr(objItem.MACAddress))
            If Len(macAddr) > 0 Then
                gCachedMAC = Replace(macAddr, ":", " ")
                gMacInitialized = True
                GetLocalMAC = gCachedMAC
                Exit Function
            End If
        End If
    Next

    gCachedMAC = "Not Found"
    gMacInitialized = True
    GetLocalMAC = gCachedMAC
End Function

' ============================================================
' COM / display
' ============================================================

Function RunComCommandHidden(commandLine)
    On Error Resume Next

    Dim processID
    Dim createRC
    Dim processes
    Dim startedAt

    processID = 0
    Err.Clear
    createRC = gWmiProcess.Create(commandLine, Null, gWmiStartup, processID)

    If Err.Number <> 0 Or createRC <> 0 Or processID = 0 Then
        Err.Clear
        RunComCommandHidden = shell.Run(commandLine, 0, True)
        On Error GoTo 0
        Exit Function
    End If

    startedAt = Timer
    Do
        Set processes = gWmiService.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE ProcessId=" & CStr(processID))
        If Err.Number <> 0 Then
            Err.Clear
            Exit Do
        End If
        If processes.Count = 0 Then Exit Do
        If TimerDiff(startedAt, Timer) >= 5 Then Exit Do
        WScript.Sleep 10
    Loop

    RunComCommandHidden = 0
    On Error GoTo 0
End Function

Sub ConfigComPort()
    Call RunComCommandHidden("cmd /c mode " & COM_PORT & ": BAUD=" & BAUDRATE & " PARITY=N DATA=8 STOP=1")
    WScript.Sleep 200
End Sub

Function NuvoDeviceResponding()
    On Error Resume Next

    Dim psCmd
    Dim command
    Dim rc

    psCmd = "$ErrorActionPreference='Stop';" & _
        "$p=New-Object System.IO.Ports.SerialPort('" & COM_PORT & "'," & BAUDRATE & ",'None',8,'One');" & _
        "$p.ReadTimeout=700;$p.WriteTimeout=700;$ok=$false;" & _
        "try{$p.Open();$p.DiscardInBuffer();" & _
        "$p.Write('*Z1STATUS?'+[char]13+[char]10);" & _
        "$until=[DateTime]::UtcNow.AddMilliseconds(1800);$buf='';" & _
        "while([DateTime]::UtcNow -lt $until){Start-Sleep -Milliseconds 100;" & _
        "$buf+=$p.ReadExisting();if($buf -match '#Z\d+,'){$ok=$true;break}}}" & _
        "finally{if($p.IsOpen){$p.Close()};$p.Dispose()};" & _
        "if($ok){exit 0}else{exit 2}"

    command = "powershell.exe -NoProfile -ExecutionPolicy Bypass " & _
        "-WindowStyle Hidden -Command " & Chr(34) & psCmd & Chr(34)
    rc = shell.Run(command, 0, True)
    NuvoDeviceResponding = (Err.Number = 0 And rc = 0)
    Err.Clear
    On Error GoTo 0
End Function

Function WaitForComReady()

    Dim ret
    Dim waitStarted

    WaitForComReady = False
    waitStarted = Timer

    Do
        ' Configure the complete serial profile on every retry because COM4
        ' may appear only after its virtual/USB driver finishes cold boot.
        ret = RunComCommandHidden("cmd /c mode " & COM_PORT & _
              ": BAUD=" & BAUDRATE & " PARITY=N DATA=8 STOP=1 >nul 2>nul")

        If ret = 0 And IsComPortReady() Then
            If COM_PREWAKE_ENABLE Then
                Call FastWakeCom4()
            End If

            WScript.Sleep 200
            If IsComPortReady() Then
                ' Strict device gate: only a real #Z... reply to *Z1STATUS?
                ' proves that VSPE is connected through to the NuVo equipment.
                If NuvoDeviceResponding() Then
                    WaitForComReady = True
                    Exit Function
                End If
            End If
        End If

        If TimerDiff(waitStarted, Timer) >= COM_READY_MAX_WAIT_SEC Then
            Exit Do
        End If

        WScript.Sleep COM_PREWAKE_RETRY_MS
    Loop

End Function

Function IsComPortReady()

    Dim ret
    ret = RunComCommandHidden("cmd /c mode " & COM_PORT & " >nul 2>nul")
    IsComPortReady = (ret = 0)

End Function

Sub FastWakeCom4()

    Dim i

    For i = 1 To COM_PREWAKE_SUCCESS_PULSES
        Call WakeSerial()
        WScript.Sleep COM_PREWAKE_PULSE_MS
    Next

End Sub

Sub WakeSerial()

    Dim cmd

    cmd = "cmd /c echo. > \\.\\" & COM_PORT
    Call RunComCommandHidden(cmd)

End Sub

Sub TriggerAllComPortsOnce()

    Dim p

    For Each p In FINAL_COM_LIST
        Call SafeWakeComPort(CStr(p))
        WScript.Sleep FINAL_COM_SAFETY_PULSE_MS
    Next

End Sub

Sub SafeWakeComPort(portName)

    On Error Resume Next

    Dim ret
    Dim cmd

    ret = RunComCommandHidden("cmd /c mode " & portName & " >nul 2>nul")

    If ret = 0 Then
        cmd = "cmd /c echo. > \\.\\" & portName
        Call RunComCommandHidden(cmd)
    End If

    On Error GoTo 0

End Sub

Sub InitDisplay()
    curLine1 = ""
    curLine2 = ""
    curLine3 ="      NuCasa "
    curLine4 = ""
    Call SendAllSourcesAbortable()
    If source5Ready = True Then
        Call VerifyAndRetryCriticalSource(5)
    Else
        Call WriteBootLog("SKIP Source 5 display initialization while ACTIVE0")
    End If
    If source6Ready = True Then
        Call VerifyAndRetryCriticalSource(6)
    Else
        Call WriteBootLog("SKIP Source 6 display initialization while disabled/not ready")
    End If
End Sub

Sub SendAllSourcesAbortable()
    Dim s
    Dim nowElapsed

    nowElapsed = ElapsedSec()

    If gWriterReleased = True Then Exit Sub

    For Each s In SOURCE_LIST

        If (CInt(s) = 5 And source5Ready = False) Or _
           (CInt(s) = 6 And source6Ready = False) Then
            ' Never send source-specific display commands to an inactive source.
        Else

            ' Recheck metadata only after the complete arranged startup flow.
            ' Until then BootPhase owns COM4 and Translator is not running.
            If svcReadyTriggered = True And startupStatusStage > 9 Then
                If metadataDetected = False Then
                    If (nowElapsed - gLastMetadataCheckTick) >= METADATA_POLL_INTERVAL_SEC Then
                        gLastMetadataCheckTick = nowElapsed
                        If IsMetadataActive() Then
                            metadataDetected = True
                            metadataTick = Timer
                            Call ReleaseComWriter("metadata ownership detected during source writes")
                            Exit Sub
                        End If
                    End If
                Else
                        Exit Sub
                End If
            End If

            Call SendDisplay(s)
            WScript.Sleep SOURCE_WRITE_GAP_MS
        End If
    Next
End Sub

Sub SendDisplay(src)
    Dim cmd

    If gWriterReleased = True Then Exit Sub

    cmd = "*S" & CStr(src) & "DISPLINES0,1,1," & _
          Q(SafeText(curLine1)) & "," & _
          Q(SafeText(curLine2)) & "," & _
          Q(SafeText(curLine3)) & "," & _
          Q(SafeText(curLine4))

    Call RunComCommandHidden("cmd /c (echo " & cmd & " & echo.) > \\.\\" & COM_PORT)
End Sub

Function Q(t)
    Q = Chr(34) & CStr(t) & Chr(34)
End Function

Function SafeText(t)
    SafeText = Replace(CStr(t), Chr(34), "'")
End Function

Sub SendSerial(str)
    Dim cmd
    If gWriterReleased = True Then Exit Sub
    cmd = "cmd /c (echo " & str & " & echo.) > \\.\\" & COM_PORT
    Call RunComCommandHidden(cmd)
End Sub
