/*************************************************************************
 * Copyright (C) [2023-2024] by Cambricon, Inc.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *************************************************************************/
#ifndef CSRC_COMMON_STACK_EXCEPTION_H_
#define CSRC_COMMON_STACK_EXCEPTION_H_

#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

namespace tmo {
namespace stack_exception {
/** Call-stack entry datastructure. */
struct entry {
  /** Default constructor that clears all fields. */
  entry() : line(0) {}

  std::string file;      ///< filename
  size_t line;           ///< line number
  std::string function;  ///< name of function or method

  /** Serialize entry into a text string. */
  std::string to_string() const {
    std::ostringstream os;
    os << file;
    if (line > 0) {
      os << ":" << line;
    }
    os << " @" << function;
    return os.str();
  }
};

/** Stack-trace base class, for retrieving the current call-stack. */
class call_stack {
 public:
  /** Stack-trace consructor.
   \param num_discard - number of stack entries to discard at the top. */
  call_stack(const size_t num_discard = 0);

  virtual ~call_stack() throw();

  /** Serializes the entire call-stack into a text string. */
  std::string to_string() const {
    std::ostringstream os;
    for (size_t i = 0; i < stack.size(); i++)
      os << stack[i].to_string() << std::endl;
    return os.str();
  }

  /** Call stack. */
  std::vector<entry> stack;
};

/** Abstract base-class for all stack-augmented exception classes.
 *  Enables catching of all stack-augmented exception classes. */
class stack_exception_base : public call_stack {
 public:
  stack_exception_base(const bool _show_stack) : call_stack(2), show_stack(_show_stack) {}
  virtual ~stack_exception_base() throw() {}

  virtual const char *what() const throw() = 0;

  /// flag to indicate if stack-trace is included in what() messages
  bool show_stack;
};

/** Template for stack-augmented exception classes. */
template <class T>
class stack_exception : public T, public stack_exception_base {
 public:
  stack_exception(const std::string &msg) : T(msg), stack_exception_base(true) {}
  virtual ~stack_exception() throw() {}

  stack_exception(const char *file, int line, const char *pretty_function, const std::string &msg)
      : T(msg), stack_exception_base(true) {
    entry e;
    e.file = file;
    e.line = line;
    e.function = pretty_function;
    stack.insert(stack.begin(), e);
  }

  virtual const char *what() const throw() {
    if (show_stack) {
      // concatenate message with stack trace
      buffer = "[" + std::string(T::what()) + "]\n" + stack_exception::to_string();
      return buffer.c_str();
    } else {
      return T::what();
    }
  }

 private:
  mutable std::string buffer;
};
/** Stack-augmented exception classes for all std::exception classes. */
// typedef stack_exception<std::runtime_error>    TmoException;
// typedef stack_exception<std::range_error>      stack_range_error;
// typedef stack_exception<std::overflow_error>   stack_overflow_error;
// typedef stack_exception<std::underflow_error>  stack_underflow_error;
// typedef stack_exception<std::logic_error>      stack_logic_error;
// typedef stack_exception<std::domain_error>     stack_domain_error;
// typedef stack_exception<std::invalid_argument> stack_invalid_argument;
// typedef stack_exception<std::length_error>     stack_length_error;
// typedef stack_exception<std::out_of_range>     stack_out_of_range;
}  // namespace stack_exception

class _TmoException : public stack_exception::stack_exception<std::runtime_error> {
 public:
  _TmoException(const std::string &msg) : stack_exception<std::runtime_error>(msg) {}
  _TmoException(const char *file, int line, const char *pretty_function, const std::string &msg)
      : stack_exception<std::runtime_error>(file, line, pretty_function, msg) {}
};
#define TmoException(msg) tmo::_TmoException(__FILE__, __LINE__, __PRETTY_FUNCTION__, msg)
}  // namespace tmo

#endif  // CSRC_COMMON_STACK_EXCEPTION_H_
