Require.psm1

                        
function Request-Module(
    [Parameter(Mandatory=$true)]
    $modules, 
    $version = $null,
    $package = $null,
    [switch][bool] $reload,
    $source = "oneget",
    [switch][bool] $wait = $false,
    [Parameter()]
    [ValidateSet("AllUsers","CurrentUser","Auto")]
    [string] $scope = "CurrentUser",
    [switch][bool] $SkipPublisherCheck,
    [switch][bool] $AllowClobber = $true

) {
    $original_version = $version
    if ($version -eq "latest") {
        $version = "999.999.999"
    }
    function init-psget {
         if ((get-command Install-PackageProvider -module PackageManagement -ErrorAction Ignore) -ne $null) {
            import-module PackageManagement
            $nuget = get-packageprovider -Name Nuget -force -forcebootstrap
            if ($nuget -eq $null) {
                write-host "installing nuget package provider"
                # this isn't availalbe in the current official release of oneget (?)
                install-packageprovider -Name NuGet -Force -MinimumVersion 2.8.5.201 -verbose
            } 
        }
        import-module powershellget
        if ((get-PSRepository -Name PSGallery).InstallationPolicy -ne "Trusted") {
            Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
        }
    }
    function Request-PowershellModule($name) {
        $psrepository = $null
        if ($source.startswith("psgallery:")) {
            $psrepository = $source.substring("psgallery:".Length)
        }
        write-warning "trying powershellget package manager repository='$psrepository'"
        init-psget
        
        if ($mo -eq $null) {
            $p = @{
                Name = $name
                Verbose = $true
                Scope = $scope
                ErrorAction = "Stop"
            }


            if ($AllowClobber -and ((get-command install-module).Parameters.AllowClobber) -ne $null) {
                $p += @{ AllowClobber = $true }  
            } 
            if ($SkipPublisherCheck -and ((get-command install-module).Parameters.SkipPublisherCheck) -ne $null) {
                $p += @{ SkipPublisherCheck = $true }  
            } 
            if ($psrepository -ne $null) {
                $p += @{ Repository = $psrepository }
            }
            $cmd = "install-module"
            write-warning "$cmd"                    
            foreach($kvp in $p.GetEnumerator()) {
                $val = $kvp.value
                if ($val -eq $true) { $val = "`$true" }
                $cmd += " -$($kvp.key):$($val)"
            }
            if ($scope -eq "CurrentUser") {                       
                install-module @p
            } else {
                Invoke-AsAdmin -ArgumentList @("-Command", $cmd) -wait       
                if ($LASTEXITCODE -ne 0) { write-error "install-module failed" }                 
            }
        }            
        else {
            # remove module to avoid loading multiple versions
            rmo $name -erroraction Ignore
            $toupdate = $name
            $p = @{
                Name = $toupdate
                Verbose = $true
                ErrorAction = "Stop"
            }
            if ($psrepository -ne $null) {
                # update-module has no repository parameter
              # $p += @{ Repository = $psrepository }
            }
            $cmd = "update-module"
            foreach($kvp in $p.GetEnumerator()) {
                $val = $kvp.value
                if ($val -eq $true) { $val = "`$true" }
                $cmd += " -$($kvp.key):$($val)"
            }
            write-warning "$cmd"                    
            if ($scope -eq "CurrentUser") {
                try {   
                    ipmo pathutils
                    $path = $mo.path
                    $islinked = $false
                    try {
                        $islinked = pathutils\test-isjunction (split-path -parent $path)
                    } catch {
                        write-warning $_.Exception.message
                    }
                    if ($islinked) {
                        pathutils\update-modulelink $toupdate -ErrorAction stop
                    } else {
                        update-module @p                                      
                    }
                } catch {
                    if ($_.Exception.Message.Contains("Install-Module")) { 
                        # "was not installed by using Install-Module"
                        throw
                    }
                    elseif ($_.Exception.Message.Contains("Administrator")) {
                        # "cannot be updated because Administrator rights are required"
                        write-warning "need to update module as admin"
                        # if module was installed as Admin, try to update as admin
                        Invoke-AsAdmin -ArgumentList @("-Command", $cmd) -wait    
                        if ($LASTEXITCODE -ne 0) { write-error "update-module failed" }
                    }
                    else {
                        throw
                    }
                }
            } else {
                Invoke-AsAdmin -ArgumentList @("-Command", $cmd) -wait
                if ($LASTEXITCODE -ne 0) { write-error "update-module failed" }

            }
        }
        $mo = gmo $name -ListAvailable | select -first 1   
        
        if ($mo -ne $null -and $mo.Version[0] -lt $version) {
            # ups, update-module did not succeed?
            # if module is already installed, oneget will try to update from same repositoty
            # if the repository has changed, we need to force install

            write-warning "requested module $name version $version, but found $($mo.Version[0])!"
            $p = @{
                Name = $name
                Verbose = $true
                Scope = $scope
                ErrorAction = "Stop"
                Force = $true
            }
            if ($AllowClobber -and ((get-command install-module).Parameters.AllowClobber) -ne $null) {
                $p += @{ AllowClobber = $true }  
            } 
            if ($SkipPublisherCheck -and ((get-command install-module).Parameters.SkipPublisherCheck) -ne $null) {
                $p += @{ SkipPublisherCheck = $true }  
            } 
            if ($psrepository -ne $null) {
                $p += @{ Repository = $psrepository }
            }
            $cmd = "install-module"
            write-warning "trying again: $cmd"
      
            foreach($kvp in $p.GetEnumerator()) {
                $val = $kvp.value
                if ($val -eq $true) { $val = "`$true" }
                $cmd += " -$($kvp.key):$($val)"
            }
            if ($scope -eq "CurrentUser") {                       
                install-module @p
            } else {                      
                Invoke-AsAdmin -ArgumentList @("-Command", $cmd) -wait    
                if ($LASTEXITCODE -ne 0) { write-error "update-module failed" }                    
            }
            $mo = gmo $name -ListAvailable | sort version -Descending | select -first 1  
        }

        if ($mo -eq $null) { 
            Write-Warning "failed to install module $name through oneget"
            Write-Warning "available modules:"
            $list = find-module $name
            $list
        } elseif ($mo.Version[0] -lt $version -and $original_version -ne "latest") {
            Write-Warning "modules found:"
            $m = find-module $name
            $m | Format-Table | Out-String | Write-Warning                    
            Write-Warning "sources:"
            $s = Get-PackageSource
            $s | Format-Table | Out-String | Write-Warning
        }   
    }
    function Request-ChocoPackage($name) {
        if ($mo -eq $null) {
            # install
            $cmd = "Process\invoke choco install -y $package -verbose"
            if ($source.startswith("choco:")) {
                $customsource = $source.substring("choco:".length)
                $cmd = "Process\invoke choco install -y $package -source $customsource -verbose"
            }
            $processModulePath = split-path -parent (gmo Process).path
            # ensure choco is installed, then install package
            Invoke-AsAdmin -ArgumentList @("-Command", "
                try {
                   `$env:PSModulePath = `$env:PSModulePath + ';$processModulePath'
                . '$PSScriptRoot\functions\helpers.ps1';
                ipmo Require
                req Process
                write-host 'Ensuring chocolatey is installed';
                _ensure-choco;
                write-host 'installing chocolatey package $package';
                $cmd;
                } finally {
                    if (`$$wait) { Read-Host 'press Enter to close this window and continue'; }
                }
            "
) -wait
            if ($LASTEXITCODE -ne 0) { write-error "choco install failed" }

            #refresh PSModulePath
            # throw "Module $name not found. `r`nSearched paths: $($env:PSModulePath)"
        }
        elseif ($mo.Version[0] -lt $version) {
            # update
            write-warning "requested module $name version $version, but found $($mo.Version[0])!"
            # ensure choco is installed, then upgrade package
            $cmd = "Process\invoke choco upgrade -y $package -verbose"
            if ($source.startswith("choco:")) {
                $customsource = $source.substring("choco:".length)
                $cmd = "Process\invoke choco upgrade -y $package -source $customsource -verbose"
            }
            $processModulePath = split-path -parent ((gmo Process).path)
   
            Invoke-AsAdmin -ArgumentList @("-Command", "
                try {
                `$ex = `$null;
                `$env:PSModulePath = `$env:PSModulePath + ';$processModulePath'
                ipmo Require
                req Process
                . '$PSScriptRoot\functions\helpers.ps1';
                write-host 'Ensuring chocolatey is installed';
                _ensure-choco;
                write-host 'updating chocolatey package $package';
                $cmd;
                
                if (`$$wait) { Read-Host 'press Enter to close this window and continue' }
                
                } catch {
                    write-error `$_;
                    `$ex = `$_;
                    if (`$$wait) { Read-Host 'someting went wrong. press Enter to close this window and continue' }
                    throw;
                }
                finally {
                }
            "
) -wait  
            if ($LASTEXITCODE -ne 0) { write-error "choco upgrade failed" }

        }

        refresh-modulepath 
        if ($mo -ne $null) { rmo $name }
        ipmo $name -ErrorAction Ignore
        $mo = gmo $name -ListAvailable

    }

    import-module process -Global
    if ($scope -eq "Auto") {
        $scope = "CurrentUser"
        if (test-IsAdmin) { $scope = "AllUsers" }
    }

    foreach($_ in $modules)
    { 
        $currentversion = $null
        $mo = gmo $_
        $loaded = $mo -ne $null
        $found = $loaded
        if ($loaded) { $currentversion = $mo.Version[0] }
        if ($loaded -and !$reload -and ($mo.Version[0] -ge $version -or $version -eq $null)) { return }

        if (!$loaded) {
            try {
                ipmo $_ -ErrorAction SilentlyContinue -Global
                $mo = gmo $_
                $loaded = $mo -ne $null
                $found = $loaded
            } catch {
            }
        }

        if(!$found) {
            $mo = gmo $_ -ListAvailable
        }
        $found = $mo -ne $null -and $mo.Version[0] -ge $version
    
        if ($mo -ne $null) {
            write-verbose "found module $($mo.name) version $($mo.version). found=$found reload=$reload"
        }
        else {
            write-verbose "module $_ not found"
        }

        if(!$found -and $mo -ne $null) {
            $available = @(gmo $_ -ListAvailable)
            $mo = $available
            $matchingVers = @($available | ? { $_.Version -ge $version })
            $found = ($matchingVers.Length -gt 0)
            #$found = $available -ne $null
        }
    
        write-verbose "version=$version mo=$mo mo.version=$($mo.Version[0]) requested version = $version"
        if ($reload -or ($version -ne $null -and $loaded -and $currentversion -lt $version)) {
            write-verbose "reloading module $_"
            if (gmo $_) { rmo $_ }
        }

        if (!$found) {
            . "$PSScriptRoot\functions\helpers.ps1";

            if ($currentversion -ne $null) { write-host "current version of module $_ is $currentversion" }
            write-warning "module $_ version >= $version not found. installing from $source"
            if ($source -eq "choco" -or $source.startswith("choco:")) {
                request-chocopackage -name $_
            }
            if ($source -in "oneget","psget","powershellget","psgallery" -or $source.startswith("psgallery:")) {
                request-powershellmodule -name $_
            }
        
        $found = $mo -ne $null -and $mo.Version[0] -ge $version
        } else {
            if (($matchingvers -ne $null) -and ($matchingvers.count -ge 0)) {
                ipmo $_ -MinimumVersion $version
                $mo = gmo $_
            }
        }

               
        if (!($mo)) {          
            throw "Module $_ not found. `r`nSearched paths: $($env:PSModulePath)"
        }
        if ($original_version -eq "latest") {
            Import-Module $_ -DisableNameChecking -Global -ErrorAction stop
            $mo = gmo $_
            write-host "lastest version of module $_ : $($mo.version)"
        }
        else {
            if ($mo.Version[0] -lt $version) {
                throw "requested module $_ version $version, but found $($mo.Version[0])!"
            }
            Import-Module $_ -DisableNameChecking -MinimumVersion $version -Global -ErrorAction stop
        }

        
        }
}


if ((get-alias Require-Module -ErrorAction ignore) -eq $null) { new-alias Require-Module Request-Module }
if ((get-alias req -ErrorAction ignore) -eq $null) { new-alias req Request-Module }

Export-ModuleMember -Function "Request-Module" -Alias *