param (
    [Parameter(Mandatory = $true)]
    [string]$vmPath,
    [Parameter(Mandatory = $true)]
    [string]$vmName,
    [Parameter(Mandatory = $true)]
    [string]$parentSystemVHDxTemplate,
    [Parameter(Mandatory = $false)]
    [int]$CPUCores = 4,
    [Parameter(Mandatory = $false)]
    [int]$ramSizeGB = 4,
    [Parameter(Mandatory = $true)]
    [string]$templatePassword,
    [Parameter(Mandatory = $true)]
    [string]$newPassword,
    [Parameter(Mandatory = $false)]
    [string]$GPUInstanceId,
    [Parameter(Mandatory = $false)]
    [string]$GPULocationPath,
    [Parameter(Mandatory = $true)]
    [string]$parentGamesVHDxTemplate
)

Set-Location $PSScriptRoot

#Include common functions
#VMC spojuje skript a AOF Library "textove" a vysledek pak vola jako jeden cely skript - ridi se touto znackou:
#<VMC-Include-AOFLibrary>
#Pri debugovani includujeme AOFLibrary standardnim zpusobem
.($PSScriptRoot+"\AOFLibrary.ps1")

#variables
$ScriptName = "CreateVM"
$LogFile = BeginScriptLogging -ScriptName $ScriptName

$VM = $null

#main

if($CPUCores -lt 1)
{
    HandleScriptExecutionError -Message "Must assign at least one CPU core" -Log $LogFile
}

$ramMinGB = 4
if($ramSizeGB -lt $ramMinGB)
{
    HandleScriptExecutionError -Message "Must assign at least $ramMinGB GB of RAM" -Log $LogFile
}

if(-not (GpuExists -GpuInstanceId $GPUInstanceId -GpuLocationPath $GPULocationPath))
{
    HandleScriptExecutionError -Message "GPU with the specified InstanceId ($GPUInstanceId) and LocationPath ($GPULocationPath) does not exist" -Log $LogFile
}

if((IsGpuAlreadyAssigned -GpuLocationPath $GPULocationPath))
{
    HandleScriptExecutionError -Message "GPU is already assigned to another VM" -Log $LogFile
}

if((Get-VM -Name $vmName -ErrorAction SilentlyContinue))
{
    HandleScriptExecutionError -Message "VM with the same name already exists" -Log $LogFile
}
if(-not (Test-Path -Path $parentGamesVHDxTemplate -PathType Leaf))
{
    HandleScriptExecutionError -Message "Games VHDx does not exist" -Log $LogFile
}
$vSwitches = Get-VMSwitch
if(-not $vSwitches)
{
    HandleScriptExecutionError -Message "No VM switch found" -Log -LogFile
}
$switchName = $vSwitches[0].Name

#create folders for every VM
$VMDir = New-Item -Path "$($vmPath)\" -ItemType Directory -Name "$vmName" -ErrorAction SilentlyContinue
if ($VMDir) {
    Write-Log -Message "Created VM folder '$($VMDir.FullName)'" -Level INFO -Log $LogFile
}
else {
    HandleScriptExecutionError -Message "Failed to create VM folder $vmPath\$vmName" -Log $LogFile
}
$NewVHDX = "$vmPath\$vmName\$vmName.vhdx"

Write-Log -Message "$parentSystemVHDxTemplate's last write time: $((Get-Item -Path $parentSystemVHDxTemplate).LastWriteTime)" -Level INFO -Log $LogFile
#copy parent VHDX to VM's folder and rename it
Write-Log -Message "Copying parent VHDX to VM folder (from '$parentSystemVHDxTemplate' to '$NewVHDX')" -Level INFO -Log $LogFile
Copy-Item -Path $parentSystemVHDxTemplate $NewVHDX -ErrorAction SilentlyContinue -ErrorVariable nSVHDX | Out-Null
if ($nSVHDX) {
    HandleScriptExecutionError -Message "Failed to create VHDX '$NewVHDX'`r`nError: $nSVHDX" -Log $LogFile
}

