#ifndef DEPTH_GUARD_H_00000000000000000000000000000000000000000000000000000000
#define DEPTH_GUARD_H_00000000000000000000000000000000000000000000000000000000

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4))  // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include "exceptions.h"

namespace YODA_YAML {

/**
 * @brief The DeepRecursion class
 *  An exception class which is thrown by DepthGuard. Ideally it should be
 * a member of DepthGuard. However, DepthGuard is a templated class which means
 * that any catch points would then need to know the template parameters. It is
 * simpler for clients to not have to know at the catch point what was the
 * maximum depth.
 */
class DeepRecursion : public ParserException {
public:
  virtual ~DeepRecursion() = default;

  DeepRecursion(int depth, const Mark& mark_, const std::string& msg_);

  // Returns the recursion depth when the exception was thrown
  int depth() const {
    return m_depth;
  }

private:
  int m_depth = 0;
};

/**
 * @brief The DepthGuard class
 *  DepthGuard takes a reference to an integer. It increments the integer upon
 * construction of DepthGuard and decrements the integer upon destruction.
 *
 * If the integer would be incremented past max_depth, then an exception is
 * thrown. This is ideally geared toward guarding against deep recursion.
 *
 * @param max_depth
 *  compile-time configurable maximum depth.
 */
template <int max_depth = 2000>
class DepthGuard final {
public:
  DepthGuard(int & depth_, const Mark& mark_, const std::string& msg_) : m_depth(depth_) {
    ++m_depth;
    if ( max_depth <= m_depth ) {
        throw DeepRecursion{m_depth, mark_, msg_};
    }
  }

  DepthGuard(const DepthGuard & copy_ctor) = delete;
  DepthGuard(DepthGuard && move_ctor) = delete;
  DepthGuard & operator=(const DepthGuard & copy_assign) = delete;
  DepthGuard & operator=(DepthGuard && move_assign) = delete;

  ~DepthGuard() {
    --m_depth;
  }

  int current_depth() const {
    return m_depth;
  }

private:
    int & m_depth;
};

} // namespace YODA_YAML

#endif // DEPTH_GUARD_H_00000000000000000000000000000000000000000000000000000000
