# Copyright 2019, Adam Edwards
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

. (import-script GraphSegment)
. (import-script GraphManager)
. (import-script UriCache)

ScriptClass SegmentParser {
    $graph = $null
    $context = $null
    $uriCache = $null
    $cacheEntities = $false

    function __initialize($graphContext, $existingGraph = $null, $cacheEntities = $false) {
        $this.graph = if ( $existingGraph ) {
            $existingGraph
        } else {
            $this.context = $graphContext
        }

        $this.cacheEntities = $cacheEntities
    }

    function __initializeGraph {
        if ( ! $this.graph ) {
            $this.graph = $::.GraphManager |=> GetGraph $this.context
        }

        if ( ! $this.UriCache ) {
            $uriCache = $this.context |=> GetState $::.GraphManager.UriCacheStateKey
            if ( ! $uriCache ) {
                $uriCache = new-so UriCache 1000
                $this.context |=> AddState $::.GraphManager.UriCacheStateKey $uriCache
            }
            $this.uriCache = $uriCache
        }
    }

    function GetChildren($segment, $allowedTransitions = $null ) {
        if ( ! $segment ) {
            throw "Segment may not be null"
        }

        __initializeGraph

        $existingChildren = if ( $this.uriCache ) {
            $this.uriCache |=> GetChildSegmentsFromUri $segment.graphUri
        }

        $results = if ($existingChildren) {
            $existingChildren
        } elseif ( ( Test-ScriptObject $segment.graphElement EntityVertex ) -and ($segment.graphElement |=> IsRoot) ) {
            $childVertices = $this.graph |=> GetRootVertices
            $childVertices.values | foreach {
                new-so GraphSegment $_
            }
        } else {
            $segment |=> NewNextSegments $this.graph $null $null
        }

        if ( $this.uriCache ) {
            $parentSegment = if ( ! $existingChildren ) { $segment }
            $this.uriCache |=> AddUriForSegments $results $this.cacheEntities $parentSegment
        }

        $filteredResults = if ( ! $allowedTransitions ) {
            $results
        } else {
            $results | where {
                if ( Test-ScriptObject $_.graphElement EntityVertex ) {
                    $_.graphElement.Type -in $allowedTransitions
                } elseif ( Test-ScriptObject $_.graphElement EntityEdge ) {
                    $_.graphElement.transition.Type -in $allowedTransitions
                } else {
                    $types = $_.graphElement.psobject.typenames -join ';'
                    throw "Expecting types EntityVertex or EntityEdge, received unexpected types '$types'"
                }
            }
        }

        $filteredResults
    }

    function SegmentsFromUri([Uri] $uri, $noFailOnExtraSegment) {
        __initializeGraph

        $unescapedPath = [Uri]::UnescapeDataString($uri.tostring()).trim()

        $noRoot = if ( $unescapedPath[0] -eq '/' ) {
            $unescapedPath.trim('/')
        } else {
            if ($unescapedPath.length -gt 0) {
                $unescapedPath.trim('/')
            } else {
                $unescapedPath
            }
        }

        $segmentStrings = $noRoot -split '/'

        if ( $segmentStrings[0] -eq '' ) {
            $segmentStrings = @()
        }

        $segments = @(new-so GraphSegment $::.EntityVertex.RootVertex $null $null)
        $lastSegment = $segments[0]

        foreach ( $targetSegmentName in $segmentStrings ) {
            $cachedSegment = if ($this.UriCache) {
                $this.uriCache |=> GetSegmentFromParent $lastSegment $targetSegmentName
            }

            $currentSegments = if ( $cachedSegment ) {
                $cachedSegment
            } else {
                GetChildren $lastSegment
            }

            if ( ! $currentSegments -and ($currentSegments -isnot [object[]]) ) {
                if ( ! $noFailOnExtraSegment ) {
                    throw "Uri '$($Uri.tostring()) not found: no children found for '$($lastSegment.name)'"
                } else {
                    # In this case, we just return the segments we already parsed -- we'll ignore the last segment
                    # This is added for cases where context information has incorrectly identified the uri
                    # as referring to a navigation to a collection when in fact it is a single entity, so the
                    # last id segment is actually unnecessary
                    break
                }
            }

            $matchingSegment = $null
            if ( $currentSegments -isnot [object[]] -and ! $cachedSegment ) {
                $matchingSegment = new-so GraphSegment $currentSegments[0].graphElement $lastSegment $targetSegmentName
            } else {
                $matchingSegment = $currentSegments | where {
                    $_.name -eq $targetSegmentName
                } | select -first 1

                if ( ! $matchingSegment ) {
                    $parentName = if ( $lastSegment ) {
                        $lastSegment.name
                    } else {
                        '<root>'
                    }
                    throw "Uri '$($Uri.tostring())' not found: no matching child segment '$targetSegmentName' under segment '$parentName'"
                }
            }
            $lastSegment = $matchingSegment

            $segments += $lastSegment

            if ( $this.uriCache ) {
                $this.uriCache |=> AddUriForSegments $segments $this.cacheentities
            }
        }

        $segments
    }
}

