//  Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
 // LICENSE file in the root directory of this source tree.

#include "jsireact/JSIExecutor.h"

#include <cxxreact/JSBigString.h>
#include <cxxreact/ModuleRegistry.h>
#include <cxxreact/ReactMarker.h>
#include <cxxreact/SystraceSection.h>
#include <folly/Conv.h>
#include <folly/json.h>
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>

#include <sstream>
#include <stdexcept>
// CRN BEGIN

#include <fstream>

// CRN END
using namespace facebook::jsi;

namespace facebook {
namespace react {

class JSIExecutor::NativeModuleProxy : public jsi::HostObject {
 public:
  NativeModuleProxy(JSIExecutor& executor) : executor_(executor) {}

  Value get(Runtime& rt, const PropNameID& name) override {
    if (name.utf8(rt) == "name") {
      return jsi::String::createFromAscii(rt, "NativeModules");
    }

    return executor_.nativeModules_.getModule(rt, name);
  }

  void set(Runtime&, const PropNameID&, const Value&) override {
    throw std::runtime_error(
        "Unable to put on NativeModules: Operation unsupported");
  }

 private:
  JSIExecutor& executor_;
};

namespace {

// basename_r isn't in all iOS SDKs, so use this simple version instead.
std::string simpleBasename(const std::string& path) {
  size_t pos = path.rfind("/");
  return (pos != std::string::npos) ? path.substr(pos) : path;
}

} // namespace

JSIExecutor::JSIExecutor(
    std::shared_ptr<jsi::Runtime> runtime,
    std::shared_ptr<ExecutorDelegate> delegate,
    Logger logger,
    const JSIScopedTimeoutInvoker& scopedTimeoutInvoker,
    RuntimeInstaller runtimeInstaller)
    : runtime_(runtime),
      delegate_(delegate),
      nativeModules_(delegate ? delegate->getModuleRegistry() : nullptr),
      logger_(logger),
      scopedTimeoutInvoker_(scopedTimeoutInvoker),
      runtimeInstaller_(runtimeInstaller) {
  runtime_->global().setProperty(
      *runtime, "__jsiExecutorDescription", runtime->description());
}

void JSIExecutor::loadApplicationScript(
    std::unique_ptr<const JSBigString> script,
    std::string sourceURL) {
  SystraceSection s("JSIExecutor::loadApplicationScript");

  // TODO: check for and use precompiled HBC

  runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(*this)));

  runtime_->global().setProperty(
      *runtime_,
      "nativeFlushQueueImmediate",
      Function::createFromHostFunction(
          *runtime_,
          PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
          1,
          [this](
              jsi::Runtime&,
              const jsi::Value&,
              const jsi::Value* args,
              size_t count) {
            if (count != 1) {
              throw std::invalid_argument(
                  "nativeFlushQueueImmediate arg count must be 1");
            }
            callNativeModules(args[0], false);
            return Value::undefined();
          }));

  runtime_->global().setProperty(
      *runtime_,
      "nativeCallSyncHook",
      Function::createFromHostFunction(
          *runtime_,
          PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
          1,
          [this](
              jsi::Runtime&,
              const jsi::Value&,
              const jsi::Value* args,
              size_t count) { return nativeCallSyncHook(args, count); }));

  if (logger_) {
    // Only inject the logging function if it was supplied by the caller.
    runtime_->global().setProperty(
        *runtime_,
        "nativeLoggingHook",
        Function::createFromHostFunction(
            *runtime_,
            PropNameID::forAscii(*runtime_, "nativeLoggingHook"),
            2,
            [this](
                jsi::Runtime&,
                const jsi::Value&,
                const jsi::Value* args,
                size_t count) {
              if (count != 2) {
                throw std::invalid_argument(
                    "nativeLoggingHook takes 2 arguments");
              }
              logger_(
                  args[0].asString(*runtime_).utf8(*runtime_),
                  folly::to<unsigned int>(args[1].asNumber()));
              return Value::undefined();
            }));
  }

  if (runtimeInstaller_) {
    runtimeInstaller_(*runtime_);
  }

