/*  ****************************************************************************
    *  File: astris_except_classes.h
    *
    *  Exception c++/c interface
    *
    ****************************************************************************
    *
    *  Copyright (C) 2016-2017  Apple, Inc. All rights reserved.
    *
    *  This document is the property of Apple Inc.
    *  It is considered confidential and proprietary.
    *
    *  This document may not be reproduced or transmitted in any form,
    *  in whole or in part, without the express written permission of
    *  Apple Inc.
    ************************************************************************* */

#ifndef _ASTRIS_EXCEPT_CLASSES_H
#define _ASTRIS_EXCEPT_CLASSES_H

// c99 standard library
#include <stdio.h>
#include <stdint.h>

// astris library
#include "astris_api.h"
#ifdef __cplusplus
// Constants, misc.

#include <map>

// Put these AXLOG constants into astris_api.h
#define AXLOG_M_EXCEPTION  "Exception"
#define AXLOG_MD_EXCEPTION "Exception"

// Forward class declarations.
class exc_instance_t;
class exc_breadcrumbs_t;

static constexpr uint32_t K_FMT_STR_MAX = 128;

//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//                          class:  exc_t
//                          class:  exc_t
//                          class:  exc_t
//                          class:  exc_t
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

//                          class:  exc_t
//  Description:
//      Interface used to hold local error state and throw and check for
//      exceptions.  Error state for a function is typically stored in a local
//      variable in the function or as a parameter passed to a function.  Error
//      state is typically returned to a calling function as the return
//      parameter or as a function call parameter by reference.
class exc_t {
public:
    //                          exc_t()
    //  Description:
    //      Default constructor.  Use for local variable in a function when no
    //      exc_t parameter is passed into the function.  Typically used
    //      for adding exception handling to pre-existing code.
    exc_t();
    exc_t(int err_val);

    //                          exc_throw()
    //  Description:
    //      Call this to throw an exception.  Logs exception conditions.  Macro
    //      EXCEPT_THROW() calls this unconditionally.  Macro
    //      EXCEPT_THROW_IF(condition) calls this on condition being true.
    //  Inputs:
    //      err_val - ASTRIS_ERR_* value creating this exception.
    //      log_domain - Passed to all ax_os_log() calls, parameter 1 axldomain.
    //      log_category - Passed to all ax_os_log() calls, parameter 2 category.
    //      file_name - Typically __FILE__ macro.
    //      line_num - Typically __LINE__ macro.
    //      fn_name - Typically __func__ macro.
    //      exc_id_str - Exception ID string.
    //      format - Format string for log message.
    //      ... - Variable parameters for format.
    int exc_throw(int err_val, const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format = NULL, ...);

    //                          exc_check()
    //  Description:
    //      Call this immediately after a function call that returns with
    //      an exception.  Assumes that an exception occurred.  It logs the
    //      exception conditions.  EXCEPT_CHECK() macro calls this when
    //      an exception has been thrown.
    //  Inputs:
    //      log_domain - Passed to all ax_os_log() calls, parameter 1 axldomain.
    //      log_category - Passed to all ax_os_log() calls, parameter 2 category.
    //      file_name - Typically __FILE__ macro.
    //      line_num - Typically __LINE__ macro.
    //      fn_name - Typically __func__ macro.
    //      exc_id_str - Exception ID string.
    //      format - Format string for log message.
    //      ... - Variable parameters for format.
    int exc_check(const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format = NULL, ...);


    /*!
     @function info_msg
     @discussion This is the entry point from exc_t to add an info message to
                 the breadcrumb_t object that is part of the root.
                 info level messages are meant to be internal dev messages.
     */
    int info_msg(const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *format = NULL, ...);

    /*!
     @function user_msg
     @discussion This is the entry point from exc_t to add an user message to
                 the breadcrumb_t object that is part of the root.
                 user level messages are meant to be for the users of API.
     */
    int user_msg(const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *format = NULL, ...);


    /*!
     @function set_fmt_user
     @discussion Setting this will cause a call to exc_check, to then use
                 the format string to log as a user message to the breadcrumb
                 if error value != ASTRIS_ERR_OK
     */
    void set_fmt_user() { m_fmt_to_user = true; }
    void clear_fmt_user() {m_fmt_to_user = false; }

    /*!
     @function set_idstr_user
     @discussion Setting this will cause a call to exc_check, to then use
                 the exc_id_str string to log as a user message to the
                 breadcrumb if error value != ASTRIS_ERR_OK
     */
    void set_idstr_user() { m_idstr_to_user = true; }
    void clear_idstr_user() { m_idstr_to_user = false; }