#create the VM
Write-Log -Message "Creating VM '$vmName'" -Level INFO -Log $LogFile
$VM = New-VM -Name "$vmName" -Path "$vmPath" -VHDPath $NewVHDX -Generation 2
if ($VM) {
    Write-Log -Message "[$vmName]: Successfully created VM" -Log $LogFile -Level INFO
    #configure the VM
    Write-Log -Message "[$vmName]: Configuring VM" -Level INFO -Log $LogFile
    [int64]$RAMBytes = [int64]$ramSizeGB * 1GB
    Set-VM -VM $VM -ProcessorCount $CPUCores -StaticMemory -MemoryStartupBytes $RAMBytes -ErrorAction SilentlyContinue -ErrorVariable sVM

    # https://learn.microsoft.com/en-us/powershell/module/hyper-v/set-vmprocessor?view=windowsserver2025-ps
    Set-VMProcessor -VM $VM -EnableHostResourceProtection $true -ErrorAction SilentlyContinue -ErrorVariable sCPU

    Set-VMProcessor -VM $VM -ExposeVirtualizationExtensions $false

    Set-VM -VM $VM -AutomaticStopAction TurnOff
    Set-VM -VM $VM -GuestControlledCacheTypes $true
    Set-VM -VM $VM -HighMemoryMappedIoSpace 65GB

    # Required for copying files from hypervisor to VM
    Enable-VMIntegrationService -VM $VM -Name "Guest Service Interface"

    Set-VMNetworkAdapter -VM $VM -DhcpGuard On -RouterGuard On -ErrorAction SilentlyContinue -ErrorVariable sNIC
    Connect-VMNetworkAdapter -VMNetworkAdapter $VM.NetworkAdapters[0] -SwitchName $switchName -ErrorAction SilentlyContinue -ErrorVariable cNIC
    if ($sVM -or $sCPU -or $sNIC -or $sNICVLAN -or $cNIC) {
        HandleScriptExecutionError -Message "Failed to configure VM`r`nError: $sVM, $sCPU, $sNIC, $sNICVLAN, $cNIC" -Log $LogFile
    }

    Set-VMKeyProtector -VM $VM -NewLocalKeyProtector
    Enable-VMTPM -VM $VM
}
else {
    HandleScriptExecutionError -Message "Failed to create VM $vmName" -Log $LogFile
}

try
{
    Set-Location $PSScriptRoot
    ..\DDA\AssignGpuToVM.ps1 -vmName $vmName -GpuInstanceId $GPUInstanceId -GpuLocationPath $GPULocationPath
    Write-Log -Message "DDA Successful" -Level INFO -Log $LogFile
}
catch
{
    HandleScriptExecutionError -Message "DDA Failed: $($_.Exception.Message)" -Log $LogFile
}


Write-Log -Message "Starting VM for NVIDIA driver installation" -Level INFO -Log $LogFile
Start-VM -VMName $vmName -ErrorAction Stop
Wait-ForVMReady -VMName $vmName | Out-Null

Write-Log -Message "Installing the preinstalled NVIDIA driver" -Level INFO -Log $LogFile
InstallSuppliedNvidiaDriver -vmName $vmName -adminPassword $templatePassword

Write-Log -Message "Installation successful, shutting down VM" -Level INFO -Log $LogFile
Stop-VM -VMName $vmName


Write-Log -Message "VM created and configured, GPU attached, GPU driver installed, creating VM Checkpoint" -Level INFO -Log $LogFile
try {
    Checkpoint-VM -Name $vmName -SnapshotName "Built & configured"
} catch {
    HandleScriptExecutionError -Message "Failed to create snaphost for VM '$vmName'" -Log $LogFile
}


#start the VM and wait for the heartbeat IC to become available as indication that the OS is fully booted
Write-Log -Message "[$vmName]: Starting VM" -Level INFO -Log $LogFile
Start-VM -VM $VM -ErrorAction SilentlyContinue -ErrorVariable stVM
if ($stVM) {
    HandleScriptExecutionError -Message "Failed to start VM '$vmName'`r`nError: $stVM" -Log $LogFile
}
Wait-ForVMReady -VMName $vmName | Out-Null

#insert short delay, because on fast systems, the netadapter will contain also the APIPA IP, before the new IP is successfully set,
#when skipping the code to set admin password
Start-Sleep -Seconds 10

Write-Log -Message "Testing VM's connection to Parsec" -Level INFO -Log $LogFile
TestVMParsecConnection -vmName $vmName -adminPassword $templatePassword -Log $LogFile

#change local administrator password to randomly generated one when SetVMPassword parameter is specified
ChangeAdminPassword -vmName $vmName -CurrentPassword $templatePassword -NewPassword $newPassword -LogFile $LogFile

Write-Log -Message "Creating diff disk, assigning it and configuring it" -Level INFO -Log $LogFile
..\BuildVM\AttachAndConfigureGamesDisk.ps1 -vmName $vmName -GamesDiskParentPath $parentGamesVHDxTemplate -vmPath $vmPath -AdminPassword $newPassword

Write-Log -Message "Detecting if GPU is working" -Level INFO -Log $LogFile
EnsureGpuRunningWithoutErrors -vmName $vmName -adminPassword $newPassword -LogFile $LogFile
Write-Log -Message "GPU is working" -LEVEL INFO -Log $LogFile

Write-ScriptSuccess -Log $LogFile
