Function Error_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        :   Error_Services

    Description :   Handler program for all error services.

    Notes       :   Application errors should be logged using the Error Services module. There are a few methodological
                    assumptions built into way errors are managed which are important to understand in order to properly
                    work with Error Services:

                    - The term 'top' refers to the originating procedure of a call stack and the term 'bottom' refers to
                      the last routine (or the current routine) within a call stack. Within the OpenInsight Debugger
                      this will appear backwards since the originating procedure always appears at the bottom of the
                      list and the current routine appears at the top of the list. We are using this orientation because
                      it is common to refer to the process of calling other procedures as 'drilling down'.

                    - The reason for defining the orientation of the call stack is because Error_Services allows for
                      multiple error conditions to be appended to an original error. In most cases this will happen when
                      a procedure at the bottom of the stack generates an error condition and then returns to its
                      calling procedure. This higher level procedure can optionally add more information relevant to
                      itself. This continues as the call stack 'bubbles' its way back to the top to where the
                      originating procedure is waiting.

                    - Native OpenInsight commands that handle errors (e.g., Set_Status, Set_FSError, Set_EventStatus)
                      preserve their error state until explicitly cleared. This can hinder the normal execution of code
                      since subsequent procedures (usually SSPs) will fail if a pre-existing error condition exists.
                      Our philosophy is that error conditions should automatically be cleared before a new procedure
                      is executed to avoid this problem. However, the nature of Basic+ does not make this easy to
                      automate for any given stored procedure. Therefore, if a stored procedure wants to conform to our
                      philosophy then it should include a call into the 'Clear' service request at the top of the
                      program. Alternatively this can be done through a common insert (see SERVICE_SETUP for example.)

                    - Service modules will use the SERVICE_SETUP insert and therefore automatically clear out any
                      error conditions that were set before.

                    - The 'Set' service request is the equivelent to the various forms of setting an error within Basic+
                      (e.g., Set_Status, Set_FSError, Set_EventStatus). This will clear out any pre-existing error(s)
                      first (see 'Clear' service request description below). In most cases the 'Add' service request
                      (see below) should be used since error conditions are intended to be automatically cleared by
                      service modules or properly managed stored procedures.

                    - The 'Add' service request is similar to the 'Set' service request but it will not clear out any
                      pre-existing errors. Using 'Add', the error conditions can be stacked allowing the higher level
                      calling procedures the ability to contribute to the existing error or add additional errors.

                    - The 'Clear' service request will reset all of the error condition flags.

    Parameters  :
        Service         [in] -- Name of the service being requested
        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)
        12/28/12    dmb     Original programmer.
        12/31/12    dmb     Add hooks for various service requests. Add comments in the Notes section to explain the
                            theory of operation of Error Services.
        01/01/13    dmb     Add functionality to the Set, Add, GetMessage, and GetMessages service requests.
        01/02/13    dmb     Remove reference to SERVICES_SETUP and put the Assigned command lines directly into this
                            code to avoid infinite loop problem.
        01/05/13    dmb     Added HasError service request.
        03/13/13    dmb     [SRPFW-9] Added NoError service request.
        10/01/13    dmb     [SRPFW-18] Replace APP_INSERTS with LOGICAL and declare Error_Services.
        10/06/13    dmb     [SRPFW-17] Retrofit to use the default FrameWorks system font.
        03/20/17    fjt	    [SRPFW-160] Conversion to EB+; addition of justification parameter to display.
        10/09/17    dmb     Add SendRuntimeAlert service to act as a debugger intercept process.

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

#pragma precomp SRP_PreCompiler

$Insert LOGICAL
$Insert MSG_EQUATES
$Insert RTI_DEBUG_COMMON
$Insert SRPMail_Inserts
$Insert ENVIRON_CONSTANTS

Common /ErrorServices/ ErrorMessages@, ErrorSources@, ErrorCodes@, RetStacks@, EsSpStatCode@, EsSpStatMessage@, Unused7@, Unused8@

