src/typesystem/TypeIndex.ps1

# Copyright 2021, 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 TypeIndexEntry)
. (import-script TypeMatch)

enum TypeIndexClass {
    Name
    Property
    NavigationProperty
    Method
}

enum TypeIndexLookupClass {
    Exact
    StartsWith
    Contains
}

ScriptClass TypeIndex {
    $IndexedField = $null
    $index = $null
    $typeClassAggregates = $null

    function __initialize([TypeIndexClass] $indexedField) {
        $this.indexedField = $indexedField
        $this.index = [System.Collections.Generic.SortedList[String, Object]]::new(([System.StringComparer]::OrdinalIgnoreCase))
        $this.typeClassAggregates = @{
            Entity = 0
            Complex = 0
            Enumeration = 0
        }
    }

    function Add([string] $lookupValue, $typeId, $typeClass) {
        $entry = __FindEntry $lookupValue

        if ( ! $entry ) {
            $entry = new-so TypeIndexEntry $lookupValue
            $this.index.Add($lookupValue, $entry)
        }

        $entry.AddTarget($typeId, $typeClass)

        if ( $this.typeClassAggregates.ContainsKey($typeClass.tostring()) ) {
            $this.typeClassAggregates[$typeClass.tostring()] += 1
        }
    }

    function Get($key) {
        if ( ! $this.index.ContainsKey($key) ) {
            throw "Key '$key' not found for index '$($this.indexedField)'"
        }

        __FindEntry $key
    }

    function GetLookupValues {
        $this.index.Keys
    }

    function Find($key, $typeClasses) {
        $entry = __FindEntry $key

        if ( $entry ) {
            foreach ( $matchingType in $entry.targets.keys ) {
                $matchedTypeClass = $entry.targets[$matchingType]
                if ( ! $typeClasses -or ( $typeClasses -contains $matchedTypeClass ) ) {
                    new-so TypeMatch $this.indexedField $key $matchingType $matchedTypeClass @($key)
                }
            }
        }
    }

    function FindStartsWith($searchString, $typeClasses) {
        $normalizedSearchString = $searchString.tolower()

        $matchedValues = $this.index.keys | where { $_.tolower().StartsWith($normalizedSearchString) }

        if ( $matchedValues ) {
            foreach ( $matchingvalue in $matchedValues ) {
                $entry = $this.index[$matchingValue]
                foreach ( $matchingType in $entry.targets.keys ) {
                    $matchedTypeClass = $entry.targets[$matchingType]
                    if ( ! $typeClasses -or ( $typeClasses -contains $matchedTypeClass ) ) {
                        new-so TypeMatch $this.indexedField $searchString $matchingType $matchedTypeClass $matchedValues
                    }
                }
            }
        }
    }

    function FindContains($searchString, $typeClasses) {
        $normalizedSearchString = $searchString.tolower()

        $matchedValues = $this.index.keys | where { $_.tolower().Contains($normalizedSearchString) }

        if ( $matchedValues ) {
            foreach ( $matchingvalue in $matchedValues ) {
                $entry = $this.index[$matchingValue]
                foreach ( $matchingType in $entry.targets.keys ) {
                    $matchedTypeClass = $entry.targets[$matchingType]
                    if ( ! $typeClasses -or ( $typeClasses -contains $matchedTypeClass ) ) {
                        new-so TypeMatch $this.indexedField $searchString $matchingType $matchedTypeClass @($matchingValue)
                    }
                }
            }
        }
    }

    function GetStatistics {
        [PSCustomObject] @{
            EntityCount = $this.typeClassAggregates['Entity']
            ComplexCount = $this.typeClassAggregates['Complex']
            EnumerationCount = $this.typeClassAggregates['Enumeration']
        }
    }

    function __FindEntry($key) {
        $this.index[$key]
    }
}