param([string]$AssemblyPath = (Join-Path $PSScriptRoot '..\bin\Debug\netstandard2.0\ClumsyPilot.dll'))

$ErrorActionPreference = 'Stop'
$assembly = [Reflection.Assembly]::LoadFrom((Resolve-Path $AssemblyPath))

function Assert-True($Actual, [string]$Message) {
    if (-not $Actual) { throw $Message }
}

function Assert-False($Actual, [string]$Message) {
    if ($Actual) { throw $Message }
}

function Assert-Near([double]$Expected, [double]$Actual, [double]$Tolerance, [string]$Message) {
    if ([Math]::Abs($Expected - $Actual) -gt $Tolerance) {
        throw "$Message Expected=$Expected Actual=$Actual Tolerance=$Tolerance"
    }
}

function Get-RequiredType([string]$Name) {
    return $assembly.GetType($Name, $true)
}

function New-Map([bool]$WithObstacle) {
    $bounds = [Activator]::CreateInstance($boundsType, @([single]0, [single]4000, [single]0, [single]4000))
    $request = [Activator]::CreateInstance($mapRequestType)
    $request.Bounds = $bounds
    $request.ResolutionMm = [single]50
    if ($WithObstacle) {
        $obstacle = [Activator]::CreateInstance($rectangleType, @([single]1900, [single]2100, [single]1800, [single]2200))
        $obstacles = [Array]::CreateInstance($obstacleType, 1)
        $obstacles.SetValue($obstacle, 0)
        $source = [Activator]::CreateInstance($manualSourceType, @('validator-obstacle', [long]1, $true, $obstacles))
        $sources = [Array]::CreateInstance($obstacleSourceType, 1)
        $sources.SetValue($source, 0)
        $request.ObstacleSources = $sources
    } else {
        $request.ObstacleSources = [Array]::CreateInstance($obstacleSourceType, 0)
        $request.AllowExplicitEmptyMap = $true
    }
    $result = [Activator]::CreateInstance($mapFactoryType).Create($request)
    Assert-True $result.Succeeded 'Validation test map must be built.'
    Assert-True $result.Map.PlanningReady 'Validation test map must be ready.'
    return $result.Map
}

function New-SmoothedPoint([double]$X, [double]$Y, [double]$ArcLength, $Direction,
    [double]$VehicleCurvature = 0.0, [bool]$IsGearSwitch = $false, [double]$Clearance = 999.0,
    [double]$VehicleCurvatureDerivative = 0.0) {
    return [Activator]::CreateInstance($smoothedPointType, @(
        $X, $Y, [double]0.0, [double]0.0, $ArcLength, $Direction,
        $VehicleCurvature, $VehicleCurvature, $VehicleCurvatureDerivative, $Clearance, $IsGearSwitch, $anchor))
}

function New-PreparedPoint([double]$X, [double]$Y, [double]$ArcLength, [bool]$IsGearSwitch = $false) {
    return [Activator]::CreateInstance($smoothingPointType, @(
        $X, $Y, $ArcLength, [double]0.0, [double]0.0, [double]999.0, $IsGearSwitch, $anchor))
}

function New-OneSegmentCase([double]$X0, [double]$Y0, [double]$X1, [double]$Y1, [double]$VehicleCurvature = 0.0,
    [double]$StartArcLength = 0.0, [double]$VehicleCurvatureDerivative = 0.0) {
    $candidatePath = [Array]::CreateInstance($smoothedPointType, 2)
    $candidatePath.SetValue((New-SmoothedPoint $X0 $Y0 $StartArcLength $forward 0.0 $false 999.0 $VehicleCurvatureDerivative), 0)
    $candidatePath.SetValue((New-SmoothedPoint $X1 $Y1 ($StartArcLength + 1.0) $forward $VehicleCurvature $false 999.0 $VehicleCurvatureDerivative), 1)
    $candidateSegments = [Array]::CreateInstance($smoothedSegmentType, 1)
    $candidateSegments.SetValue([Activator]::CreateInstance($smoothedSegmentType, @(0, $forward, 0, 1, $false, $false)), 0)
    $preparedPoints = [Array]::CreateInstance($smoothingPointType, 2)
    $preparedPoints.SetValue((New-PreparedPoint $X0 $Y0 0.0), 0)
    $preparedPoints.SetValue((New-PreparedPoint $X1 $Y1 1.0), 1)
    $preparedSegments = [Array]::CreateInstance($preparedSegmentType, 1)
    $preparedSegments.SetValue([Activator]::CreateInstance($preparedSegmentType, @(0, $forward, $preparedPoints, $false, $false)), 0)
    return [PSCustomObject]@{
        CandidatePath = $candidatePath
        CandidateSegments = $candidateSegments
        Original = [Activator]::CreateInstance($preparedPathType, [object[]]@(,$preparedSegments))
    }
}

