// Copyright 2004-present Facebook. All Rights Reserved.

#pragma once

#include <atomic>
#include <functional>
#include <map>
#include <vector>

#include <folly/dynamic.h>

#include "Executor.h"
#include "ExecutorToken.h"
#include "JSModulesUnbundle.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "NativeModule.h"
#include "Value.h"

namespace folly {

struct dynamic;

}

namespace facebook {
namespace react {

struct InstanceCallback;
class ModuleRegistry;

class ExecutorRegistration {
public:
  ExecutorRegistration(
      std::unique_ptr<JSExecutor> executor,
      std::shared_ptr<MessageQueueThread> executorMessageQueueThread) :
    executor_(std::move(executor)),
    messageQueueThread_(executorMessageQueueThread) {}

  std::unique_ptr<JSExecutor> executor_;
  std::shared_ptr<MessageQueueThread> messageQueueThread_;
};

class JsToNativeBridge;

// This class manages calls from native code to JS.  It also manages
// executors and their threads.  This part is used by both bridges for
// now, but further refactorings should separate the bridges more
// fully #11247981.
class NativeToJsBridge {
public:
  friend class JsToNativeBridge;

  /**
   * This must be called on the main JS thread.
   */
  NativeToJsBridge(
      JSExecutorFactory* jsExecutorFactory,
      std::shared_ptr<ModuleRegistry> registry,
      std::shared_ptr<MessageQueueThread> jsQueue,
      std::unique_ptr<MessageQueueThread> nativeQueue,
      std::shared_ptr<InstanceCallback> callback);
  virtual ~NativeToJsBridge();

  /**
   * Executes a function with the module ID and method ID and any additional
   * arguments in JS.
   */
  void callFunction(
    ExecutorToken executorToken,
    const std::string& moduleId,
    const std::string& methodId,
    const folly::dynamic& args,
    const std::string& tracingName);

  /**
   * Invokes a callback with the cbID, and optional additional arguments in JS.
   */
  void invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& args);

  /**
   * Starts the JS application from an "bundle", i.e. a JavaScript file that
   * contains code for all modules and a runtime that resolves and
   * executes modules.
   */
  void loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL);

  /**
   * An "unbundle" is a backend that stores and injects JavaScript modules as
   * individual scripts, rather than bundling all of them into a single scrupt.
   *
   * Loading an unbundle means setting the storage backend and executing the
   * startup script.
   */
  void loadApplicationUnbundle(
    std::unique_ptr<JSModulesUnbundle> unbundle,
    std::unique_ptr<const JSBigString> startupCode,
    std::string sourceURL);
  void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
  void* getJavaScriptContext();
  bool supportsProfiling();
  void startProfiler(const std::string& title);
  void stopProfiler(const std::string& title, const std::string& filename);
  void handleMemoryPressureUiHidden();
  void handleMemoryPressureModerate();
  void handleMemoryPressureCritical();

  /**
   * Returns the ExecutorToken corresponding to the main JSExecutor.
   */
  ExecutorToken getMainExecutorToken() const;

  /**
   * Synchronously tears down the bridge and the main executor.
   */
  void destroy();
private:
  /**
   * Registers the given JSExecutor which runs on the given MessageQueueThread
   * with the NativeToJsBridge. Part of this registration is transfering
   * ownership of this JSExecutor to the NativeToJsBridge for the duration of
   * the registration.
   *
   * Returns a ExecutorToken which can be used to refer to this JSExecutor
   * in the NativeToJsBridge.
   */
  ExecutorToken registerExecutor(
      ExecutorToken token,
      std::unique_ptr<JSExecutor> executor,
      std::shared_ptr<MessageQueueThread> executorMessageQueueThread);

  /**
   * Unregisters a JSExecutor that was previously registered with this NativeToJsBridge
   * using registerExecutor.
   */
  std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executorToken);

  void runOnExecutorQueue(ExecutorToken token, std::function<void(JSExecutor*)> task);

  // This is used to avoid a race condition where a proxyCallback gets queued
  // after ~NativeToJsBridge(), on the same thread. In that case, the callback
  // will try to run the task on m_callback which will have been destroyed
  // within ~NativeToJsBridge(), thus causing a SIGSEGV.
  std::shared_ptr<bool> m_destroyed;
  JSExecutor* m_mainExecutor;
  ExecutorToken m_mainExecutorToken;
  std::shared_ptr<JsToNativeBridge> m_delegate;
  std::unordered_map<JSExecutor*, ExecutorToken> m_executorTokenMap;
  std::unordered_map<ExecutorToken, ExecutorRegistration> m_executorMap;
  std::mutex m_registrationMutex;
  #ifdef WITH_FBSYSTRACE
  std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT();
  #endif

  MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken);
  JSExecutor* getExecutor(const ExecutorToken& executorToken);
  ExecutorToken getTokenForExecutor(JSExecutor& executor);
};

} }