    //                          operator=()
    //  Description:
    //      Assign an err_val to exception.  Typically used to
    //      process an ASTRIS_ERR* return code from an astris_*()
    //      function that does not have an exc_t* parameter.
    //  Inputs:
    //      err_val - One of the ASTRIS_ERR_* constants.
    void operator=(int err_val);

    //                          exc_err_val()
    //  Description:
    //      Return ASTRIS_ERR_* value for exception.  If no
    //      exception has occurred, returns ASTRIS_ERR_OK.
    inline int exc_err_val() {return m_err_val;}

    //                          err_val_first()
    //  Description:
    //      Return first ASTRIS_ERR_* value for exception.  If no
    //      exception has occurred, returns ASTRIS_ERR_OK.
    //  Notes:
    //      Even though this function is inline, c++ won't inline it
    //      because m_err_val is declared private.
    inline int err_val_first() {return m_err_val_first;}
public:
    // ASRIS_ERR_* values.  Initialized to ASTRIS_ERR_OK.
    int m_err_val;
private:
    // ASRIS_ERR_* values.  Initialized to ASTRIS_ERR_OK.  If set by any
    // e = f<n>(); statement, will be the last statement with an
    // exception, even if a later statement has e = ASTRIS_ERR_OK.
    // Useful for returning an error value if any exception occurred
    // in multiple statements.
    // int g() {exc_t e; e = f1(); e = f2(); e = f3(); return e.m_err_val_first;}
    int m_err_val_first;

    bool m_fmt_to_user = false;
    bool m_idstr_to_user = false;
};

//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//                          class:  exc_thread_root_t
//                          class:  exc_thread_root_t
//                          class:  exc_thread_root_t
//                          class:  exc_thread_root_t
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

//                          class:  exc_thread_root_t
//  Description:
//      Root exception object.  There is exactlty one of these per thread.
//      Contains per thread exception state like number of exceptions that
//      have occurred within the thread.
//
//      For multithreaded program, an instance per thread is created on demand
//      by the first exception thrown within that thread. For single threaded
//      code, is allocated as a global variable.

class exc_thread_root_t {
    friend class exc_instance_t;
public:
    //                          exc_thread_root_t()
    //  Description:
    //      Constructor.
    //  Inputs:
    //      log_domain - Passed to ax_os_log() logging function for all
    //          exceptions.  Defaults to "Exception".
    //      log_category - Log category, OR's of AXL_* constants, for the
    //          log messages for all exceptions.  Typically AXL_ERR or
    //          AXL_U_ERR to write exception messages to stdout.  Defaults
    //          to AXL_ERR.
    exc_thread_root_t(const char *log_domain = AXLOG_M_EXCEPTION, uint64_t log_category = AXL_ERR);

    //                          ~exc_thread_root_t()
    //  Description:
    //      Destructor.  Required to free dynamically allocated
    //      exception stack trace.
    ~exc_thread_root_t();

    //                          est()
    //  Description:
    //      Accessor function, return exc_stack_trace.
    exc_instance_t *est();

    //                          est()
    //  Description:
    //      Delete exception root's exc_stack_trace.
    void est_delete();

    void set_to_log(bool to_log)       {m_to_log = to_log;}
    void set_to_stdout(bool to_stdout) {m_to_stdout = to_stdout;}

    bool to_log() const    {return m_to_log;}
    bool to_stdout() const {return m_to_stdout;}

    //                          add()
    //  Description:
    //      Add exception attributes to exception stack trace. Create EST
    //      if necessary
    //  Inputs:
    //      throw_ - If true, throwing new exception.  If false, adding to current
    //          exception.
    //      err_val
    //      file_name
    //      line_num
    //      fn_name - Function name. Typically __func__ macro.
    //      exc_id_str - Exception ID string.
    //      format_str - String created from (format, args ...) part of exc_throw() and
    //          exc_check() calls.
    //  Returns:
    //      -1 - No stack, or stack overflow
    //      [0..m_stack_size_max-1] - Added OK, index into m_stack of
    //          exception attributes added.
    int er_add(bool throw_, int err_val, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format_str);

    //                          er_print()
    //  Description:
    //      Print exception if it exists.
    void er_print();

    //                          er_log()
    //  Description:
    //      Log exception if it exists.
    void er_log();

    //                          er_exc_number()
    //  Description:
    //      Returns exception number of current exception or 0 if no current
    //      exception.
    unsigned er_exc_number();

    exc_breadcrumbs_t *current_breadcrumbs = NULL;

private:
    // If true, all exception output to stdout
    bool m_to_stdout;

    // If true, all exception output to log
    bool m_to_log;

    // Log domain used when printing or logging all exception instances for
    // this thread.
    const char *m_log_domain;

    // Log category used when printing or logging all exception instances
    // for this thread.
    uint64_t m_log_category;