  bool hasLogger(ReactMarker::logTaggedMarker);
  std::string scriptName = simpleBasename(sourceURL);
  if (hasLogger) {
    ReactMarker::logTaggedMarker(
        ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
  }
  runtime_->evaluateJavaScript(
      std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);

  flush();
  if (hasLogger) {
    ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
    ReactMarker::logTaggedMarker(
        ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
  }
}

//CRN BEGIN
void JSIExecutor::setCRNModuleIdConfig(const folly::dynamic& config) {
    m_CRNJSModuleConfig = config;
    LOG(ERROR) << "setCRNModuleIdConfig start 1" << m_isBindCRNNativeRequire;
    if (!m_isBindCRNNativeRequire) {
        runtime_->global().setProperty(
            *runtime_,
            "nativeRequire",
            Function::createFromHostFunction(
                *runtime_,
                PropNameID::forAscii(*runtime_, "nativeRequire"),
                2,
                [this](
                    Runtime& rt,
                    const facebook::jsi::Value&,
                    const facebook::jsi::Value* args,
                    size_t count) { return nativeRequire(args, count); }));
        m_isBindCRNNativeRequire = true;
    }
    LOG(ERROR) << "setCRNModuleIdConfig start 2" << m_isBindCRNNativeRequire;
}
//CRN END

void JSIExecutor::setBundleRegistry(std::unique_ptr<RAMBundleRegistry> r) {
  if (!bundleRegistry_) {
    runtime_->global().setProperty(
        *runtime_,
        "nativeRequire",
        Function::createFromHostFunction(
            *runtime_,
            PropNameID::forAscii(*runtime_, "nativeRequire"),
            2,
            [this](
                Runtime& rt,
                const facebook::jsi::Value&,
                const facebook::jsi::Value* args,
                size_t count) { return nativeRequire(args, count); }));
  }
  bundleRegistry_ = std::move(r);
}

void JSIExecutor::registerBundle(
    uint32_t bundleId,
    const std::string& bundlePath) {
  const auto tag = folly::to<std::string>(bundleId);
  ReactMarker::logTaggedMarker(
      ReactMarker::REGISTER_JS_SEGMENT_START, tag.c_str());
  if (bundleRegistry_) {
    bundleRegistry_->registerBundle(bundleId, bundlePath);
  } else {
    auto script = JSBigFileString::fromPath(bundlePath);
    runtime_->evaluateJavaScript(
        std::make_unique<BigStringBuffer>(std::move(script)),
        JSExecutor::getSyntheticBundlePath(bundleId, bundlePath));
  }
  ReactMarker::logTaggedMarker(
      ReactMarker::REGISTER_JS_SEGMENT_STOP, tag.c_str());
}

void JSIExecutor::callFunction(
    const std::string& moduleId,
    const std::string& methodId,
    const folly::dynamic& arguments) {
  SystraceSection s(
      "JSIExecutor::callFunction", "moduleId", moduleId, "methodId", methodId);
  if (!callFunctionReturnFlushedQueue_) {
    bindBridge();
  }

  // Construct the error message producer in case this times out.
  // This is executed on a background thread, so it must capture its parameters
  // by value.
  auto errorProducer = [=] {
    std::stringstream ss;
    ss << "moduleID: " << moduleId << " methodID: " << methodId
       << " arguments: " << folly::toJson(arguments);
    return ss.str();
  };

  Value ret = Value::undefined();
  try {
    scopedTimeoutInvoker_(
        [&] {
          ret = callFunctionReturnFlushedQueue_->call(
              *runtime_,
              moduleId,
              methodId,
              valueFromDynamic(*runtime_, arguments));
        },
        std::move(errorProducer));
  } catch (...) {
    std::throw_with_nested(
        std::runtime_error("Error calling " + moduleId + "." + methodId));
  }

  callNativeModules(ret, true);
}

void JSIExecutor::invokeCallback(
    const double callbackId,
    const folly::dynamic& arguments) {
  SystraceSection s("JSIExecutor::invokeCallback", "callbackId", callbackId);
  if (!invokeCallbackAndReturnFlushedQueue_) {
    bindBridge();
  }
  Value ret;
  try {
    ret = invokeCallbackAndReturnFlushedQueue_->call(
        *runtime_, callbackId, valueFromDynamic(*runtime_, arguments));
  } catch (...) {
    std::throw_with_nested(std::runtime_error(
        folly::to<std::string>("Error invoking callback ", callbackId)));
  }

  callNativeModules(ret, true);
}

void JSIExecutor::setGlobalVariable(
    std::string propName,
    std::unique_ptr<const JSBigString> jsonValue) {
  SystraceSection s("JSIExecutor::setGlobalVariable", "propName", propName);
  runtime_->global().setProperty(
      *runtime_,
      propName.c_str(),
      Value::createFromJsonUtf8(
          *runtime_,
          reinterpret_cast<const uint8_t*>(jsonValue->c_str()),
          jsonValue->size()));
}

std::string JSIExecutor::getDescription() {
  return "JSI " + runtime_->description();
}

void* JSIExecutor::getJavaScriptContext() {
  return runtime_.get();
}

bool JSIExecutor::isInspectable() {
  return runtime_->isInspectable();
}

void JSIExecutor::bindBridge() {
  std::call_once(bindFlag_, [this] {
    SystraceSection s("JSIExecutor::bindBridge (once)");
    Value batchedBridgeValue =
        runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
    if (batchedBridgeValue.isUndefined()) {
      Function requireBatchedBridge = runtime_->global().getPropertyAsFunction(
          *runtime_, "__fbRequireBatchedBridge");
      batchedBridgeValue = requireBatchedBridge.call(*runtime_);
      if (batchedBridgeValue.isUndefined()) {
        throw JSINativeException(
            "Could not get BatchedBridge, make sure your bundle is packaged correctly");
      }
    }

    Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
    callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "callFunctionReturnFlushedQueue");
    invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
        *runtime_, "invokeCallbackAndReturnFlushedQueue");
    flushedQueue_ =
        batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
    callFunctionReturnResultAndFlushedQueue_ =
        batchedBridge.getPropertyAsFunction(
            *runtime_, "callFunctionReturnResultAndFlushedQueue");
  });
}

