text: // Copyright 2014 MongoDB Inc.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  // http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  #pragma once
  
  #include <array>
  #include <functional>
  #include <iostream>
  #include <memory>
  #include <mutex>
  #include <stack>
  #include <thread>
  #include <type_traits>
  #include <unordered_map>
  #include <vector>
  
  #include <mongocxx/config/private/prelude.hh>
  
  namespace mongocxx {
  MONGOCXX_INLINE_NAMESPACE_BEGIN
  namespace test_util {
  
  template <typename T>
  class mock;
  
  template <typename R, typename... Args>
  class mock<R (*)(Args...)> {
     public:
      using underlying_ptr = R (*)(Args...);
      using callback = std::function<R(Args...)>;
      using conditional = std::function<bool(Args...)>;
  
      class rule {
          friend class mock;
  
         public:
          rule(callback callback) : _callback(std::move(callback)) {
              times(1);
          }
  
          void times(int n) {
              until([n](Args...) mutable -> bool { return n-- > 0; });
          }
  
          void forever() {
              until([](Args...) { return true; });
          }
  
          void until(conditional conditional) {
              _conditional = std::move(conditional);
          }
  
         private:
          callback _callback;
          conditional _conditional;
      };
  
      class instance {
          friend class mock;
  
         public:
          instance(const instance&) = delete;
          instance& operator=(const instance&) = delete;
  
          ~instance() {
              _parent->destroy_active_instance();
          }
  
          // Interposing functions replace C-Driver functionality completely
          rule& interpose(const std::function<R(Args...)>& func) {
              _callbacks.emplace([func](Args... args) { return func(args...); });
  
              return _callbacks.top();
          }
  
          template <typename T, typename... U>
          typename std::enable_if<std::is_same<T, R>::value, rule&>::type interpose(T r, U... rs) {
              std::array<R, sizeof...(rs) + 1> vec = {r, rs...};
              std::size_t i = 0;
  
              _callbacks.emplace([vec, i](Args...) mutable -> R {
                  if (i == vec.size()) {
                      i = 0;
                  }
                  return vec[i++];
              });
              _callbacks.top().times(vec.size());
  
              return _callbacks.top();
          }
  
          // Visiting functions get called in addition to the original C-Driver function
          rule& visit(std::function<void(Args...)> func) {
              _callbacks.emplace([=](Args... args) {
                  func(args...);
                  return _parent->_func(args...);
              });
  
              return _callbacks.top();
          }
  
          std::size_t depth() const {
              return _callbacks.size();
          }
  
          bool empty() const {
              return _callbacks.empty();
          }
  
         private:
          instance(mock* parent) : _parent(parent) {
          }
  
          mock* _parent;
          std::stack<rule> _callbacks;
      };
  
      friend class instance;
  
      mock(underlying_ptr func) : _func(std::move(func)) {
      }
      mock(mock&&) = delete;
      mock(const mock&) = delete;
      mock& operator=(const mock&) = delete;
  
      R operator()(Args... args) {
          auto instance = active_instance();
          if (instance) {
              while (!instance->_callbacks.empty()) {
                  if (instance->_callbacks.top()._conditional(args...)) {
                      return instance->_callbacks.top()._callback(args...);
                  }
                  instance->_callbacks.pop();
              }
          }
  
          return _func(args...);
      }
  
      std::unique_ptr<instance> create_instance() {
          std::unique_ptr<instance> mock_instance(new instance(this));
          active_instance(mock_instance.get());
          return mock_instance;
      }
  
     private:
      instance* active_instance() {
          const auto id = std::this_thread::get_id();
          std::lock_guard<std::mutex> lock(_active_instances_lock);
          const auto iterator = _active_instances.find(id);
          if (iterator != _active_instances.end()) {
              return iterator->second;
          }
          return nullptr;
      }
  
      void active_instance(instance* instance) {
          const auto id = std::this_thread::get_id();
          std::lock_guard<std::mutex> lock(_active_instances_lock);
  
          auto& current = _active_instances[id];
          assert(!current);  // It is impossible to create two instances in a single thread
          current = instance;
      }
  
      void destroy_active_instance() {
          const auto id = std::this_thread::get_id();
          std::lock_guard<std::mutex> lock(_active_instances_lock);
          _active_instances.erase(id);
      }
  
      std::mutex _active_instances_lock;
      std::unordered_map<std::thread::id, instance*> _active_instances;
      const underlying_ptr _func;
  };
  
  }  // namespace test_util
  MONGOCXX_INLINE_NAMESPACE_END
  }  // namespace mongocxx
  
  #include <mongocxx/config/private/postlude.hh>
  