Equ Segoe_UI$ to 'Segoe UI' : @SVM : -12 : @SVM : 400 : @SVM : 0 : @SVM : 0 : @SVM : 0 : @SVM : 0 : @SVM : 34 : @SVM : 0 : @SVM : 3 : @SVM : 2 : @SVM : 1 : @SVM : 0 : @SVM : 0 : @SVM : 0 : @SVM : 0

// Make sure any request parameters which have not been assigned are nulled.
// Normally these would be referenced in the SERVICES_SETUP insert but there is a call to ERROR_SERVICES in that
// insert which causes an infinite loop to occur.
If Assigned(Service)    else Service    = ''
If Assigned(Error)      else Error      = ''
If Assigned(Param1)     else Param1     = ''
If Assigned(Param2)     else Param2     = ''
If Assigned(Param3)     else Param3     = ''
If Assigned(Param4)     else Param4     = ''
If Assigned(Param5)     else Param5     = ''
If Assigned(Param6)     else Param6     = ''
If Assigned(Param7)     else Param7     = ''
If Assigned(Param8)     else Param8     = ''
If Assigned(Param9)     else Param9     = ''
If Assigned(Param10)    else Param10    = ''
If Assigned(Response)   else Response   = ''

AutoDisplayErrors   = False$    ; // Set this to True$ when debugging so all errors will automatically display.

Declare function      RetStack, Error_Services, SRPSendMail, Get_Env
Declare subroutine    Error_Services, Set_Env, Set_Status, GetTempPath

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

Return Response else ''


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


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

//----------------------------------------------------------------------------------------------------------------------
// Set
//
// Param1 - Error message. Messages should be fairly descriptive.
//
// Sets an error to the stack. This will automatically clear any existing error conditions first so this error will
// be the only one on the stack.
//----------------------------------------------------------------------------------------------------------------------
Service Set(ErrorMessage)

    Error_Services('Clear')
    Error_Services('Add', ErrorMessage)

    If AutoDisplayErrors then Error_Services('DisplayError')

End Service


//----------------------------------------------------------------------------------------------------------------------
// Add
//
// Param1 - Error message. Messages should be fairly descriptive.
//
// Adds an error to the stack. This will not clear existing error conditions first. It is intended to allow higher level
// routines to add more information to an existing error condition or simply to maintain an ongoing error log for some
// troubleshooting or debugging purposes.
//----------------------------------------------------------------------------------------------------------------------
Service Add(ErrorMessage)

    CurStack    = RetStack()
    AtSelf      = CurStack[1, @FM]              ; // AtSelf should be the name of this routine (e.g., ERROR_SERVICES)
    Loop
        CurRoutine  = CurStack[1, @FM]          ; // Get the next routine from the program call stack.
    Until CurRoutine _NEC AtSelf
        CurStack    = Delete(CurStack, 1, 0, 0) ; // Remove any self-references from the program call stack.
    Repeat
    Convert @FM to @VM in CurStack              ; // Convert the delimiter so it can be added to the global common.
    
    If Len(ErrorMessages@) then
        ErrorMessages@  := @FM : ErrorMessage
        RetStacks@      := @FM : CurStack
    end else
        ErrorMessages@  = ErrorMessage
        RetStacks@      = CurStack
    end
    
    If AutoDisplayErrors then Error_Services('DisplayError')

End Service


//----------------------------------------------------------------------------------------------------------------------
// Clear
//
// Clears all error conditions and related information.
//----------------------------------------------------------------------------------------------------------------------
Service Clear()

    ErrorMessages@  = ''
    ErrorSources@   = ''
    ErrorCodes@     = ''
    RetStacks@      = ''

End Service


//----------------------------------------------------------------------------------------------------------------------
// GetMessage
//
// Returns the most current error message.
//----------------------------------------------------------------------------------------------------------------------
Service GetMessage()

    Response = ErrorMessages@[-1, 'B' : @FM]