void JSIExecutor::callNativeModules(const Value& queue, bool isEndOfBatch) {
  SystraceSection s("JSIExecutor::callNativeModules");
  // If this fails, you need to pass a fully functional delegate with a
  // module registry to the factory/ctor.
  CHECK(delegate_) << "Attempting to use native modules without a delegate";
#if 0 // maybe useful for debugging
  std::string json = runtime_->global().getPropertyAsObject(*runtime_, "JSON")
    .getPropertyAsFunction(*runtime_, "stringify").call(*runtime_, queue)
    .getString(*runtime_).utf8(*runtime_);
#endif
  delegate_->callNativeModules(
      *this, dynamicFromValue(*runtime_, queue), isEndOfBatch);
}

void JSIExecutor::flush() {
  SystraceSection s("JSIExecutor::flush");
  if (flushedQueue_) {
    callNativeModules(flushedQueue_->call(*runtime_), true);
    return;
  }

  // When a native module is called from JS, BatchedBridge.enqueueNativeCall()
  // is invoked.  For that to work, require('BatchedBridge') has to be called,
  // and when that happens, __fbBatchedBridge is set as a side effect.
  Value batchedBridge =
      runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
  // So here, if __fbBatchedBridge doesn't exist, then we know no native calls
  // have happened, and we were able to determine this without forcing
  // BatchedBridge to be loaded as a side effect.
  if (!batchedBridge.isUndefined()) {
    // If calls were made, we bind to the JS bridge methods, and use them to
    // get the pending queue of native calls.
    bindBridge();
    callNativeModules(flushedQueue_->call(*runtime_), true);
  } else if (delegate_) {
    // If we have a delegate, we need to call it; we pass a null list to
    // callNativeModules, since we know there are no native calls, without
    // calling into JS again.  If no calls were made and there's no delegate,
    // nothing happens, which is correct.
    callNativeModules(nullptr, true);
  }
}

//CRN BEGIN
    void JSIExecutor::loadModuleForCRN(std::string &modulePath, std::string &moduleDiffPath) {
        // auto sourceUrl = String::createExpectingAscii(m_context, modulePath);

        std::string code = readScriptFromFileForCRN(modulePath, moduleDiffPath);
        // auto source = adoptString(std::unique_ptr<JSBigString>(new JSBigStdString(code)));
        // evaluateScript(m_context, source, sourceUrl);
        runtime_->evaluateJavaScript(std::make_unique<StringBuffer>(code), modulePath);
    }

    std::string JSIExecutor::readScriptFromFileForCRN(const std::string& solidFile, const std::string &diffFile) {
        if (!diffFile.empty()) {
            std::ifstream jsDiffFile(diffFile);
            if (jsDiffFile) {
                std::string output;
                jsDiffFile.seekg(0, std::ios::end);
                output.reserve(jsDiffFile.tellg());
                jsDiffFile.seekg(0, std::ios::beg);
                output.assign((std::istreambuf_iterator<char>(jsDiffFile)), std::istreambuf_iterator<char>());
                return output;
            }
        }
        if (!solidFile.empty()) {
            std::ifstream jsFile(solidFile);
            if (jsFile) {
                std::string output;
                jsFile.seekg(0, std::ios::end);
                output.reserve(jsFile.tellg());
                jsFile.seekg(0, std::ios::beg);
                output.assign((std::istreambuf_iterator<char>(jsFile)), std::istreambuf_iterator<char>());
                return output;
            }
        }
        LOG(ERROR) << "Unable to load script from file: " << solidFile.c_str() << " diffFile: " << diffFile.c_str();
        return "{}";
    }
    //CRN END