function Invoke-Validation($Case, $Map) {
    $arguments = [object[]]@($Case.CandidatePath, $Case.CandidateSegments, $Case.Original, $Map, $vehicle,
        [double]0.05, $null, [double]0.0, $null)
    $accepted = $validateMethod.Invoke($validator, $arguments)
    return [PSCustomObject]@{ Accepted = $accepted; Path = $arguments[6]; MinimumClearance = $arguments[7]; Reason = $arguments[8] }
}

function New-MovedGearSwitchCase {
    $candidatePath = [Array]::CreateInstance($smoothedPointType, 4)
    $candidatePath.SetValue((New-SmoothedPoint 0.5 0.5 0.0 $forward), 0)
    $candidatePath.SetValue((New-SmoothedPoint 1.5 0.5 1.0 $forward), 1)
    $candidatePath.SetValue((New-SmoothedPoint 1.6 0.5 1.0 $reverse 0.0 $true), 2)
    $candidatePath.SetValue((New-SmoothedPoint 0.5 0.5 2.0 $reverse), 3)
    $candidateSegments = [Array]::CreateInstance($smoothedSegmentType, 2)
    $candidateSegments.SetValue([Activator]::CreateInstance($smoothedSegmentType, @(0, $forward, 0, 1, $false, $true)), 0)
    $candidateSegments.SetValue([Activator]::CreateInstance($smoothedSegmentType, @(1, $reverse, 2, 3, $true, $false)), 1)
    $preparedFirst = [Array]::CreateInstance($smoothingPointType, 2)
    $preparedFirst.SetValue((New-PreparedPoint 0.5 0.5 0.0), 0)
    $preparedFirst.SetValue((New-PreparedPoint 1.5 0.5 1.0), 1)
    $preparedSecond = [Array]::CreateInstance($smoothingPointType, 2)
    $preparedSecond.SetValue((New-PreparedPoint 1.5 0.5 0.0 $true), 0)
    $preparedSecond.SetValue((New-PreparedPoint 0.5 0.5 1.0), 1)
    $preparedSegments = [Array]::CreateInstance($preparedSegmentType, 2)
    $preparedSegments.SetValue([Activator]::CreateInstance($preparedSegmentType, @(0, $forward, $preparedFirst, $false, $true)), 0)
    $preparedSegments.SetValue([Activator]::CreateInstance($preparedSegmentType, @(1, $reverse, $preparedSecond, $true, $false)), 1)
    return [PSCustomObject]@{
        CandidatePath = $candidatePath
        CandidateSegments = $candidateSegments
        Original = [Activator]::CreateInstance($preparedPathType, [object[]]@(,$preparedSegments))
    }
}

$root = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.'
$processing = $root + 'Processing.'
$validation = $root + 'Validation.'
$coarsePath = 'MultiWheelC.TrajectoryPlanning.CoarsePath.'
$mapping = 'MultiWheelC.TrajectoryPlanning.Mapping.'

$validatorType = Get-RequiredType ($validation + 'SmoothedPathValidator')
$smoothedPointType = Get-RequiredType ($root + 'SmoothedPathPoint')
$smoothedSegmentType = Get-RequiredType ($root + 'SmoothedPathSegment')
$smoothingPointType = Get-RequiredType ($processing + 'SmoothingPoint2D')
$preparedSegmentType = Get-RequiredType ($processing + 'PreparedDirectionSegment')
$preparedPathType = Get-RequiredType ($processing + 'PreparedPath')
$directionType = Get-RequiredType ($coarsePath + 'TravelDirection')
$sourceType = Get-RequiredType ($root + 'SmoothedPathPointSource')
$vehicleType = Get-RequiredType ($coarsePath + 'VehicleParameters')
$boundsType = Get-RequiredType ($mapping + 'MapBoundsMm')
$obstacleType = Get-RequiredType ($mapping + 'IMapObstacle')
$rectangleType = Get-RequiredType ($mapping + 'AxisAlignedRectangleObstacle')
$obstacleSourceType = Get-RequiredType ($mapping + 'IMapObstacleSource')
$manualSourceType = Get-RequiredType ($mapping + 'ManualObstacleSource')
$mapRequestType = Get-RequiredType ($mapping + 'PlanningMapRequest')
$mapFactoryType = Get-RequiredType ($mapping + 'PlanningMapFactory')