End Service


//----------------------------------------------------------------------------------------------------------------------
// GetMessages
//
// Returns the stack of error messages. This will be @FM delimited.
//----------------------------------------------------------------------------------------------------------------------
Service GetMessages()

    // Business logic goes here. Data that needs to be returned should be assigned to the Response parameter.
    Response = ErrorMessages@

End Service


//----------------------------------------------------------------------------------------------------------------------
// HasError
//
// Returns True if there is an error condition, False if there is no error condition. Caller will still need to use
// the GetMessage or GetMessages service to determine what the error is. The HasError service allows the caller to
// embed the Error_Services service call inside of a conditional statement like this:
//
// If Error_Services('HasError') then
//     * An error has occured. Proceed accordingly.
//     ErrorMessage = Error_Services('GetMessage')
// end else
//     * No error has occured.
// end
//----------------------------------------------------------------------------------------------------------------------
Service HasError()

    If Len(ErrorMessages@) then
        Response = True$
    end else
        Response = False$
    end

End Service


//----------------------------------------------------------------------------------------------------------------------
// NoError
//
// Returns True if there are no error conditions, False if there is an error condition. This is the opposite of the
// HasError service and exists for improved readability.
//----------------------------------------------------------------------------------------------------------------------
Service NoError()

    If Len(ErrorMessages@) then
        Response = False$
    end else
        Response = True$
    end

End Service


//----------------------------------------------------------------------------------------------------------------------
// DisplayError
//
// Displays the current error message to the end user.
//----------------------------------------------------------------------------------------------------------------------
Service DisplayError(Justification)

    ErrorMessage = Error_Services('GetMessage')
    If Len(ErrorMessage) then
        MsgStruct                   = ''
        MsgStruct<MTEXT$>           = ErrorMessage
        MsgStruct<MTYPE$>           = 'BO'
        MsgStruct<MMODAL$>          = 'W'
        MsgStruct<MICON$>           = '!'
        MsgStruct<MCOL$>            = -1
        MsgStruct<MROW$>            = -1
        MsgStruct<MJUST$>           = Justification
        MsgStruct<MCAPTION$>        = 'Error Services'
        MsgStruct<MFONT$>           = Segoe_UI$
        Msg(@Window, MsgStruct)
    end

End Service


Service GetSource()

    // Business logic goes here. Data that needs to be returned should be assigned to the Response parameter.
    Response = '<Service Response>'

End Service


Service GetSources()

    // Business logic goes here. Data that needs to be returned should be assigned to the Response parameter.
    Response = '<Service Response>'

End Service


Service GetCode()

    // Business logic goes here. Data that needs to be returned should be assigned to the Response parameter.
    Response = '<Service Response>'

End Service


Service GetCodes()

    // Business logic goes here. Data that needs to be returned should be assigned to the Response parameter.
    Response = '<Service Response>'

End Service


//----------------------------------------------------------------------------------------------------------------------
// GetStackTrace
//
// Uses a debugger intercept to log the stack including line numbers to a temporary file and returns the contents
// in the Response variable.
//
// Warning:
//      Stepping into this while debugging will interrupt the debugging session and may cause an error message to
//      be displayed. It is advised to set a breakpoint after the call and let the debugger run through this.
//----------------------------------------------------------------------------------------------------------------------
Service GetStackTrace()
    
    Error_Services('LogStack')
    TempLogFileName = 'OIStackLog.csv'         
    TempDirectory   = Str(\00\, 1024)
    GetTempPath(Len(TempDirectory), TempDirectory)
    Convert \00\ to '' in TempDirectory                       
    TempLogPath     = TempDirectory:TempLogFileName	    
    OSRead Response from TempLogpath else 
        Error_Services('Add', 'Error in ':Service:' service. Error retrieving OIStackLog.csv')
    end
    
End Service