    // Exception for this thread.  NULL iif no exception has occurred.  Is
    // created when an exception is thrown.  Deleted and set to NULL
    // by destructor after writing out to log file inside destructor.
    exc_instance_t *m_exc_instance;

    // Number of exceptions that have occurred in this thread.
    unsigned m_num_exc;

};

typedef enum exc_elem_type_s
{
    EXC_DEFAULT,
    EXC_EXCEPTION,
    EXC_INFO,
    EXC_USER_ERR_MSG,
} exc_elem_type_t;

//                 class:  exc_elem_t
// Descripion:
//      Contains complete exception attributes.
typedef struct exc_elem_s {
    // Astris error value for current exception state. Note that when
    // tracing, this is usually equal to ASTRIS_ERR_NONE.
    int m_err_val;

    // Name of source file
    const char *m_file_name;

    // Line number or exception ID number
    uint32_t m_line_num;

    // Function name
    const char *m_fn_name;

    // Exception ID string
    const char *m_exc_id_str;

    // String created from (format, args ...) part of exc_throw() and
    // exc_check() calls.
    char m_format_args_str[K_FMT_STR_MAX];

    exc_elem_type_t m_type;

    int m_level;
    int m_index;


} exc_elem_t;


//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//                      class:  exc_instance_t
//                      class:  exc_instance_t
//                      class:  exc_instance_t
//                      class:  exc_instance_t
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

//                      Class:  exc_instance_t
// Descripion:
//      Contains state for a single exception.  Is constructed where
//      the exception is thrown, then built at the exception propogates
//      up the call stack to the top level API function where it can be
//      logged.  It is destroyed after being caught and logged.
class exc_instance_t {
public:
    //                          exc_instance_t()
    //  Description:
    //      Constructor.
    //  Inputs:
    //      exc_number - Exception number for this thread.
    //      num_elem - Number of elements/calls allocated in the stack trace.
    exc_instance_t(int err_val, unsigned exc_number);
    exc_instance_t(int err_val, unsigned exc_number, unsigned stack_size);
    ~exc_instance_t();

    //                          est_stack_malloc()
    //  Description:
    //      Allocate space for exception stack.  Is one contiguous space
    //      for all elements on the stack.
    //  Inputs:
    //      num_elem - Number of elements/calls allocated in the stack trace.
    //          Defaults to 30.
    //  Updates:
    //      this->m_stack - Points to new allocated space for the exception
    //          stack.  Should have been NULL, not pointing to allocated
    //          memory, before this call.s
    void est_stack_malloc(unsigned num_elem);

    //                          est_add()
    //  Description:
    //      Add exception attributes to exception stack trace.
    //  Inputs:
    //      err_val
    //      file_name
    //      line_num
    //      fn_name - Function name. Typically __func__ macro.
    //      exc_id_str - Exception ID string.
    //      format_str - String created from (format, args ...) part of exc_throw() and
    //          exc_check() calls.
    //  Returns:
    //      -1 - No stack, or stack overflow
    //      [0..m_stack_size_max-1] - Added OK, index into m_stack of
    //          exception attributes added.
    int est_add(int err_val, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format_str);

    //                          est_print()
    //  Description:
    //      Print stack trace to stdout.
    //  Inputs:
    //      exc_root - Exception root in currently exeucting thread.
    void est_print(const exc_thread_root_t &exc_root);

    //                          est_log()
    //  Description:
    //      Print stack trace to AXlog.  Uses root log domain and category.
    //  Inputs:
    //      exc_root - Exception root in currently exeucting thread.
    void est_log(const exc_thread_root_t &exc_root);

    inline unsigned est_exc_number() {return m_exc_number;}
private:
    // Exception number in thread code is executing in.
    unsigned m_exc_number;

    // Time the exception was thrown.
    //time_t m_time_thrown;

    // Stack trace of exception attributes
    exc_elem_t *m_stack;

    // Maximum size of list m_stack[].
    unsigned m_stack_size_max;

    // Size of list m_stack[].
    unsigned m_stack_size;
};


class exc_breadcrumbs_t {
public:

    exc_breadcrumbs_t();
    ~exc_breadcrumbs_t();

    int add_exc(exc_t *exc_ptr, int err_val, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format_str);
    int add_info(exc_t *exc_ptr, int err_val, const char *file_name, uint32_t line_num, const char *fn_name, const char *format_str);
    int add_info(exc_t *exc_ptr, int err_val, const char *file_name, uint32_t line_num, const char *fn_name, const char * exc_id_str, const char *format_str);

    int add_user_msg(const char *file_name, uint32_t line_num, const char *fn_name, const char *format_str);
    int add_user_msg(const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format_str);

