/*!
 *  Copyright (c) 2017 by Contributors
 * \file verilog_module.cc
 * \brief Build verilog source code.
 */
#include <tvm/runtime/packed_func.h>
#include <tvm/codegen.h>
#include <mutex>
#include "./codegen_verilog.h"
#include "../../runtime/file_util.h"
#include "../../runtime/meta_data.h"

namespace tvm {
namespace codegen {
namespace verilog {
using runtime::TVMArgs;
using runtime::TVMRetValue;
using runtime::PackedFunc;

// Simulator function
class VerilogModuleNode : public runtime::ModuleNode {
 public:
  VerilogModuleNode() : fmt_("v") {}
  const char* type_key() const {
    return "verilog";
  }

  PackedFunc GetFunction(
      const std::string& name,
      const std::shared_ptr<ModuleNode>& sptr_to_self) final {
    CHECK(sptr_to_self.get() == this);
    if (!m_.fmap.count(name)) return PackedFunc();
    auto f = [sptr_to_self, name, this](const runtime::TVMArgs& args, TVMRetValue* rv) {
      auto* fsim = runtime::Registry::Get("tvm_callback_verilog_simulator");
      CHECK(fsim != nullptr)
        << "tvm_callback_verilog_simulator is not registered,"
        <<" did you import tvm.addon.verilog?";
      std::string code = m_.AppendSimMain(name);

      if (const auto* f = runtime::Registry::Get("tvm_callback_verilog_postproc")) {
        code = (*f)(code).operator std::string();
      }
      std::vector<TVMValue> values;
      std::vector<int> codes;
      TVMValue v;
      v.v_str = code.c_str();
      values.push_back(v);
      codes.push_back(kStr);
      for (int i = 0; i < args.num_args; ++i) {
        values.push_back(args.values[i]);
        codes.push_back(args.type_codes[i]);
      }
      fsim->CallPacked(TVMArgs(&values[0], &codes[0], args.num_args + 1), rv);
    };
    return PackedFunc(f);
  }

  std::string GetSource(const std::string& format) final {
    return m_.code;
  }

  void Init(const Array<LoweredFunc>& funcs) {
    CodeGenVerilog cg;
    cg.Init();
    for (LoweredFunc f :  funcs) {
      cg.AddFunction(f);
    }
    m_ = cg.Finish();
  }

 private:
  // the verilog code. data
  VerilogCodeGenModule m_;
  // format;
  std::string fmt_;
};

TVM_REGISTER_API("codegen.build_verilog")
.set_body([](TVMArgs args, TVMRetValue* rv) {
    std::shared_ptr<VerilogModuleNode> n =
        std::make_shared<VerilogModuleNode>();
    n->Init(args[0]);
    *rv = runtime::Module(n);
  });
}  // namespace verilog
}  // namespace codegen
}  // namespace tvm