//----------------------------------------------------------------------------------------------------------------------
// LogStack
//
// Uses a debugger intercept, DEBUGGER_LOGGER, to log the stack including line numbers to .../LogFiles/StackTrace.
//
// Warning:
//      Stepping into this while debugging will interrupt the debugging session and may cause an error message to
//      be displayed. It is advised to set a breakpoint after the call and let the debugger run through this.
//----------------------------------------------------------------------------------------------------------------------
Service LogStack()
	
	DebuggerEnabled   = Get_Env(ENV_DEBUGGER_ENABLED$)
	DebuggerIntercept = Get_Env(ENV_DEBUGGER_INTERCEPT_PROC$)
	Set_Env(ENV_DEBUGGER_ENABLED$, 2, 1)
	Set_Env(ENV_DEBUGGER_INTERCEPT_PROC$, 'DEBUGGER_LOGGER', 1)
	// Save the current status code and message before forcing runtime error 
	// to get OI to update stack variables in RTI_DEBUG_COMMON.
	EsSpStatCode@     = Get_Status(EsSpStatMessage@)
	ForceRuntimeError = 1/0
	Set_Env(ENV_DEBUGGER_ENABLED$, DebuggerEnabled, 1)
	Set_Env(ENV_DEBUGGER_INTERCEPT_PROC$, DebuggerIntercept, 1)
	// Restore OI status variables so we don't break any logic relying on it.
	Set_Status(EsSpStatCode@, EsSpStatMessage@)
	
end service


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

//----------------------------------------------------------------------------------------------------------------------
// SendRuntimeAlert
//
// Sends out an email alert when this service is called as a debugger intercept.
//----------------------------------------------------------------------------------------------------------------------
SendRuntimeAlert:

    Done            = False$
    Error           = False$
    Program         = Curr_Program@
    MsgSent         = ''
    
    If Program EQ '' then Program = 'Error Services'

    ConfigFile      = ''
    ConfigFile<1>   = SendUsing_Port$
    ConfigFile<2>   = ''
    ConfigFile<3>   = ''                        ; // Server port
    ConfigFile<4>   = ''                        ; // Mail server
    ConfigFile<5>   = True$                     ; // Authenticate
    ConfigFile<6>   = ''                        ; // Username
    ConfigFile<7>   = ''                        ; // Password
    ConfigFile<8>   = False$                    ; // Use SSL

    Text        = ''
    Text<-1>    = 'App:     '       : @APPID<1>
    Text<-1>    = 'Window:  '       : @WINDOW
    Text<-1>    = 'User:    '       : @USERNAME
    Text<-1>    = 'Station: '       : @STATION
    Text<-1>    = ' '
    Text<-1>    = 'SP Status:    '  : SPStatus@
    Text<-1>    = 'SP Stat Code: '  : SPStatCode@
    Text<-1>    = 'Program:      '  : Program
    Text<-1>    = 'Call Depth:   '  : CallDepth@
    Text<-1>    = 'Line No:      '  : LineNo@
    Text<-1>    = ' '
    Text<-1>    = 'Stack:   '
    Text<-1>    = CallStack@
    Convert \00\ TO ',' in Text
    Swap @VM with ':@VM:' IN Text
    Swap @FM with Char(13) : Char(10) IN Text
    Swap @TM with Char(13) : Char(10) IN Text

    SentFrom    = ''
    SentTo      = ''
    Message     = ''
    Message<1>  = '' : Program                          ; // Subject
    Message<2>  = SentFrom                              ; // From (email address)
    Message<3>  = SentTo                                ; // Send to (email address)
    Message<5>  = ''                                    ; // Blind Carbon Copy (email address)
    Message<6>  = ''                                    ; // Reply To (email address)
    Message<7>  = 'TEXT'                                ; // Content Type (TEXT or HTML)
    Message<8>  = Text                                  ; // Content / Body
    Message<9>  = ''                                    ; // Attachment(s) (path to file name(s))

    MsgSent     = SRPSendMail(Message, ConfigFile)

return

