Function Memory_Services(@Service, @Params)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Memory_Services

    Description :   Handler program for all module related services.

    Notes       :   The generic parameters should contain all the necessary information to process the services. Often
                    this will be information like the data Record and Key ID.

    Parameters  :
        Service         [in] -- Name of the service being requested
        Error          [out] -- Any errors that were created
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    History     :   (Date, Initials, Notes)
        03/29/13    dmb     [SRPFW-9]   Original programmer.
        08/01/13    dmb     [SRPFW-16]  Add PM_CURRENT_IMAGE_PATH as an option to the GETVALUES list.
        10/01/13    dmb     [SRPFW-18]  Replace APP_INSERTS with LOGICAL and declare Error_Services. - 
        10/05/13    dmb     [SRPFW-18]  Add the RemoveKey service.
        11/06/14    dmb     [SRPFW-79]  Add support to tag cached data with a time marker in the SetValue service so
                            subsequent GetValue calls can avoid using data that might be too old.
        10/29/17    dmb     Retrofit to use Enhanced BASIC+.
        02/17/18    dmb     Add support to name specific memory service caches. This was to allow some memory service
                            data to be protect from the ReleaseHashTable service. All services will default to the
                            general cache if no cache name is specified. Added ReleaseAllHashTables to clear all caches.
        09/25/19    dmb     [SRPFW-278] Update all services that support named caches so that whenever the named cache
                            doesn't exist it will create one automatically rather than default to the primary cache.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP

Common /MemoryServices/ MemoryServicesManagers@, MemoryCacheNames@, Unused3@, Unused4@, Unused5@, Unused6@

Equ Day$            to 86400    ; // Seconds in one day.

// Self referencing declarations.
Declare function    Memory_Services
Declare subroutine  Memory_Services, Error_Services
// SRP FastArray declarations.
Declare function    SRP_FastArray_Count, SRP_FastArray_Create, SRP_FastArray_Extract, SRP_FastArray_GetVariable, 
Declare subroutine  SRP_FastArray_Delete, SRP_FastArray_Insert, SRP_FastArray_InsertFromList, SRP_FastArray_Release, SRP_FastArray_Replace, SRP_FastArray_ReplaceWithList
// SRP HashTable declarations.
Declare function    SRP_HashTable_Create, SRP_HashTable_Contains, SRP_HashTable_Count, SRP_HashTable_Get, SRP_HashTable_GetKeys, SRP_HashTable_GetValues, SRP_HashTable_GetKeyValuePairs
Declare subroutine  SRP_HashTable_Set, SRP_HashTable_Release, SRP_HashTable_Remove
// SRP List declarations.
Declare function    SRP_List_Create, SRP_List_CreateFromFastArray, SRP_List_Count, SRP_List_GetAt, SRP_List_GetVariable, SRP_List_Locate
Declare subroutine  SRP_List_Add, SRP_List_InsertAt, SRP_List_Release, SRP_List_RemoveAt, SRP_List_SetAt
// SRP Array declarations.
Declare function    SRP_Clean_Array, SRP_Join_Arrays, SRP_Reorder_Array, SRP_Rotate_Array, SRP_Sort_Array

GoToService else
    Error_Services('Add', Service : ' is not a valid service request within the ' : ServiceModule : ' module.')
end

Return Response OR ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// KeyExists
//
// Returns a True or False depending on whether the Key ID exists.
//----------------------------------------------------------------------------------------------------------------------
Service KeyExists(KeyID, CacheName)

    KeyExists   = False$    ; // Assume it does not exist for now.

    If Len(KeyID) then
        If CacheName EQ '' then
            HandlePos   = 1
        end else
            Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                // The named cache does not exist so create it now.
                Memory_Services('CreateHashTable', CacheName)
                Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                    HandlePos = 1
                end
            end
        end
        If Len(MemoryServicesManagers@<HandlePos>) then
            KeyExists   = SRP_HashTable_Contains(MemoryServicesManagers@<HandlePos>, KeyID)
        end
    end else
        Error_Services('Set', 'Key ID did not contain a value in the KeyExists service request.')
    end

    Response    = KeyExists

end service


