#ifndef CAFFE2_CORE_INIT_H_
#define CAFFE2_CORE_INIT_H_

#include "caffe2/core/common.h"
#include "caffe2/core/flags.h"
#include "caffe2/core/logging.h"

namespace caffe2 {

namespace internal {
class TORCH_API Caffe2InitializeRegistry {
 public:
  typedef bool (*InitFunction)(int*, char***);
  // Registry() is defined in .cpp file to make registration work across
  // multiple shared libraries loaded with RTLD_LOCAL
  static Caffe2InitializeRegistry* Registry();

  void Register(
      InitFunction function,
      bool run_early,
      const char* description,
      const char* name = nullptr) {
    if (name) {
      named_functions_[name] = function;
    }
    if (run_early) {
      // Disallow registration after GlobalInit of early init functions
      CAFFE_ENFORCE(!early_init_functions_run_yet_);
      early_init_functions_.emplace_back(function, description);
    } else {
      if (init_functions_run_yet_) {
        // Run immediately, since GlobalInit already ran. This should be
        // rare but we want to allow it in some cases.
        LOG(WARNING) << "Running init function after GlobalInit: "
                     << description;
        // TODO(orionr): Consider removing argc and argv for non-early
        // registration. Unfortunately that would require a new InitFunction
        // typedef, so not making the change right now.
        //
        // Note that init doesn't receive argc and argv, so the function
        // might fail and we want to raise an error in that case.
        int argc = 0;
        char** argv = nullptr;
        bool success = (function)(&argc, &argv);
        CAFFE_ENFORCE(success);
      } else {
        // Wait until GlobalInit to run
        init_functions_.emplace_back(function, description);
      }
    }
  }

  bool RunRegisteredEarlyInitFunctions(int* pargc, char*** pargv) {
    CAFFE_ENFORCE(!early_init_functions_run_yet_);
    early_init_functions_run_yet_ = true;
    return RunRegisteredInitFunctionsInternal(
        early_init_functions_, pargc, pargv);
  }

  bool RunRegisteredInitFunctions(int* pargc, char*** pargv) {
    CAFFE_ENFORCE(!init_functions_run_yet_);
    init_functions_run_yet_ = true;
    return RunRegisteredInitFunctionsInternal(init_functions_, pargc, pargv);
  }

  bool RunNamedFunction(const char* name, int* pargc, char*** pargv) {
    if (named_functions_.count(name)) {
      return named_functions_[name](pargc, pargv);
    }
    return false;
  }

 private:
  // Run all registered initialization functions. This has to be called AFTER
  // all static initialization are finished and main() has started, since we are
  // using logging.
  bool RunRegisteredInitFunctionsInternal(
      vector<std::pair<InitFunction, const char*>>& functions,
      int* pargc, char*** pargv) {
    for (const auto& init_pair : functions) {
      VLOG(1) << "Running init function: " << init_pair.second;
      if (!(*init_pair.first)(pargc, pargv)) {
        LOG(ERROR) << "Initialization function failed.";
        return false;
      }
    }
    return true;
  }

  Caffe2InitializeRegistry() {}
  vector<std::pair<InitFunction, const char*> > early_init_functions_;
  vector<std::pair<InitFunction, const char*> > init_functions_;
  std::unordered_map<std::string, InitFunction> named_functions_;
  bool early_init_functions_run_yet_ = false;
  bool init_functions_run_yet_ = false;
};
}  // namespace internal

TORCH_API bool unsafeRunCaffe2InitFunction(
    const char* name,
    int* pargc = nullptr,
    char*** pargv = nullptr);

class TORCH_API InitRegisterer {
 public:
  InitRegisterer(
      internal::Caffe2InitializeRegistry::InitFunction function,
      bool run_early,
      const char* description,
      const char* name = nullptr) {
    internal::Caffe2InitializeRegistry::Registry()->Register(
        function, run_early, description, name);
  }
};

#define REGISTER_CAFFE2_INIT_FUNCTION(name, function, description)         \
  namespace {                                                              \
  ::caffe2::InitRegisterer                                                 \
      g_caffe2_initregisterer_##name(function, false, description, #name); \
  } // namespace

#define REGISTER_CAFFE2_EARLY_INIT_FUNCTION(name, function, description)  \
  namespace {                                                             \
  ::caffe2::InitRegisterer                                                \
      g_caffe2_initregisterer_##name(function, true, description, #name); \
  } // namespace

/**
 * @brief Determine whether GlobalInit has already been run
 */
TORCH_API bool GlobalInitAlreadyRun();

class TORCH_API GlobalInitIsCalledGuard {
 public:
  GlobalInitIsCalledGuard() {
    if (!GlobalInitAlreadyRun()) {
      LOG(WARNING)
          << "Caffe2 GlobalInit should be run before any other API calls.";
    }
  }
};

/**
 * @brief Initialize the global environment of caffe2.
 *
 * Caffe2 uses a registration pattern for initialization functions. Custom
 * initialization functions should take the signature
 *     bool (*func)(int*, char***)
 * where the pointers to argc and argv are passed in. Caffe2 then runs the
 * initialization in three phases:
 * (1) Functions registered with REGISTER_CAFFE2_EARLY_INIT_FUNCTION. Note that
 *     since it is possible the logger is not initialized yet, any logging in
 *     such early init functions may not be printed correctly.
 * (2) Parses Caffe-specific commandline flags, and initializes caffe logging.
 * (3) Functions registered with REGISTER_CAFFE2_INIT_FUNCTION.
 * If there is something wrong at each stage, the function returns false. If
 * the global initialization has already been run, the function returns false
 * as well.
 *
 * GlobalInit is re-entrant safe; a re-entrant call will no-op and exit.
 *
 * GlobalInit is safe to call multiple times but not idempotent;
 * successive calls will parse flags and re-set caffe2 logging levels from
 * flags as needed, but NOT re-run early init and init functions.
 *
 * GlobalInit is also thread-safe and can be called concurrently.
 */
TORCH_API bool GlobalInit(int* pargc, char*** argv);

/**
 * @brief Initialize the global environment without command line arguments
 *
 * This is a version of the GlobalInit where no argument is passed in.
 * On mobile devices, use this global init, since we cannot pass the
 * command line options to caffe2, no arguments are passed.
 */
TORCH_API bool GlobalInit();
}  // namespace caffe2
#endif  // CAFFE2_CORE_INIT_H_