$validator = [Activator]::CreateInstance($validatorType)
$validateMethod = $validatorType.GetMethod('TryValidate')
Assert-True ($null -ne $validateMethod) 'SmoothedPathValidator must expose TryValidate.'
Assert-True ($validateMethod.GetParameters().Length -eq 9) 'TryValidate must accept path, segments, originals, map, vehicle, step, path, clearance, and reason.'
$forward = [Enum]::Parse($directionType, 'Forward')
$reverse = [Enum]::Parse($directionType, 'Reverse')
$anchor = [Enum]::Parse($sourceType, 'Anchor')
$vehicle = [Activator]::CreateInstance($vehicleType)
$vehicle.LengthMeters = 0.20
$vehicle.WidthMeters = 0.20
$vehicle.SafetyMarginMeters = 0.0
$vehicle.MaximumCurvaturePerMeter = 1.0
$vehicle.MinimumTurningRadiusMeters = 1.0

$emptyMap = New-Map $false
$obstacleMap = New-Map $true
$valid = Invoke-Validation (New-OneSegmentCase 0.5 0.5 1.5 0.5) $obstacleMap
Assert-True $valid.Accepted ('A valid straight candidate must pass. Reason=' + $valid.Reason)
Assert-True ($null -ne $valid.Path) 'A valid candidate must return clearance-recomputed points.'
Assert-True ($valid.Path[0].BodyClearance -lt 999.0) 'Validated output must replace an overclaimed candidate clearance.'
Assert-True ($valid.MinimumClearance -ge 0.0) 'A valid candidate must report non-negative conservative clearance.'
$derivativePreserved = Invoke-Validation (New-OneSegmentCase 0.5 0.5 1.5 0.5 0.0 0.0 0.25) $obstacleMap
Assert-True $derivativePreserved.Accepted ('A valid derivative-bearing candidate must pass. Reason=' + $derivativePreserved.Reason)
Assert-Near 0.25 $derivativePreserved.Path[0].VehicleCurvatureDerivative 0.000000001 `
    'Clearance recomputation must preserve the first point curvature derivative.'
Assert-Near 0.25 $derivativePreserved.Path[1].VehicleCurvatureDerivative 0.000000001 `
    'Clearance recomputation must preserve the final point curvature derivative.'

$pointCollision = Invoke-Validation (New-OneSegmentCase 2.0 2.0 2.5 2.0) $obstacleMap
Assert-False $pointCollision.Accepted 'A smoothing candidate that touches an obstacle must be rejected.'
$sweptCollision = Invoke-Validation (New-OneSegmentCase 1.5 2.0 2.5 2.0) $obstacleMap
Assert-False $sweptCollision.Accepted 'A smoothing candidate whose sweep cuts through an obstacle must be rejected.'
$outsideBounds = Invoke-Validation (New-OneSegmentCase 0.05 0.5 0.5 0.5) $emptyMap
Assert-False $outsideBounds.Accepted 'A smoothing candidate outside map bounds must be rejected.'
$overCurvature = Invoke-Validation (New-OneSegmentCase 0.5 0.5 1.5 0.5 2.0) $emptyMap
Assert-False $overCurvature.Accepted 'A smoothing candidate above vehicle maximum curvature must be rejected.'
$nonzeroStartArc = Invoke-Validation (New-OneSegmentCase 0.5 0.5 1.5 0.5 0.0 1.0) $emptyMap
Assert-False $nonzeroStartArc.Accepted 'A smoothing candidate must start at global arc length zero.'
$movedSwitch = Invoke-Validation (New-MovedGearSwitchCase) $emptyMap
Assert-False $movedSwitch.Accepted 'A smoothing candidate with a moved gear-switch pose must be rejected.'

Write-Output 'Path smoothing validation checks passed.'
