Param(

    [Parameter(Mandatory = $false)]
    [string]$cmdPath,
    [Parameter(Mandatory = $false)]
    [object[]]$fwFiles,
    [Parameter(Mandatory = $false)]
    [string]$mac = "000000000000",
    [Parameter(Mandatory = $false)]
    [string]$tpg = "0000",
    [Parameter(Mandatory = $false)]
    [string]$subsystemId= "FFFF",
    [Parameter(Mandatory = $false)]
	[string]$logFile = "Programmer.log",
    [Parameter(Mandatory = $false)]
    [string]$chip = "colligo",
    [Parameter(Mandatory = $false)]
    [string]$internalI2cAddress = "60",
    [Parameter(Mandatory = $false)]
    [string]$externalTool = $null,
    [Parameter(Mandatory = $false)]
    [string]$resetMac = "000000000000",
    [Parameter(Mandatory = $false)]
    [string]$resetTpg = "0000",
    [Parameter(Mandatory = $false)]
    [string]$resetSubsystem = "FFFF"

)

#ValUECmd callings

function Get-DeviceName($targetMac, $targetTpg, $targetSubsystemId)
{
    $params = "ua VCPGET {0} {1} {2} {3} {4}" -f $targetMac, $targetTpg, $targetSubsystemId, "00010300", 8
    $output = ''

    $result = Run-ValUECmdProcess $params $true ([ref]$output)
    if($result -ne 0)
    {
        Log-Message "Device not found. Unable to complete firmware update"
        return $null
    }
    else
    {
        $splitedOutput =  $output.Split([environment]::NewLine) | where{[regex]::IsMatch($_, "(COM\d*).*-.*(RX|TX)")}
        if($splitedOutput.Count -eq 0)
        {
            Log-Message "Device not found. Unable to complete firmware update" 
            return $null  
        }
        if($splitedOutput.Count -eq 1 -or ($splitedOutput.Count -eq $null))
        {
            $com = $splitedOutput.Split(' ')
            return @($com[0], $com[2])
        }
        Log-Message "Please make sure PC is connected to 1 device only"
        return $null      
    }
}

#Colligo specific Functions
function Burn-Colligo($fwFile)
{
    $cmdParams = "ua VCPBURN {0} {1} {2} {3}" -f $mac, $tpg, $subsystemId, $fwFile
    $burnResult = Run-ValUECmdProcess $cmdParams
    return $burnResult
}

function Verify-Colligo($fwFile)
{    
    $cmdParams = "ua VCPVERIFY {0} {1} {2} {3}" -f $mac, $tpg, $subsystemId, $fwFile
    $burnResult = Run-ValUECmdProcess $cmdParams
    return $burnResult
}

function Erase-ColligoBank($fwFile)
{
    $cmdParams = "ua VCPERASE {0} {1} {2} {3}" -f $mac, $tpg, $subsystemId, $fwFile

    $programmOut = ''
    $setResult = Run-ValUECmdProcess $cmdParams $true ([ref]$programmOut)
    return $setResult
}

function Reset-Board($targetMac, $targetTpg, $targetSubsystem)
{
   $cmdParams = "ua VCPSET {0} {1} {2} 00010604 01" -f $targetMac, $targetTpg, $targetSubsystem
    $programmOut = ''
    $setResult = Run-ValUECmdProcess $cmdParams $true ([ref]$programmOut)
    return $setResult
}

#DSC specific functions
function Burn-Dsc($fwFile)
{
    $cmdParams = "ua DSCBURN {0} {1} {2} {3} {4}" -f $mac, $tpg, $subsystemId, $fwFile, $internalI2cAddress
    $burnResult = Run-ValUECmdProcess $cmdParams
    return $burnResult
}

function Verify-Dsc($fwFile)
{
    $cmdParams = "ua DSCVERIFY {0} {1} {2} {3} {4}" -f $mac, $tpg, $subsystemId, $fwFile, $internalI2cAddress
    $verifyResult = Run-ValUECmdProcess $cmdParams
    return $verifyResult
}

function Erase-Dsc($fwFile)
{
    $cmdParams = "ua DSCERASE {0} {1} {2} {3} {4}" -f $mac, $tpg, $subsystemId, $fwFile, $internalI2cAddress
    $eraseResult = Run-ValUECmdProcess $cmdParams
    return $eraseResult
}

function Run-ValUECmdProcess($arguments, $redirectOutput = $false, [ref]$stdout)
{   
    #$arguments = "{0}" -f $arguments
    $pi = New-Object Diagnostics.ProcessStartInfo -Property @{
	   FileName = $cmdPath
	   Arguments = $arguments
       RedirectStandardOutput = $redirectOutput
       RedirectStandardError = $true
       UseShellExecute = $false
    }


    $pr = [Diagnostics.Process]::Start($pi)
    $pr.WaitForExit()
    if($redirectOutput)
    {
        $stdout.Value = $pr.StandardOutput.ReadToEnd().TrimEnd([environment]::NewLine)
    }
    $errors = $pr.StandardError.ReadToEnd().TrimEnd([environment]::NewLine)
    if(-not [string]::IsNullOrEmpty($errors))
    {
        Log-Message $errors
    }
    return $pr.ExitCode
}

#Common functions
function Is-Colligo()
{
    return ($chip.ToLower() -eq "colligo")
}

function Burn-Board($fwFile)
{
    if(Is-Colligo)
    {
        return Burn-Colligo $fwFile
    }
    else
    {
        return Burn-Dsc $fwFile
    }
}