//----------------------------------------------------------------------------------------------------------------------
// GetValue
//
// Returns the value pair stored in the SRP Hash Table for the current Key ID. If the NotExpired flag is set, the
// ExpirationDuration will be used to compare against the last time marker set for the current data.
//----------------------------------------------------------------------------------------------------------------------
Service GetValue(KeyID, NotExpired, ExpirationDuration, CacheName)

    Value               = ''    ; // Assume the value is null for now.

    If Len(KeyID) then
        If CacheName EQ '' then
            HandlePos   = 1
        end else
            Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                // The named cache does not exist so create it now.
                Memory_Services('CreateHashTable', CacheName)
                Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                    HandlePos = 1
                end
            end
        end
        If Len(MemoryServicesManagers@<HandlePos>) then
            If NotExpired then
                CurrMarker  = (Date() * Day$) + Time()
                PrevMarker  = SRP_HashTable_Get(MemoryServicesManagers@<HandlePos>, 'TIMEMARKER*' : KeyID)
                If (CurrMarker - PrevMarker) LT ExpirationDuration then
                    Value   = SRP_HashTable_Get(MemoryServicesManagers@<HandlePos>, KeyID)
                end
            end else
                Value   = SRP_HashTable_Get(MemoryServicesManagers@<HandlePos>, KeyID)
            end
        end
    end else
        Error_Services('Set', 'Key ID did not contain a value in the GetValue service request.')
    end

    Response = Value

end service


