//Copyright 2020 Siemens Digital Industries Software
//==================================================
//Copyright $2010.
//Siemens Product Lifecycle Management Software Inc.
//All Rights Reserved.
//==================================================
//Copyright 2020 Siemens Digital Industries Software

/**
    @file ErrorStoreBase.hxx

    This file contains the declarations of the ErrorStoreBase class in module libbase_utils.
    ErrorStoreBase is the keeper of error codes raised by ERROR_raise() in libsyss.
    ErrorStoreBase is a singleton, which can be accessed through the static function Teamcenter::ErrorStoreBase::getInstance().
    The system (Teamcenter) needs to do the following in order to fully initialise ErrorStore functionality:
    <ol>
    <li>implement a decoder function that translates an error code into an internationalized error message (something like EMH_get_err_string())
    <li>this decoder function must set its return value and success/failure flag via @c libbase_utils Teamcenter::ErrorStoreBase::set_error_string()
    <li>register this decoder function once per session via @c libbase_utils Teamcenter::ErrorStoreBase::set_decoder_function(...) (e.g. in ERROR_store_init_table())
    <li>register this decoder function for various error code ranges via @c libsyss ERROR_register_decoder_fn() (e.g. in ERROR_store_init_table())
    </ol>
*/

#ifndef BASE_UTILS_ERROR_STORE_HXX
#define BASE_UTILS_ERROR_STORE_HXX

#include <set>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <unidefs.h>
#include <vector>

#include <base_utils/libbase_utils_exports.h>

namespace Teamcenter
{

class BASE_UTILS_API ErrorStoreBase
{
public:
    typedef int ERROR_severity_t;
    typedef char * (*ERROR_decode_function_t) (int ifail, void * data);
    typedef void   (*ERROR_decode_free_function_t) (void * decoded_text);
    typedef  void  (*ERROR_journal_comment_function_t)( const char * );
    typedef  void  (*ERROR_journal_flush_function_t)( void );

    static const ERROR_severity_t ERRORSTORE_SEVERITY_INFORMATION  = 1;  // EMH_severity_information
    static const ERROR_severity_t ERRORSTORE_SEVERITY_WARNING      = 2;  // EMH_severity_warning
    static const ERROR_severity_t ERRORSTORE_SEVERITY_ERROR        = 3;  // EMH_severity_error
    static const ERROR_severity_t ERRORSTORE_SEVERITY_USER_ERROR   = 4;  // EMH_severity_user_error
    static const ERROR_severity_t ERRORSTORE_SEVERITY_PROTECT_MARK = 100;

    static ErrorStoreBase & getInstance();

    /** 
        Returns the top-most error message from the error stack.
        <br/>If the error stack is empty, return "".
    */
    static std::string getTopErrorMessage( int presumed_top_ifail );

    /**
       Adds the given error to the store.
       
       @note The parameters @p s1 to @p s7 will be substituted into the parameterized error message 
       to form the localized error message.
       <br/>Parameters show as @c "%n$" in the parameterized error message, where @c n is the n-th argument to substitute.
       
       @note If @p ifail is zero, the first string is taken as the total error message.

       @returns The provided @p ifail.
    */
    int store( ERROR_severity_t severity, int ifail, 
               const char *s1=0, const char *s2=0, const char *s3=0, const char *s4=0, const char *s5=0, const char *s6=0, const char *s7=0);

    /** 
        Clears the error stack and adds the given error.

        @note It might complain indignantly if the error store was not empty anyway (i.e. you ignored an error).

        @returns The provided @p ifail.
    */
    int store_initial_error( ERROR_severity_t severity, int ifail, 
                             const char *s1=0, const char *s2=0, const char *s3=0, const char *s4=0, const char *s5=0, const char *s6=0, const char *s7=0);

    /**
       Stores the given error as the last-but-one error.

       @returns The provided @p ifail.
    */
    int store_before_latest( ERROR_severity_t severity, int ifail, 
                             const char *s1=0, const char *s2=0, const char *s3=0, const char *s4=0, const char *s5=0, const char *s6=0, const char *s7=0);

    /** 
        Stores the given error, but replaces its normal error message with a provided message.

        @note It might complain indignantly if the error store was not empty anyway (i.e. you ignored an error).

        @returns The provided @p ifail.
    */
    int store_substituted( ERROR_severity_t severity, int ifail, const char *s);

    /**
       Forgets the last-stored error.
    */
    void forget_last_error (int ifail_to_forget);

    /** 
        Forgets all errors on the error stack.
    */
    void clear();