function Verify-Board($fwFile)
{
    
    if(Is-Colligo)
    {
        return Verify-Colligo $fwFile
    }
    else
    {
        return Verify-Dsc $fwFile
    }
}

function Erase-Board($fwFile)
{
    if(Is-Colligo)
    {
        return Erase-ColligoBank $fwFile
    }
    else
    {
        return Erase-Dsc $fwFile
    }
}

#Write String to the file
function Log-Message($message)
{
    if(-not [string]::IsNullOrEmpty($message))
    {
        $timestamp = (Get-Date).ToString('dd\/MM\/yyyy hh.mm.ss')
        $logMessage = '[{0}] {1}' -f $timestamp, $message
        Write-Host $logMessage
        Out-File -filepath $logFile -InputObject $logMessage -Append
    } 
}

function Finalize-FailedImplemetation($failedMessage, $needsReset)
{
    Log-Message $failedMessage
    if($needsReset)
    {
        Log-Message "Try to reset board after failed operation"
        $resetResult = Reset-Board
        if($resetResult -ne 0)
        {
            Log-Message "Reset of the board are failed. Possible connection or power problems"
            return
        }
        Log-Message "Reset success"
    }
    Log-Message ("Implementation of {0} ended" -f $externalTool) 
}

#Convert relative paths to absolute check path existing
function Check-ValidFilePath($rootFolder, $path, $defaultPath)
{
    $resultPath = $path
    if([string]::IsNullOrEmpty($path))
    {
        return Join-Path $rootFolder $defaultPath
    }
    elseif(-not([System.IO.Path]::IsPathRooted($path)))
    {
        $resultPath = Join-Path $rootFolder $path
    }
    if(-not (Test-Path $resultPath))
    {
        return ''
    }
    return $resultPath
}

#Script Instructions

    #Get absolute path to Script Directory
    $executionPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
    
    Log-Message ("Implementation of {0} started" -f $externalTool) 

    #Check path to ValUECmd
    $valUECmdPath = Check-ValidFilePath $executionPath $cmdPath ValUECmd\ValUECmd.exe
    if([string]::IsNullOrEmpty($valUECmdPath))
    {
        $cmdFoundMessage = ("ValUECmd not found by path: {0}" -f $cmdPath)
        Finalize-FailedImplemetation $cmdFoundMessage $false
        return
    }
    else
    {
        $cmdPath = $valUECmdPath
    }

    if(Is-Colligo)
    {
        $deviceName = Get-DeviceName $mac $tpg $subsystemId
    }
    else
    {
        $deviceName = Get-DeviceName $resetMac $resetTpg $resetSubsystem
    }
    
    $comPortName = ''
    if($deviceName -eq $null)
    {

        Log-Message ("Implementation of {0} ended" -f $externalTool) 
        return
    }
    else
    {
        $comPortName = $deviceName[0]
        Log-Message ('{0} device found on Port {1} (device ID: {2} {3} {4})' -f $deviceName[1], $comPortName, $mac, $tpg, $subsystemId)
    }

    $needsReset = $false
    foreach($fwFile in $fwFiles)
    {
        #Check FW file
        $checkedFw = Check-ValidFilePath $executionPath $fwFile FW
        if([string]::IsNullOrEmpty($checkedFw))
        {
            $fwErrorMessage = "Firmware file not found {0}" -f $fwFile
            Finalize-FailedImplemetation $fwErrorMessage
            return
        }
        else
        {
            $fwFile = $checkedFw
        }

        #do operations
        #do erase
        Log-Message ("Starting firmware erase on Port {0}" -f $comPortName)
        $eraseResult = Erase-Board $fwFile
        if($eraseResult -ne 0)
        {
            Finalize-FailedImplemetation "Erase failed. Unable to complete firmware update" $needsReset
            return
        }
        else
        { 
            Log-Message "Firmware erase successfully completed"
        }

        #do burn   
        Log-Message ("Starting firmware programming on Port {0}" -f $comPortName)
        $burnResult = Burn-Board $fwFile
        if($burnResult -ne 0)
        {
            Finalize-FailedImplemetation "Programming failed. Unable to complete firmware update" $needsReset
            return
        }
        else
        {
            Log-Message "Firmware programming successfully completed"

            #do verify only if programming succeed
            Log-Message ("Starting verify of new firmware on Port {0}" -f $comPortName)
            $verifyResult = Verify-Board $fwFile
            if($verifyResult -ne 0)
            {
                Finalize-FailedImplemetation "Verify failed. Unable to complete firmware update" $needsReset
                return
            }
            else
            {
                Log-Message "Verify of new firmware successfully completed"
                $needsReset = $true
            }
        }
    }

    #do reset only if all previous operation succed
    Log-Message ("Starting reset of device on Port {0}" -f $comPortName)
    if(Is-Colligo)
    {
        $resetResult = Reset-Board $mac $tpg $subsystemId
    }
    else
    {
        $resetResult = Reset-Board $resetMac $tpg $subsystemId
    }
    
    if($resetResult -ne 0)
    {
        Log-Message “Reset failed. Unable to complete firmware update”
        Log-Message ("Implementation of {0} ended" -f $externalTool) 
    }
    else
    {
        Log-Message "Reset successfully completed"
    }
    Start-Sleep -s 4
    Log-Message ("Implementation of {0} ended" -f $externalTool) 