Value JSIExecutor::nativeRequire(const Value* args, size_t count) {
  /* if (count > 2 || count == 0) {
    throw std::invalid_argument("Got wrong number of args");
  }

  uint32_t moduleId = folly::to<uint32_t>(args[0].getNumber());
  uint32_t bundleId = count == 2 ? folly::to<uint32_t>(args[1].getNumber()) : 0;
  auto module = bundleRegistry_->getModule(bundleId, moduleId);

  runtime_->evaluateJavaScript(
      std::make_unique<StringBuffer>(module.code), module.name);
  return facebook::jsi::Value(); */

    std::string moduleId = "";
    if (args[0].isNumber()) {
      uint32_t moduleIdInt = folly::to<uint32_t>(args[0].getNumber());
      moduleId = folly::to<std::string>(moduleIdInt);
    } else {
      moduleId = folly::to<std::string>(args[0].getString(*runtime_).utf8(*runtime_));
    }
    // uint32_t bundleId = count == 2 ? folly::to<uint32_t>(args[1].getNumber()) : 0;
    // JSStringRef jsString = JSValueToStringCopy(m_context, arguments[0], NULL);
    // String moduleId = String::adopt(m_context, jsString);
    // ReactMarker::logMarker(ReactMarker::NATIVE_REQUIRE_START);

      if (!moduleId.empty()) {
          try {
              if (m_isBindCRNNativeRequire && m_CRNJSModuleConfig.size() > 1) {

                  std::string moduleFilePath = "", moduleDiffFilePath = "";
                  //如果只包含modulePath和moduleDiff两个值，则值直接走最新module加载逻辑，否则老的config配置加载处理
                  if (m_CRNJSModuleConfig.size() > 2) {
                      moduleFilePath = m_CRNJSModuleConfig.getDefault(moduleId, "666666").getString().c_str();
                  } else {
                      std::string moduleDiffPath = m_CRNJSModuleConfig.getDefault("moduleDiff", "js-diffs").getString().c_str();
                      if (!moduleDiffPath.empty()) {
                        moduleDiffFilePath = moduleDiffPath + "/" + moduleId + ".js";
                      }

                      std::string modulePath = m_CRNJSModuleConfig.getDefault("modulePath", "js-modules").getString().c_str();
                      if (!modulePath.empty()) {
                          moduleFilePath = modulePath + "/" + moduleId + ".js";
                      }
                  }
                  loadModuleForCRN(moduleFilePath, moduleDiffFilePath);
              } else {
                  uint32_t moduleIdInt = folly::to<uint32_t>(args[0].getNumber());
                  LOG(ERROR) << "nativeRequire error, module: " << moduleId;
                  uint32_t bundleIdInt = count == 2 ? folly::to<uint32_t>(args[1].getNumber()) : 0;
                  auto module = bundleRegistry_->getModule(bundleIdInt, moduleIdInt);
                  runtime_->evaluateJavaScript(std::make_unique<StringBuffer>(module.code), module.name);
              }
          } catch (const std::exception &) {
              throw std::invalid_argument(folly::to<std::string>("Received invalid module ID: " + moduleId));
          }
      }
      // ReactMarker::logMarker(ReactMarker::NATIVE_REQUIRE_STOP);

    // auto module = bundleRegistry_->getModule(bundleId, moduleId);
    //runtime_->evaluateJavaScript(std::make_unique<StringBuffer>(module.code), module.name);
    return facebook::jsi::Value();
}

Value JSIExecutor::nativeCallSyncHook(const Value* args, size_t count) {
  if (count != 3) {
    throw std::invalid_argument("nativeCallSyncHook arg count must be 3");
  }

  if (!args[2].asObject(*runtime_).isArray(*runtime_)) {
    throw std::invalid_argument(
        folly::to<std::string>("method parameters should be array"));
  }

  MethodCallResult result = delegate_->callSerializableNativeHook(
      *this,
      static_cast<unsigned int>(args[0].getNumber()), // moduleId
      static_cast<unsigned int>(args[1].getNumber()), // methodId
      dynamicFromValue(*runtime_, args[2])); // args

  if (!result.hasValue()) {
    return Value::undefined();
  }
  return valueFromDynamic(*runtime_, result.value());
}

} // namespace react
} // namespace facebook
