modules/Module.psm1

using module '.\Enums.psm1'

class Version{
    hidden [int] $major
    hidden [int] $minor
    hidden [int] $patch
    hidden [int] $build

    Version([string] $version){
        if ($version.StartsWith("v.") -or $version.StartsWith("V.")) {
            $version = $version.Substring(2)
        }
        elseif ($version.StartsWith("v") -or $version.StartsWith("V")) {
            $version = $version.Substring(1)
        }

        $items = $version.Split('.')

        $this.major = $items[0]
        $this.minor = $items[1]
        $this.patch = $items[2]
        $this.build = $items[3]
    }

    [string] ToString(){
        if ($this.major -eq 0) { return 'NA' }

        return ('{0}.{1}.{2}.{3}' -f @($this.major, $this.minor, $this.patch, $this.build))
    }

    hidden [int] Compare([int] $item1, [int] $item2){
        $retValue = 0

        if ($item1 -lt $item2) { $retValue = -1 }
        elseif ($item1 -gt $item2) { $retValue = 1 }

        return $retValue
    }

    [int] CompareTo([Version] $version){
        $compare = @()

        $compare[0] = $this.Compare($this.major, $version.major)
        $compare[1] = $this.Compare($this.minor, $version.minor)
        $compare[2] = $this.Compare($this.patch, $version.patch)
        $compare[3] = $this.Compare($this.build, $version.build)

        for ($i=0; $i -lt 4; $i++){
            if ($compare[$i] -in (-1, 1)){
                return $compare[$i]
            }
        }

        return 0
    }

    [RELEASE] Difference([Version] $version){
        $compare = @()

        $compare[0] = $this.Compare($this.major, $version.major)
        $compare[1] = $this.Compare($this.minor, $version.minor)
        $compare[2] = $this.Compare($this.patch, $version.patch)
        $compare[3] = $this.Compare($this.build, $version.build)

        for ($i=0; $i -lt 4; $i++){
            if ($compare[$i] -in (-1, 1)){
                switch ($i) {
                    0 { return [RELEASE]::Major }
                    1 { return [RELEASE]::Minor }
                    2 { return [RELEASE]::Patch }
                    3 { return [RELEASE]::Build }
                }
            }
        }

        return [RELEASE]::None
    }
}

class ApplicationModule{
    hidden [string] $urlTemplate = 'https://{subdomain}{ext}.{domain}/{app}/{endpoint}'
    hidden [int] $TIMEOUT = 10

    [MODULE] $type
    [PSCustomObject] $settings
    [bool] $forceSecurityProtocol

    [bool] $containSubmodules

    ApplicationModule([MODULE] $type, [PSCustomObject] $settings, [bool]$forceSecurityProtocol){
        $this.type = $type

        $this.settings = $settings
        $this.containSubmodules = $settings.version.submodules
        $this.forceSecurityProtocol = $forceSecurityProtocol
    }

    [hashtable] GetVersion([string] $subdomainExtension, [string] $domain, [hashtable]$headers){
        $url = $this.getModuleUrl($subdomainExtension, $domain)

        $retValue = @{}
        switch ($this.settings.version.method) {
            'dfs' {
                try {
                    $response = ($this.invokeRequest($url, $headers) | ConvertFrom-Json)
                }
                catch {
                    $response = @{}
                }

                $retHash = $this.findDfsKey($response, $this.type)

                foreach ($key in $retHash.Keys) {
                    $retValue.Add($key, [Version]::new($retHash[$key]))
                }
            }
            'json' {
                try {
                    $response = ($this.invokeRequest($url, $headers) | ConvertFrom-Json)
                }
                catch {
                    $response = @{}
                }

                $retValue.Add($this.type, [Version]::new($response.($this.settings.version.element)))
            }
            'text' {
                try {
                    $response = $this.invokeRequest($url, $headers)
                }
                catch {
                    $response = ''
                }

                $textVersion = $this.findTextKey($response, $this.settings.version.element)
                if ($this.settings.version.build){
                    $textVersion += '.' + $this.findTextKey($response, $this.settings.version.build)
                }

                $retValue.Add($this.type, [Version]::new($textVersion))
            }
        }
        return $retValue
    }

    #region Private methods

    hidden [string] getModuleUrl([string] $subdomainExtension, [string] $domain){
        $unique = (Get-Date).Ticks

        $retValue = $this.urlTemplate.Replace('{subdomain}', $this.settings.subdomain)
        $retValue = $retValue.Replace('{ext}', $subdomainExtension)
        $retValue = $retValue.Replace('{domain}', $domain)
        if ($this.settings.application) {
            $retValue = $retValue.Replace('{app}', $this.settings.application)
        }
        else {
            $retValue = $retValue.Replace('/{app}', $this.settings.application)
        }
        if ($this.settings.version.endpoint) {
            $retValue = $retValue.Replace('{endpoint}', $this.settings.version.endpoint)
        }
        else {
            $retValue = $retValue.Replace('/{endpoint}', $this.settings.version.endpoint)
        }
        $retValue = $retValue.Replace('{unique}', $unique)

        return $retValue
    }

    hidden [string] findTextKey([string] $response, [string]$key) {
        $retValue=''
        foreach ($line in $response.Split([Environment]::NewLine)){
            $prop = $line.Split("=")
            if ($prop[0] -eq $key) {
                $retValue = $prop[1]
                break
            }
        }

        return $retValue
    }

    hidden [hashtable] findDfsKey([PSCustomObject] $response, [MODULE] $moduleName) {

        $retValue = @{}
        $retValue.Add($moduleName, $response.app.version)

        if ($response.providersPayload) {
            foreach ($member in $response.providersPayload.PsObject.Properties) {

                $memberVersion = $member.Value.version
                $retValue.Add($member.name, $memberVersion)
            }
        }

        return $retValue
    }

    hidden [string] findTextKeyBySearch([string]$response, [string]$start, [string]$end){

        $i = $response.ToString().IndexOf($start) + $start.Length
        $j = $response.ToString().IndexOf($end)-1

        $value = ''
        if ($j -le 0){
            $value = $response.ToString().Substring($i, $response.Length-1-$i)
        }
        else {
            $value = $response.ToString().Substring($i, $j-$i)
        }

        return $value
    }

    hidden [object] invokeRequest([string]$url, [hashtable] $headers) {
        $response = ''

        if ($url -ne '') {
            if ($this.forceSecurityProtocol -eq $true) {
                [System.Net.ServicePointManager]::SecurityProtocol = @('Ssl3', 'Tls12', 'Tls11', 'Tls')
            }
            try {
                $response = (Invoke-WebRequest $url -headers $headers -TimeOut $this.TIMEOUT)
            }
            catch {
            }
        }

        return $response
    }

    #endregion
}