    /** 
        Forgets all errors with a given severity.
    */
    void clear( 
        ERROR_severity_t level        /**<(I) The severity level. */
        );

    /**
       Forgets the first error with the given number found starting from the top of the error stack.
    */
    void clear_topmost_ifail( int ifail_to_be_cleared );

    /** 
        Protects the current errors from any call to call to ErrorStoreBase::clear (either method).
    */
    void set_protect_mark( int * mark, const char * fileName, int lineNumber );

    /** 
        Removes the protection mark set through ErrorStoreBase::set_protect_mark.
    */
    void clear_protect_mark( int mark );

    /**
       Watches for an error of the given value to be added to the error stack.
       <br/>If such an error is added, the call stack is printed in the syslog file.
    */
    void catch_this_ifail( 
        int ifail  /**< (I) The ifail to watch for. 
                      <br/>If the value is a multiple of @c 1000 (i.e. an error base), 
                      all errors from this error base and @c 999 are watched.  
                      <br/>If the value is @c -42, all the errors are being watched. */
        );

    /**
       Returns the error currently being watched.
    */
    int ask_catching_ifail( void ) const;
    
    /** 
        A potential ERROR_internal call
    */
    void assert_empty( void ) const;

    /** 
        Returns the last error added to the error stack.
    */
    int ask_last_ifail( void ) const;

    /** 
        Retrieves the translated error message corresponding to the last error.

        @returns A static pointer valid until the next ERROR store operation.
        <br/>A pointer to an empty string will be returned if
        the error stack is empty or the last entry is a protect mark.
    */
    const char * ask_last_message( int presumed_top_ifail ) const;

    /** 
        Retrieves all stored errors.

        @returns Static pointers that are valid until the next ERROR store operation
        <br/>Messages are in historical order (message zero is the oldest).
        <br/>Tracebacks will only be set if a) it is non-null and b) CHECKING is on
    */
    void ask( int * count, const ERROR_severity_t ** severities, const int ** ifails,  const char *const ** messages, const char *const ** tracebacks = 0) const;

    /** 
        Checks through the error stack down to the top protect mark, if a given error is in the error stack.
        
        @returns @c true if this particular error is in the error stack.
    */
    logical ask_had_error( int ifail ) const;

    /**
        Checks through the error stack down to the top protect mark, if a given error (of severity warning) is in the error stack.
        
        @return true if found.
    */
    logical ask_had_warning() const;

    /** 
        Prints the error store.
    */
    void print( FILE *fp = 0 ) const;

    /** 
        Returns a mini-traceback that is SM allocated (@c UGII_CHECKING_LEVEL must be set to 2).
    */
    char* traceback_as_string() const;

    /**
       Function to provide a string giving the interesting bits of a traceback.
       Used by the TC_USAGE tool.
       @param ignore_first_n The number of entries in the stack trace to ignore.
    
       @returns The SM_allocated string for writing to the TC_USAGE table.
    */
    char * traceback_as_string_for_usage ( 
        int ignore_first_n /**< (I) The number of rows in the traceback to skip before gathering the stack trace information. */
        );

    /** 
        The inital error is stored the first time ERROR_raise is called since the
        last call to clear_initial_traceback
        the returned integer was the failure code, if non-zero then it may be interesting to ask_initial_traceback()
    */
    static int ask_initial_error();

    /**
       The initial trace back is stored the first time ERROR_raise is called until the last call to ErrorStoreBase::clear_initial_traceback.
       
       @returns The first error raised since the last call ot ErrorStoreBase::clear_initial_traceback
       <br/>If it is non-zero, the traceback string may be of interest.
    */    
    static int ask_initial_traceback( const char *separator,  /**< (I) The separator used in @p traceback in between different errors. */
                                      char ** traceback       /**< (OF) The traceback information. */
        );

    /**
       The inital trace back is stored the first time ERROR_raise is called since the
       last call to clear_initial_traceback
    */
    static void clear_initial_traceback();

    /**
       A check to allow us to be confident that ERROR_decode did or did not use
       the decode function registered with ERROR_register_decoder_fn.
       <br/>Functions registered with ERROR_register_decoder_fn should call ErrorStoreBase::set_last_decoded_error_string so to:
       <ol>
       <li>Enable ErrorStorebase to use the right function to free memory
       <li>Enable ErrorStorebase to determine whether ERROR_decode did or did not use the registered function
       </ol>
      
       @note Refer to ErrorStoreBase::ask_last_decoded_error_string
    */
    void set_last_decoded_error_string( const char * decoded_error_string,    /**< (I) The decoded I18n error message, e.g. "CXPOM_wrong_class: given %1$ when expecting %2$" 
                                                                                 or "CXPOM_wrong_class:  %1$ vorhanden, wenn %2$ erwartet". */
                                        logical translation_found,             /**< (I) Whether or not the message represents a successfully internationalized error message, 
                                                                                  e.g. use "false" for the fallback error message "error_7023". */
                                        ERROR_decode_free_function_t remover   /**< (I) The function to use in order to free the memory pointed to by 'decoded_error_string'. */
        );

