# encoding: ascii
# api: powershell
# title: Invoke-Generic
# description: Invoke generic method definitions (including static methods) from PowerShell.
# version: 0.1
# type: function
# author: Joel Bennett
# license: CC0
# function: Invoke-Generic
# x-poshcode-id: 2649
# x-derived-from-id: 2662
# x-archived: 2011-11-12T05:57:14
# x-published: 2011-05-03T22:44:00
#
# There’s still a lot of documentation missing … but this time it actually works most of the time
#

function Invoke-Generic {
#.Synopsis
#  Invoke Generic method definitions via reflection:
[CmdletBinding()]
param( 
   [Parameter(Position=0,Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
   [Alias('On')]
   $InputObject
,
   [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)]
   [Alias('Named')]
   [string]$MethodName
,
   [Parameter(Position=2)]
   [Alias("Types")]
   [Type[]]$ParameterTypes
, 
   [Parameter(Position=4, ValueFromRemainingArguments=$true, ValueFromPipelineByPropertyName=$true)]
   [Object[]]$WithArgs
,
   [Switch]$Static
)
begin {
   if($Static) {
      $BindingFlags = [System.Reflection.BindingFlags]"IgnoreCase,Public,Static"
   } else {
      $BindingFlags = [System.Reflection.BindingFlags]"IgnoreCase,Public,Instance"
   }
}
process {
   $Type = $InputObject -as [Type]
   if(!$Type) { $Type = $InputObject.GetType() }
   
   if($WithArgs -and -not $ParameterTypes) {
      $ParameterTypes = $withArgs | % { $_.GetType() }
   } elseif(!$ParameterTypes) {
      $ParameterTypes = [Type]::EmptyTypes
   }   
   
   
   trap { continue }
   $MemberInfo = $Type.GetMethod($MethodName, $BindingFlags)
   if(!$MemberInfo) {
      $MemberInfo = $Type.GetMethod($MethodName, $BindingFlags, $null, $NonGenericArgumentTypes, $null)
   }
   if(!$MemberInfo) {
      $MemberInfo = $Type.GetMethods($BindingFlags) | Where-Object {
         $MI = $_
         [bool]$Accept = $MI.Name -eq $MethodName
         if($Accept){
         Write-Verbose "$Accept = $($MI.Name) -eq $($MethodName)"
            [Array]$GenericTypes = @($MI.GetGenericArguments() | Select -Expand Name)
            [Array]$Parameters = @($MI.GetParameters() | Add-Member ScriptProperty -Name IsGeneric -Value { 
                                       $GenericTypes -Contains $this.ParameterType 
                                    } -Passthru)

                                    $Accept = $ParameterTypes.Count -eq $Parameters.Count
            Write-Verbose "  $Accept = $($Parameters.Count) Arguments"
            if($Accept) {
               for($i=0;$i -lt $Parameters.Count;$i++) {
                  $Accept = $Accept -and ( $Parameters[$i].IsGeneric -or ($ParameterTypes[$i] -eq $Parameters[$i].ParameterType))
                  Write-Verbose "   $Accept =$(if($Parameters[$i].IsGeneric){' GENERIC or'}) $($ParameterTypes[$i]) -eq $($Parameters[$i].ParameterType)"
               }
            }
         }
         return $Accept
      } | Sort { @($_.GetGenericArguments()).Count } | Select -First 1
   }
   Write-Verbose "Time to make generic methods."
   Write-Verbose $MemberInfo
   [Type[]]$GenericParameters = @()
   [Array]$ConcreteTypes = @($MemberInfo.GetParameters() | Select -Expand ParameterType)
   for($i=0;$i -lt $ParameterTypes.Count;$i++){
      Write-Verbose "$($ParameterTypes[$i]) ? $($ConcreteTypes[$i] -eq $ParameterTypes[$i])"
      if($ConcreteTypes[$i] -ne $ParameterTypes[$i]) {
         $GenericParameters += $ParameterTypes[$i]
      }
      $ParameterTypes[$i] = Add-Member -in $ParameterTypes[$i] -Type NoteProperty -Name IsGeneric -Value $($ConcreteTypes[$i] -ne $ParameterTypes[$i]) -Passthru
   }
   
    $ParameterTypes | Where-Object { $_.IsGeneric }
   Write-Verbose "$($GenericParameters -join ', ') generic parameters"
      
   $MemberInfo = $MemberInfo.MakeGenericMethod( $GenericParameters )
   Write-Verbose $MemberInfo
   
   if($WithArgs) {
      [Object[]]$Arguments = $withArgs | %{ $_.PSObject.BaseObject }
      Write-Verbose "Arguments: $(($Arguments | %{ $_.GetType().Name }) -Join ', ')"
      $MemberInfo.Invoke( $InputObject, $Arguments )
   } else {
      $MemberInfo.Invoke( $InputObject )
   }
} }