//----------------------------------------------------------------------------------------------------------------------
// SetValue
//
// Updates the value pair stored in the SRP Hash Table for the current Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service SetValue(KeyID, Value, CacheName)

    If Len(KeyID) then
        // If the Memory Services's hash table does not yet exist then create it now.
        If CacheName EQ '' then
            HandlePos   = 1
        end else
            Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                // The named cache does not exist so create it now.
                Memory_Services('CreateHashTable', CacheName)
                Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                    HandlePos = 1
                end
            end
        end
        If Len(MemoryServicesManagers@<HandlePos>) else
            Memory_Services('CreateHashTable', CacheName)
        end
        SRP_HashTable_Set(MemoryServicesManagers@<HandlePos>, KeyID, Value)
        // Set a time marker for this data in case future GetValue services need to know how old the data is.
        TimeMarker  = (Date() * Day$) + Time()
        SRP_HashTable_Set(MemoryServicesManagers@<HandlePos>, 'TIMEMARKER*' : KeyID, TimeMarker)
    end else
        Error_Services('Set', 'Key ID did not contain a value in the SetValue service request.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// IsValueExpired
//
// Returns a Boolean flag indicated whether the current value for the indicated KeyID has expired. This relies upon the
// time marker set using the SetValue service. If this value has net yet been set then the value will be considered as
// expired.
//----------------------------------------------------------------------------------------------------------------------
Service IsValueExpired(KeyID, ExpirationDuration, ResetAge, CacheName)

    If Not(Num(ExpirationDuration)) then ExpirationDuration = 0
    ValueExpired    = True$                     ; // Assume the value has expired for now.
    If ResetAge NE True$ then ResetAge = False$ ; // Default is false unless otherwise specified.

    If Len(KeyID) AND (ExpirationDuration GT 0) then
        If CacheName EQ '' then
            HandlePos   = 1
        end else
            Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                // The named cache does not exist so create it now.
                Memory_Services('CreateHashTable', CacheName)
                Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                    HandlePos = 1
                end
            end
        end
        If Len(MemoryServicesManagers@<HandlePos>) then
            PrevMarker  = SRP_HashTable_Get(MemoryServicesManagers@<HandlePos>, 'TIMEMARKER*' : KeyID)
            If PrevMarker NE '' then
                TimeMarker  = (Date() * Day$) + Time()
                If (TimeMarker - PrevMarker) LE ExpirationDuration then
                    ValueExpired    = False$
                    If ResetAge EQ True$ then
                        SRP_HashTable_Set(MemoryServicesManagers@<HandlePos>, 'TIMEMARKER*' : KeyID, TimeMarker)
                    end
                end
            end
        end
    end else
        Error_Services('Set', 'KeyID or ExpirationDuraton was missing in the IsValueExpired service.')
    end

    Response = ValueExpired

end service


//----------------------------------------------------------------------------------------------------------------------
// IsValueCurrent
//
// Returns a Boolean flag indicated whether the current value for the indicated KeyID is still current. This relies upon the
// time marker set using the SetValue service. If this value has net yet been set then the value will be considered as
// expired.
//----------------------------------------------------------------------------------------------------------------------
Service IsValueCurrent(KeyID, ExpirationDuration, ResetAge, CacheName)

    If Not(Num(ExpirationDuration)) then ExpirationDuration = 0
    ValueCurrent    = False$                    ; // Assume the value is not current for now.
    If ResetAge NE True$ then ResetAge = False$ ; // Default is false unless otherwise specified.

    If Len(KeyID) AND (ExpirationDuration GT 0) then
        If CacheName EQ '' then
            HandlePos   = 1
        end else
            Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                // The named cache does not exist so create it now.
                Memory_Services('CreateHashTable', CacheName)
                Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                    HandlePos = 1
                end
            end
        end
        If Len(MemoryServicesManagers@<HandlePos>) then
            PrevMarker  = SRP_HashTable_Get(MemoryServicesManagers@<HandlePos>, 'TIMEMARKER*' : KeyID)
            If PrevMarker NE '' then
                TimeMarker  = (Date() * Day$) + Time()
                If (TimeMarker - PrevMarker) LE ExpirationDuration then
                    ValueCurrent    = True$
                    If ResetAge EQ True$ then
                        SRP_HashTable_Set(MemoryServicesManagers@<HandlePos>, 'TIMEMARKER*' : KeyID, TimeMarker)
                    end
                end
            end
        end
    end else
        Error_Services('Set', 'KeyID or ExpirationDuraton was missing in the IsValueCurrent service.')
    end

    Response = ValueCurrent

end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveKey
//
// Removes the Key ID, and its value pair, from the SRP Hash Table.
//----------------------------------------------------------------------------------------------------------------------
Service RemoveKey(KeyID, CacheName)

    If Len(KeyID) then
        // If the Memory Services's hash table does not yet exist then create it now.
        If CacheName EQ '' then
            HandlePos   = 1
        end else
            Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                // The named cache does not exist so create it now.
                Memory_Services('CreateHashTable', CacheName)
                Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else
                    HandlePos = 1
                end
            end
        end
        If Len(MemoryServicesManagers@<HandlePos>) then
            SRP_HashTable_Remove(MemoryServicesManagers@<HandlePos>, KeyID)
        end
    end else
        Error_Services('Set', 'Key ID did not contain a value in the RemoveKey service request.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// CreateHashTable
//
// Creates the SRP Hash Table that the Memory_Services module will use to manage various Key ID and Value pairs. A
// check will first be made to see if the handle to the Hash Table already exists. If so then it will be released and
// a new Hash Table will be created.
//----------------------------------------------------------------------------------------------------------------------
Service CreateHashTable(CacheName)

    If CacheName EQ '' then
        HandlePos   = 1
        If Len(MemoryServicesManagers@<HandlePos>) then
            Memory_Services('ReleaseHashTable')
        end
    end else
        Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos then
            Memory_Services('ReleaseHashTable', CacheName)
        end else
            HandlePos   = DCount(MemoryCacheNames@, @FM) + 1
            If HandlePos EQ 1 then HandlePos = 2    ; // Handle position 1 is reserved for the default cache.
        end
    end

    MemoryServicesManagers@<HandlePos>  = SRP_HashTable_Create()
    If HandlePos GT 1 then
        MemoryCacheNames@<HandlePos>    = CacheName
    end

    Response    = MemoryServicesManagers@<HandlePos>

end service


//----------------------------------------------------------------------------------------------------------------------
// ReleaseHashTable
//
// Releases the SRP Hash Table handle. If CacheName is empty then the default handle is released.
//----------------------------------------------------------------------------------------------------------------------
Service ReleaseHashTable(CacheName)

    HandlePos   = ''
    If CacheName EQ '' then
        HandlePos   = 1
    end else
        Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else Null
    end

    If HandlePos GE 1 then
        SRP_HashTable_Release(MemoryServicesManagers@<HandlePos>)
        MemoryServicesManagers@<HandlePos>  = ''
        MemoryCacheNames@<HandlePos>        = ''
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ReleaseAllHashTables
//
// Releases all SRP Hash Table handles.
//----------------------------------------------------------------------------------------------------------------------
Service ReleaseAllHashTables(CacheName)

    If MemoryServicesManagers@ NE '' then
        For Each Handle in MemoryServicesManagers@ using @FM
            If Handle NE '' then SRP_HashTable_Release(Handle)
        Next Handle
    end
    MemoryServicesManagers@ = ''
    MemoryCacheNames@       = ''

end service


//----------------------------------------------------------------------------------------------------------------------
// GetHandle
//
// Returns the handle to the SRP Hash Table used by Memory_Services.
//----------------------------------------------------------------------------------------------------------------------
Service GetHandle(CacheName)

    HandlePos   = ''
    If CacheName EQ '' then
        HandlePos   = 1
    end else
        Locate CacheName in MemoryCacheNames@ using @FM setting HandlePos else Null
    end

    If HandlePos GE 1 then
        Handle  = MemoryServicesManagers@<HandlePos>
    end
    
    Response = Handle

end service


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////?