    /**
       Allows to find out whether ERROR_decode did or did not use
       the decode function registered with ERROR_register_decoder_fn.
       <br/>The function registered with ERROR_register_decoder_fn should call ErrorStoreBase::set_last_decoded_error_string so to enable ErrorStorebase to:
       <ol>
       <li>Use the right function to free memory
       <li>Determine whether ERROR_decode did or did not use the registered function
       </ol>
       The function returns NULL if the decoded error message (e.g. "CXPOM_wrong_class:  given %1$ when expecting %2$")
       has already been substituted                           (e.g. "CXPOM_wrong_class:  given WorkspaceObject when expecting Item").
    
       @note Refer to ErrorStoreBase::set_last_decoded_error_string
    */
    void ask_last_decoded_error_string( const char ** error_string,             /**< (O) The last known decoded error message whose placeholder have not yet been substituted, 
                                                                                   something like "CXPOM_wrong_class:  given %1$ when expecting %2$" or "CXPOM_wrong_class:  %1$ vorhanden, wenn %2$ erwartet". */
                                        logical * translation_found,            /**< (O) Whether or not the message represents a successfully internationalized error message, 
                                                                                   e.g. "false" for the fallback error message "error_7023". */
                                        ERROR_decode_free_function_t * remover  /**< (O) The function to use in order to free the memory pointed to by 'decoded_error_string'. */
        ) const;

    /**
       Returns the error decoding function, which is the function used to decode an error number into its associated error string (with placeholders for I18n).
     */
    ERROR_decode_function_t ask_decoder_function() const;

    /**
       Sets the error decoding function, which is the function used to decode an error number into its associated error string (with placeholders for I18n).
     */
    void set_decoder_function( ERROR_decode_function_t decode_function );

    /** 
        Registers specific error codes that should trigger the following call:
        @code
        ERROR_note( ERROR_line, "internal programming error %d", ifail );
        @endcode
    */
    void register_internal_error( int ifail );

    /** 
        Unregisters the error codes (i.e. stop triggering a call to ERROR_note).
    */
    void unregister_internal_error( int ifail );

    /**
       Returns the registered error codes (i.e. the ones that trigger a call to ERROR_note).
     */
    const std::set< int > & ask_registered_internal_errors() const;
    
    /** 
        Sets the journal comment function, which is the function that records a comment into the journal file.
    */
    void set_journal_comment_function( ERROR_journal_comment_function_t journal_comment_function );
    
    /**
       Sets the journal flush function, which is the function that flushes the journal.
    */
    void set_journal_flush_function( ERROR_journal_flush_function_t journal_flush_function );

    /**
       Frees the memory associated with an error message returned from ERROR_decode.
    */
    void free_error_decode_message( const char *error_decode_message );

private:
    ErrorStoreBase();
    virtual ~ErrorStoreBase();

    int *              ifail_store;
    const char **      message_store;
    ERROR_severity_t * severity_store;
    const char **      substitutions_store;
    const char **      tracebacks_store;

    std::set< int > registered_internal_errors; // specific internal error codes that should trigger a ERROR_note( ERROR_line, "internal programming error %d", ifail );

    int store_size;
    int store_allocated;
    int ifail_to_catch;

    logical record_error_protect_calls;

    ERROR_decode_function_t decode_function;
    const char * decoded_error_string;
    logical decoded_error_translated;
    ERROR_decode_free_function_t decoded_error_remover;
    ERROR_journal_comment_function_t journal_comment_function;
    ERROR_journal_flush_function_t journal_flush_function;

    void increment_store_size();
    void delete_top_error();
    void delete_given_error( int where_ );
    void internal_store_error( ERROR_severity_t severity, int ifail, const char *s1, const char *s2, const char *s3, const char *s4, const char *s5, const char *s6, const char *s7);
    void record_error_protect_call( int mark, const char * fileName, int lineNumber );
    void ensure_errors_translated();
    void print_error_n( FILE * fp, int entry, int max_message_length ) const;
};

} // namespace Teamcenter


#include <base_utils/libbase_utils_undef.h>
#endif  // BASE_UTILS_ERROR_STORE_HXX