    /*!
     @function copy_logs
     @discussion Walks both the m_info_stack and m_usr_msg_stack creates messages
                 out of each object using copy_element_desc.
                 The full string is allocated by this method and its up to user
                 of the API to actually free it.
     @param desc Pointer to a char *, this function will alloc the char *
                 and its up to the function to then to free it.
                 On return if char * is NULL that would mean no messages were
                 in the stacks
     @result Will return ASTRIS_ERR_OK even if there are no messages in the stack.
             Up to the user to check the char * to see if its NULL after the call.
     */
    int copy_logs(char **desc);

    /*!
     @function dump_log
     @discussion Walks the m_info_stack and prints out to standard error the
                 description of each element in the stack.
     */
    int dump_log();

    /*!
     @function dump_error_messages
     @discussion Walks the m_usr_msg_stack and prints out to standard error the
                 description of each element in the stack.
     */
    int dump_error_messages();

    void activate() {m_enabled = true; };
    void disable() {m_enabled = false; };
    void reset();

private:

    void dump_element(const exc_elem_t *ee);
    char * copy_element_desc(const exc_elem_t *ee);

    int add_crumb(exc_t *exc_ptr, exc_elem_type_t type, int err_val, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format_str);

    void malloc_stack();

    int m_current_max_level;

    bool m_enabled;

    typedef struct exc_elem_tag_s {

        int m_level;
        int m_current_index;

    } exc_elem_tag_t;

    std::map<exc_t *, exc_elem_tag_t> m_exc_cache;

    // Stack exception and crumb messages
    exc_elem_t *m_info_stack;

    // Stack of user messages
    exc_elem_t *m_usr_msg_stack;

    // Maximum size of list m_info_stack[] and m_usr_msg_stack[].
    static constexpr uint32_t K_STACK_MAX_DEPTH = 128;

    // Size of list m_stack[].
    unsigned m_info_stack_size;

    // Size of list m_usr_msg_stack_size[].
    unsigned m_usr_msg_stack_size;
};

class LogGuard
{
public:
    LogGuard();
    ~LogGuard();

    static void enable();
    static void disable();

private:
    LogGuard(const LogGuard&) = delete;
    LogGuard& operator=(const LogGuard&) = delete;

    static int m_count;
    static bool m_enabled;
};



#endif // __cplusplus

//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//                          c interface
//                          c interface
//                          c interface
//                          c interface
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

#ifdef __cplusplus
extern "C" {
#endif

//                          astris_exc_throw()
//  Description:
//      Call this in C code to throw an exception.  Logs exception conditions.
//      Macro EXCEPT_THROW() calls this unconditionally.  Macro
//      EXCEPT_THROW_IF(condition) calls this on condition being true.
//  Inputs:
//      err_val - ASTRIS_ERR_* value for throwing this exception.
//      log_domain - Passed to all ax_os_log() calls, parameter 1 axldomain.
//      log_category - Passed to all ax_os_log() calls, parameter 2 category.
//      file_name - Typically __FILE__ macro.
//      line_num - Typically __LINE__ macro.
//      fn_name - Typically __func__ macro.
//      exc_id_str - Exception ID string.
//      format - Format string for log message.
//      ... - Variable parameters for format.
//  Returns:
//      ASTRIS_ERR_* value used to throw the original exception.
int astris_exc_throw(int err_val, const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format, ...);

//                          astris_exc_check()
//  Description:
//      Call this immediately after a function call that returns with
//      an exception.  Assumes that an exception occurred.  It logs the
//      exception conditions.  EXCEPT_CHECK() macro calls this when
//      an exception has been thrown.
//
//      Interface identical to exc_t::exc_throw(), except that parameter 3
//      err_val is added as the equivalent "this" parameter passed in
//      as a reference in the non static c++ member function
//      exc_t::exc_throw().
//  Inputs:
//      err_val - ASTRIS_ERR_* current state.
//      log_domain - Passed to all ax_os_log() calls, parameter 1 axldomain.
//      log_category - Passed to all ax_os_log() calls, parameter 2 category.
//      file_name - Typically __FILE__ macro.
//      line_num - Typically __LINE__ macro.
//      fn_name - Typically __func__ macro.
//      exc_id_str - Exception ID string.
//      format - Format string for log message.
//      ... - Variable parameters for format.
//  Returns:
//      ASTRIS_ERR_* value used to throw the original exception.
int astris_exc_check(int err_val, const char *log_domain, uint64_t log_category, const char *file_name, uint32_t line_num, const char *fn_name, const char *exc_id_str, const char *format, ...);

#ifdef __cplusplus
}
#endif

#endif // _ASTRIS_EXCEPT_CLASSES_H
