#include "caffe2/core/net.h"
#include "caffe2/core/net_simple.h"

#include <set>
#include <unordered_map>
#include <unordered_set>

#include "caffe2/core/init.h"
#include "caffe2/core/operator.h"
#include "caffe2/core/timer.h"
#include "caffe2/proto/caffe2_pb.h"
#include "caffe2/utils/proto_utils.h"
#include "caffe2/utils/string_utils.h"

C10_DEFINE_string(
    caffe2_override_executor,
    "",
    "Comma-separated list of executor overrides");

namespace caffe2 {

C10_DEFINE_REGISTRY(
    NetRegistry,
    NetBase,
    const std::shared_ptr<const NetDef>&,
    Workspace*);

NetBase::NetBase(
    const std::shared_ptr<const NetDef>& def,
    Workspace* /* unused */)
    : external_input_(
          def->external_input().begin(),
          def->external_input().end()),
      external_output_(
          def->external_output().begin(),
          def->external_output().end()),
      name_(def->name()),
      net_def_(def) {
  static GlobalInitIsCalledGuard guard;
  C10_LOG_API_USAGE_ONCE("caffe2.net.create");
  // Check that node_name is empty for all ops
  for (const OperatorDef& op : def->op()) {
    if (op.has_device_option()) {
      CAFFE_ENFORCE(
          !op.device_option().has_node_name(),
          "node_name must be empty for all operators at execution time.");
    }
  }

  // Go through the operators and make sure that blobs are correctly made.
  std::set<string> known_blobs(
      external_input_.begin(), external_input_.end());
  std::set<string> remaining_output(
      external_output_.begin(), external_output_.end());
  for (const auto& blob : known_blobs) {
    remaining_output.erase(blob);
  }
  for (const OperatorDef& op : def->op()) {
    for (const string& in : op.input()) {
      if (!known_blobs.count(in)) {
        if (external_input_.size()) {
          CAFFE_THROW(
              "op ",
              op.type(),
              ": Source for input ",
              in,
              " is unknown for net ",
              def->name(),
              ", operator ",
              ProtoDebugString(op));
        } else {
          // If we are not declaring input and output, we will simply VLOG it
          // for debugging purposes.
          VLOG(1) << "op " << op.type() << ": input " << in << " is unknown.";
        }
      }
    }
    for (const string& out : op.output()) {
      known_blobs.insert(out);
      remaining_output.erase(out);
    }
  }
  // Finally, check if all declared outputs are being created.
  CAFFE_ENFORCE(
      remaining_output.size() == 0,
      "Some of the blobs are declared as output but never produced by the "
      "net ",
      def->name(),
      ", the first one is ",
      *remaining_output.begin());
}

bool NetBase::RunAsync() {
  for (auto& op : GetOperators()) {
    op->ResetEvent();
  }
  return DoRunAsync();
}

void NetBase::Cancel() {
  for (auto& op : GetOperators()) {
    op->Cancel();
  }
}

namespace {
const std::string kSimpleNet = "simple";

std::vector<NetObserverCreator>* GetNetObserverCreators() {
  static std::vector<NetObserverCreator> creators;
  return &creators;
}

const std::unordered_map<std::string, std::string>& defaultOverrides() {
  // redirecting legacy net types to async_scheduling (except for 'simple');
  // async_scheduling checks net type for backward compatibility
  static const std::unordered_map<std::string, std::string> overrides = {
      {"dag", "async_scheduling"},
      {"prof_dag", "async_scheduling"},
      {"async_dag", "async_scheduling"},
      {"async_polling", "async_scheduling"},
      {"async_simple", "simple"}, // "async_simple" impl has been removed.
      {"rnn", "simple"}, // "rnn" impl has been removed.
  };
  return overrides;
}

void ApplyPotentialExecutorOverride(std::string* net_type) {
  auto executors = caffe2::split(',', FLAGS_caffe2_override_executor);
  CAFFE_ENFORCE(
      executors.size() % 2 == 0, "Invalid override executors flag value");
  std::unordered_map<std::string, std::string> overrides;
  for (const auto& kv : defaultOverrides()) {
    overrides[kv.first] = kv.second;
  }
  for (size_t idx = 0; idx < executors.size(); idx += 2) {
    overrides[executors[idx]] = executors[idx + 1];
  }
  if (overrides.count(*net_type)) {
    VLOG(1) << "Overrode net type '" << *net_type << "' with '"
            << overrides[*net_type] << "'";
    *net_type = overrides[*net_type];
  }
}

} // namespace

void AddGlobalNetObserverCreator(NetObserverCreator creator) {
  GetNetObserverCreators()->push_back(creator);
  VLOG(1) << "Have set a custom GlobalNetObserverCreator";
}

void ClearGlobalNetObservers() {
  GetNetObserverCreators()->clear();
  VLOG(1) << "All net observers cleared";
}

unique_ptr<NetBase> CreateNet(const NetDef& net_def, Workspace* ws) {
  std::shared_ptr<NetDef> tmp_net_def(new NetDef(net_def));
  return CreateNet(tmp_net_def, ws);
}

unique_ptr<NetBase> CreateNet(
    const std::shared_ptr<const NetDef>& net_def,
    Workspace* ws) {
  std::string net_type;
  if (net_def->has_type() && !net_def->type().empty()) {
    net_type = net_def->type();
  } else {
    // By default, we will return a simple network that just runs all operators
    // sequentially.
    net_type = kSimpleNet;
  }
  ApplyPotentialExecutorOverride(&net_type);
  unique_ptr<NetBase> net = NetRegistry()->Create(net_type, net_def, ws);

  VLOG(1) << "Adding a global observer to a net";
  if (net) {
    auto* observer_creators = GetNetObserverCreators();
    for (auto& creator : *observer_creators) {
      net->AttachObserver(creator(net.get()));
    }
  }
  return net;
}

TaskThreadPoolBase* ExecutorHelper::GetPool(
    const DeviceOption& /* unused */) const {
  CAFFE_THROW("Not implemented");
}

std::vector<OperatorBase*> ExecutorHelper::GetOperators() const {
  CAFFE_THROW("Not implemented");
}

int ExecutorHelper::GetNumWorkers() const {
  CAFFE_THROW("Not implemented");
}

// benchmark an individual run so that we can FeedBlobs with new inputs
// no warmup
// return time taken in microseconds
float NetBase::TEST_Benchmark_One_Run() {
  Timer timer;
  CAFFE_ENFORCE(Run(), "Run has failed.");
  return timer.MicroSeconds();
}

std::vector<float> NetBase::TEST_Benchmark(
    const int warmup_runs,
    const int main_runs,
    const bool run_individual) {
  LOG(INFO) << "Starting benchmark, running warmup runs";
  CAFFE_ENFORCE(
      warmup_runs >= 0,
      "Number of warm up runs should be non negative, provided ",
      warmup_runs);
  for (int run_idx = 0; run_idx < warmup_runs; ++run_idx) {
    CAFFE_ENFORCE(Run(), "Warmup run ", run_idx, " has failed");
  }

  LOG(INFO) << "Running main runs";
  CAFFE_ENFORCE(
      main_runs >= 0,
      "Number of main runs should be non negative, provided ",
      main_runs);

  Timer timer;
  for (int run_idx = 0; run_idx < main_runs; ++run_idx) {
    CAFFE_ENFORCE(Run(), "Main run ", run_idx, " has failed");
  }
  auto millis = timer.MilliSeconds();
  LOG(INFO) << "Main runs finished. Milliseconds per iter: "
            // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
            << millis / main_runs
            << ". Iters per second: " << 1000.0 * main_runs / millis;

  if (run_individual) {
    LOG(INFO) << "Net does not support per-op benchmark; "
                 "to run it, switch to a simple net type";
  }
  // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
  return std::vector<float>{millis / main_runs};
}

} // namespace caffe2
